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;
+ }
}
}