1
0
mirror of https://github.com/JKorf/CryptoExchange.Net synced 2025-06-08 16:36:15 +00:00

Added rate limit options for socket connections

This commit is contained in:
Jkorf 2021-08-26 16:15:01 +02:00
parent 76d5c1c299
commit ab072cb2f9
4 changed files with 54 additions and 144 deletions

View File

@ -1513,6 +1513,11 @@
Reconnecting Reconnecting
</summary> </summary>
</member> </member>
<member name="P:CryptoExchange.Net.Interfaces.IWebsocket.RatelimitPerSecond">
<summary>
The max amount of outgoing messages per second
</summary>
</member>
<member name="P:CryptoExchange.Net.Interfaces.IWebsocket.DataInterpreterBytes"> <member name="P:CryptoExchange.Net.Interfaces.IWebsocket.DataInterpreterBytes">
<summary> <summary>
Handler for byte data 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 If a message is received on the socket which is not handled by a handler this boolean determines whether this logs an error message
</summary> </summary>
</member> </member>
<member name="P:CryptoExchange.Net.SocketClient.RateLimitPerSocketPerSecond">
<summary>
The max amount of outgoing messages per socket per second
</summary>
</member>
<member name="M:CryptoExchange.Net.SocketClient.#ctor(System.String,CryptoExchange.Net.Objects.SocketClientOptions,CryptoExchange.Net.Authentication.AuthenticationProvider)"> <member name="M:CryptoExchange.Net.SocketClient.#ctor(System.String,CryptoExchange.Net.Objects.SocketClientOptions,CryptoExchange.Net.Authentication.AuthenticationProvider)">
<summary> <summary>
ctor ctor
@ -3387,6 +3397,11 @@
Encoding used for decoding the received bytes into a string Encoding used for decoding the received bytes into a string
</summary> </summary>
</member> </member>
<member name="P:CryptoExchange.Net.Sockets.CryptoExchangeWebSocketClient.RatelimitPerSecond">
<summary>
The max amount of outgoing messages per second
</summary>
</member>
<member name="P:CryptoExchange.Net.Sockets.CryptoExchangeWebSocketClient.Timeout"> <member name="P:CryptoExchange.Net.Sockets.CryptoExchangeWebSocketClient.Timeout">
<summary> <summary>
The timespan no data is received on the socket. If no data is received within this time an error is generated 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 @@
<member name="M:CryptoExchange.Net.Sockets.WebsocketFactory.CreateWebsocket(CryptoExchange.Net.Logging.Log,System.String,System.Collections.Generic.IDictionary{System.String,System.String},System.Collections.Generic.IDictionary{System.String,System.String})"> <member name="M:CryptoExchange.Net.Sockets.WebsocketFactory.CreateWebsocket(CryptoExchange.Net.Logging.Log,System.String,System.Collections.Generic.IDictionary{System.String,System.String},System.Collections.Generic.IDictionary{System.String,System.String})">
<inheritdoc /> <inheritdoc />
</member> </member>
<member name="T:System.Diagnostics.CodeAnalysis.AllowNullAttribute">
<summary>
Specifies that <see langword="null"/> is allowed as an input even if the
corresponding type disallows it.
</summary>
</member>
<member name="M:System.Diagnostics.CodeAnalysis.AllowNullAttribute.#ctor">
<summary>
Initializes a new instance of the <see cref="T:System.Diagnostics.CodeAnalysis.AllowNullAttribute"/> class.
</summary>
</member>
<member name="T:System.Diagnostics.CodeAnalysis.DisallowNullAttribute">
<summary>
Specifies that <see langword="null"/> is disallowed as an input even if the
corresponding type allows it.
</summary>
</member>
<member name="M:System.Diagnostics.CodeAnalysis.DisallowNullAttribute.#ctor">
<summary>
Initializes a new instance of the <see cref="T:System.Diagnostics.CodeAnalysis.DisallowNullAttribute"/> class.
</summary>
</member>
<member name="T:System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute">
<summary>
Specifies that a method that will never return under any circumstance.
</summary>
</member>
<member name="M:System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute.#ctor">
<summary>
Initializes a new instance of the <see cref="T:System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute"/> class.
</summary>
</member>
<member name="T:System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute">
<summary>
Specifies that the method will not return if the associated <see cref="T:System.Boolean"/>
parameter is passed the specified value.
</summary>
</member>
<member name="P:System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute.ParameterValue">
<summary>
Gets the condition parameter value.
Code after the method is considered unreachable by diagnostics if the argument
to the associated parameter matches this value.
</summary>
</member>
<member name="M:System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute.#ctor(System.Boolean)">
<summary>
Initializes a new instance of the <see cref="T:System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute"/>
class with the specified parameter value.
</summary>
<param name="parameterValue">
The condition parameter value.
Code after the method is considered unreachable by diagnostics if the argument
to the associated parameter matches this value.
</param>
</member>
<member name="T:System.Diagnostics.CodeAnalysis.MaybeNullAttribute">
<summary>
Specifies that an output may be <see langword="null"/> even if the
corresponding type disallows it.
</summary>
</member>
<member name="M:System.Diagnostics.CodeAnalysis.MaybeNullAttribute.#ctor">
<summary>
Initializes a new instance of the <see cref="T:System.Diagnostics.CodeAnalysis.MaybeNullAttribute"/> class.
</summary>
</member>
<member name="T:System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute">
<summary>
Specifies that when a method returns <see cref="P:System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute.ReturnValue"/>,
the parameter may be <see langword="null"/> even if the corresponding type disallows it.
</summary>
</member>
<member name="P:System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute.ReturnValue">
<summary>
Gets the return value condition.
If the method returns this value, the associated parameter may be <see langword="null"/>.
</summary>
</member>
<member name="M:System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute.#ctor(System.Boolean)">
<summary>
Initializes the attribute with the specified return value condition.
</summary>
<param name="returnValue">
The return value condition.
If the method returns this value, the associated parameter may be <see langword="null"/>.
</param>
</member>
<member name="T:System.Diagnostics.CodeAnalysis.NotNullAttribute">
<summary>
Specifies that an output is not <see langword="null"/> even if the
corresponding type allows it.
</summary>
</member>
<member name="M:System.Diagnostics.CodeAnalysis.NotNullAttribute.#ctor">
<summary>
Initializes a new instance of the <see cref="T:System.Diagnostics.CodeAnalysis.NotNullAttribute"/> class.
</summary>
</member>
<member name="T:System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute">
<summary>
Specifies that the output will be non-<see langword="null"/> if the
named parameter is non-<see langword="null"/>.
</summary>
</member>
<member name="P:System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute.ParameterName">
<summary>
Gets the associated parameter name.
The output will be non-<see langword="null"/> if the argument to the
parameter specified is non-<see langword="null"/>.
</summary>
</member>
<member name="M:System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute.#ctor(System.String)">
<summary>
Initializes the attribute with the associated parameter name.
</summary>
<param name="parameterName">
The associated parameter name.
The output will be non-<see langword="null"/> if the argument to the
parameter specified is non-<see langword="null"/>.
</param>
</member>
<member name="T:System.Diagnostics.CodeAnalysis.NotNullWhenAttribute">
<summary>
Specifies that when a method returns <see cref="P:System.Diagnostics.CodeAnalysis.NotNullWhenAttribute.ReturnValue"/>,
the parameter will not be <see langword="null"/> even if the corresponding type allows it.
</summary>
</member>
<member name="P:System.Diagnostics.CodeAnalysis.NotNullWhenAttribute.ReturnValue">
<summary>
Gets the return value condition.
If the method returns this value, the associated parameter will not be <see langword="null"/>.
</summary>
</member>
<member name="M:System.Diagnostics.CodeAnalysis.NotNullWhenAttribute.#ctor(System.Boolean)">
<summary>
Initializes the attribute with the specified return value condition.
</summary>
<param name="returnValue">
The return value condition.
If the method returns this value, the associated parameter will not be <see langword="null"/>.
</param>
</member>
</members> </members>
</doc> </doc>

