diff --git a/CryptoExchange.Net/CryptoExchange.Net.xml b/CryptoExchange.Net/CryptoExchange.Net.xml index e8565c6..b7eea1a 100644 --- a/CryptoExchange.Net/CryptoExchange.Net.xml +++ b/CryptoExchange.Net/CryptoExchange.Net.xml @@ -1513,6 +1513,11 @@ Reconnecting + + + The max amount of outgoing messages per second + + Handler for byte data @@ -3079,6 +3084,11 @@ If a message is received on the socket which is not handled by a handler this boolean determines whether this logs an error message + + + The max amount of outgoing messages per socket per second + + ctor @@ -3387,6 +3397,11 @@ Encoding used for decoding the received bytes into a string + + + The max amount of outgoing messages per second + + The timespan no data is received on the socket. If no data is received within this time an error is generated @@ -3889,148 +3904,5 @@ - - - Specifies that is allowed as an input even if the - corresponding type disallows it. - - - - - Initializes a new instance of the class. - - - - - Specifies that is disallowed as an input even if the - corresponding type allows it. - - - - - Initializes a new instance of the class. - - - - - Specifies that a method that will never return under any circumstance. - - - - - Initializes a new instance of the class. - - - - - Specifies that the method will not return if the associated - parameter is passed the specified value. - - - - - Gets the condition parameter value. - Code after the method is considered unreachable by diagnostics if the argument - to the associated parameter matches this value. - - - - - Initializes a new instance of the - class with the specified parameter value. - - - The condition parameter value. - Code after the method is considered unreachable by diagnostics if the argument - to the associated parameter matches this value. - - - - - Specifies that an output may be even if the - corresponding type disallows it. - - - - - Initializes a new instance of the class. - - - - - Specifies that when a method returns , - the parameter may be even if the corresponding type disallows it. - - - - - Gets the return value condition. - If the method returns this value, the associated parameter may be . - - - - - Initializes the attribute with the specified return value condition. - - - The return value condition. - If the method returns this value, the associated parameter may be . - - - - - Specifies that an output is not even if the - corresponding type allows it. - - - - - Initializes a new instance of the class. - - - - - Specifies that the output will be non- if the - named parameter is non-. - - - - - Gets the associated parameter name. - The output will be non- if the argument to the - parameter specified is non-. - - - - - Initializes the attribute with the associated parameter name. - - - The associated parameter name. - The output will be non- if the argument to the - parameter specified is non-. - - - - - Specifies that when a method returns , - the parameter will not be even if the corresponding type allows it. - - - - - Gets the return value condition. - If the method returns this value, the associated parameter will not be . - - - - - Initializes the attribute with the specified return value condition. - - - The return value condition. - If the method returns this value, the associated parameter will not be . - - diff --git a/CryptoExchange.Net/Interfaces/IWebsocket.cs b/CryptoExchange.Net/Interfaces/IWebsocket.cs index 170a584..b75b7b6 100644 --- a/CryptoExchange.Net/Interfaces/IWebsocket.cs +++ b/CryptoExchange.Net/Interfaces/IWebsocket.cs @@ -45,6 +45,10 @@ namespace CryptoExchange.Net.Interfaces /// bool Reconnecting { get; set; } /// + /// The max amount of outgoing messages per second + /// + public int? RatelimitPerSecond { get; set; } + /// /// Handler for byte data /// Func? DataInterpreterBytes { get; set; } diff --git a/CryptoExchange.Net/SocketClient.cs b/CryptoExchange.Net/SocketClient.cs index 637c505..73604ec 100644 --- a/CryptoExchange.Net/SocketClient.cs +++ b/CryptoExchange.Net/SocketClient.cs @@ -91,6 +91,11 @@ namespace CryptoExchange.Net /// If a message is received on the socket which is not handled by a handler this boolean determines whether this logs an error message /// protected internal bool UnhandledMessageExpected { get; set; } + + /// + /// The max amount of outgoing messages per socket per second + /// + protected internal int? RateLimitPerSocketPerSecond { get; set; } #endregion /// @@ -531,6 +536,7 @@ namespace CryptoExchange.Net socket.Timeout = SocketNoDataTimeout; socket.DataInterpreterBytes = dataInterpreterBytes; socket.DataInterpreterString = dataInterpreterString; + socket.RatelimitPerSecond = RateLimitPerSocketPerSecond; socket.OnError += e => { if(e is WebSocketException wse) diff --git a/CryptoExchange.Net/Sockets/CryptoExchangeWebSocketClient.cs b/CryptoExchange.Net/Sockets/CryptoExchangeWebSocketClient.cs index f4446b0..f511880 100644 --- a/CryptoExchange.Net/Sockets/CryptoExchangeWebSocketClient.cs +++ b/CryptoExchange.Net/Sockets/CryptoExchangeWebSocketClient.cs @@ -36,6 +36,7 @@ namespace CryptoExchange.Net.Sockets private bool _closing; private bool _startedSent; private bool _startedReceive; + private readonly List outgoingMessages; /// /// Log @@ -115,6 +116,10 @@ namespace CryptoExchange.Net.Sockets _encoding = value; } } + /// + /// The max amount of outgoing messages per second + /// + public int? RatelimitPerSecond { get; set; } /// /// The timespan no data is received on the socket. If no data is received within this time an error is generated @@ -178,6 +183,7 @@ namespace CryptoExchange.Net.Sockets this.cookies = cookies; this.headers = headers; + outgoingMessages = new List(); _sendEvent = new AutoResetEvent(false); _sendBuffer = new ConcurrentQueue(); _ctsSource = new CancellationTokenSource(); @@ -353,9 +359,24 @@ namespace CryptoExchange.Net.Sockets while (_sendBuffer.TryDequeue(out var data)) { + if(RatelimitPerSecond != null) + { + // Wait for rate limit + DateTime? start = null; + while (MessagesSentLastSecond() >= RatelimitPerSecond) + { + start = DateTime.UtcNow; + await Task.Delay(10).ConfigureAwait(false); + } + + if (start != null) + log.Write(LogLevel.Trace, $"Websocket sent delayed {Math.Round((DateTime.UtcNow - start.Value).TotalMilliseconds)}ms because of rate limit"); + + outgoingMessages.Add(DateTime.UtcNow); + } + try { - log.Write(LogLevel.Debug, "Sending " + Encoding.UTF8.GetString(data)); await _socket.SendAsync(new ArraySegment(data, 0, data.Length), WebSocketMessageType.Text, true, _ctsSource.Token).ConfigureAwait(false); } catch (OperationCanceledException) @@ -579,5 +600,12 @@ namespace CryptoExchange.Net.Sockets return lastStreamId; } } + + private int MessagesSentLastSecond() + { + var testTime = DateTime.UtcNow; + outgoingMessages.RemoveAll(r => testTime - r > TimeSpan.FromSeconds(1)); + return outgoingMessages.Count; + } } }