View File

@ -45,6 +45,10 @@ namespace CryptoExchange.Net.Interfaces
/// </summary> /// </summary>
bool Reconnecting { get; set; } bool Reconnecting { get; set; }
/// <summary> /// <summary>
/// The max amount of outgoing messages per second
/// </summary>
public int? RatelimitPerSecond { get; set; }
/// <summary>
/// Handler for byte data /// Handler for byte data
/// </summary> /// </summary>
Func<byte[], string>? DataInterpreterBytes { get; set; } Func<byte[], string>? DataInterpreterBytes { get; set; }

View File

@ -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 /// If a message is received on the socket which is not handled by a handler this boolean determines whether this logs an error message
/// </summary> /// </summary>
protected internal bool UnhandledMessageExpected { get; set; } protected internal bool UnhandledMessageExpected { get; set; }
/// <summary>
/// The max amount of outgoing messages per socket per second
/// </summary>
protected internal int? RateLimitPerSocketPerSecond { get; set; }
#endregion #endregion
/// <summary> /// <summary>
@ -531,6 +536,7 @@ namespace CryptoExchange.Net
socket.Timeout = SocketNoDataTimeout; socket.Timeout = SocketNoDataTimeout;
socket.DataInterpreterBytes = dataInterpreterBytes; socket.DataInterpreterBytes = dataInterpreterBytes;
socket.DataInterpreterString = dataInterpreterString; socket.DataInterpreterString = dataInterpreterString;
socket.RatelimitPerSecond = RateLimitPerSocketPerSecond;
socket.OnError += e => socket.OnError += e =>
{ {
if(e is WebSocketException wse) if(e is WebSocketException wse)

View File

@ -36,6 +36,7 @@ namespace CryptoExchange.Net.Sockets
private bool _closing; private bool _closing;
private bool _startedSent; private bool _startedSent;
private bool _startedReceive; private bool _startedReceive;
private readonly List<DateTime> outgoingMessages;
/// <summary> /// <summary>
/// Log /// Log
@ -115,6 +116,10 @@ namespace CryptoExchange.Net.Sockets
_encoding = value; _encoding = value;
} }
} }
/// <summary>
/// The max amount of outgoing messages per second
/// </summary>
public int? RatelimitPerSecond { get; set; }
/// <summary> /// <summary>
/// The timespan no data is received on the socket. If no data is received within this time an error is generated /// 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.cookies = cookies;
this.headers = headers; this.headers = headers;
outgoingMessages = new List<DateTime>();
_sendEvent = new AutoResetEvent(false); _sendEvent = new AutoResetEvent(false);
_sendBuffer = new ConcurrentQueue<byte[]>(); _sendBuffer = new ConcurrentQueue<byte[]>();
_ctsSource = new CancellationTokenSource(); _ctsSource = new CancellationTokenSource();
@ -353,9 +359,24 @@ namespace CryptoExchange.Net.Sockets
while (_sendBuffer.TryDequeue(out var data)) 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 try
{ {
log.Write(LogLevel.Debug, "Sending " + Encoding.UTF8.GetString(data));
await _socket.SendAsync(new ArraySegment<byte>(data, 0, data.Length), WebSocketMessageType.Text, true, _ctsSource.Token).ConfigureAwait(false); await _socket.SendAsync(new ArraySegment<byte>(data, 0, data.Length), WebSocketMessageType.Text, true, _ctsSource.Token).ConfigureAwait(false);
} }
catch (OperationCanceledException) catch (OperationCanceledException)
@ -579,5 +600,12 @@ namespace CryptoExchange.Net.Sockets
return lastStreamId; return lastStreamId;
} }
} }
private int MessagesSentLastSecond()
{
var testTime = DateTime.UtcNow;
outgoingMessages.RemoveAll(r => testTime - r > TimeSpan.FromSeconds(1));
return outgoingMessages.Count;
}
} }
} }