mirror of
https://github.com/JKorf/CryptoExchange.Net.git
synced 2026-08-12 17:03:10 +00:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d06bd5f176 | |||
| d55fc8da65 | |||
| 01184f2c5d | |||
| cadc93c2f0 | |||
| 2600a51461 | |||
| 9e6a86ba8b | |||
| c4430d63fa | |||
| f3e1cfef33 | |||
| cc3053719c | |||
| cd6907e601 | |||
| 8fe00693bd | |||
| fb90d1e015 | |||
| 4b44861e43 | |||
| e42ca4ab5a | |||
| 5b97f6dd67 | |||
| a9813ecb0a |
@@ -6,10 +6,10 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<packagereference Include="Microsoft.NET.Test.Sdk" Version="17.1.0-preview-20211130-02"></packagereference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0-preview-20211130-02"></PackageReference>
|
||||
<PackageReference Include="Moq" Version="4.16.1" />
|
||||
<packagereference Include="NUnit" Version="3.13.2"></packagereference>
|
||||
<packagereference Include="NUnit3TestAdapter" Version="4.2.0"></packagereference>
|
||||
<PackageReference Include="NUnit" Version="3.13.2"></PackageReference>
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.2.0"></PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -40,6 +40,10 @@ namespace CryptoExchange.Net
|
||||
/// </summary>
|
||||
protected int MaxSocketConnections { get; set; } = 9999;
|
||||
/// <summary>
|
||||
/// Keep alive interval for websocket connection
|
||||
/// </summary>
|
||||
protected TimeSpan KeepAliveInterval { get; set; } = TimeSpan.FromSeconds(10);
|
||||
/// <summary>
|
||||
/// Delegate used for processing byte data received from socket connections before it is processed by handlers
|
||||
/// </summary>
|
||||
protected Func<byte[], string>? dataInterpreterBytes;
|
||||
@@ -574,6 +578,7 @@ namespace CryptoExchange.Net
|
||||
if (ClientOptions.Proxy != null)
|
||||
socket.SetProxy(ClientOptions.Proxy);
|
||||
|
||||
socket.KeepAliveInterval = KeepAliveInterval;
|
||||
socket.Timeout = ClientOptions.SocketNoDataTimeout;
|
||||
socket.DataInterpreterBytes = dataInterpreterBytes;
|
||||
socket.DataInterpreterString = dataInterpreterString;
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace CryptoExchange.Net.Converters
|
||||
if(reader.TokenType is JsonToken.Integer)
|
||||
{
|
||||
var longValue = (long)reader.Value;
|
||||
if (longValue == 0)
|
||||
if (longValue == 0 || longValue == -1)
|
||||
return objectType == typeof(DateTime) ? default(DateTime): null;
|
||||
if (longValue < 19999999999)
|
||||
return ConvertFromSeconds(longValue);
|
||||
@@ -45,6 +45,9 @@ namespace CryptoExchange.Net.Converters
|
||||
else if (reader.TokenType is JsonToken.Float)
|
||||
{
|
||||
var doubleValue = (double)reader.Value;
|
||||
if (doubleValue == 0 || doubleValue == -1)
|
||||
return objectType == typeof(DateTime) ? default(DateTime) : null;
|
||||
|
||||
if (doubleValue < 19999999999)
|
||||
return ConvertFromSeconds(doubleValue);
|
||||
|
||||
@@ -56,6 +59,9 @@ namespace CryptoExchange.Net.Converters
|
||||
if (string.IsNullOrWhiteSpace(stringValue))
|
||||
return null;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(stringValue) || stringValue == "0" || stringValue == "-1")
|
||||
return objectType == typeof(DateTime) ? default(DateTime) : null;
|
||||
|
||||
if (stringValue.Length == 8)
|
||||
{
|
||||
// Parse 20211103 format
|
||||
|
||||
@@ -6,16 +6,16 @@
|
||||
<PackageId>CryptoExchange.Net</PackageId>
|
||||
<Authors>JKorf</Authors>
|
||||
<Description>A base package for implementing cryptocurrency API's</Description>
|
||||
<PackageVersion>5.1.9</PackageVersion>
|
||||
<AssemblyVersion>5.1.9</AssemblyVersion>
|
||||
<FileVersion>5.1.9</FileVersion>
|
||||
<PackageVersion>5.1.11</PackageVersion>
|
||||
<AssemblyVersion>5.1.11</AssemblyVersion>
|
||||
<FileVersion>5.1.11</FileVersion>
|
||||
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<RepositoryUrl>https://github.com/JKorf/CryptoExchange.Net.git</RepositoryUrl>
|
||||
<PackageProjectUrl>https://github.com/JKorf/CryptoExchange.Net</PackageProjectUrl>
|
||||
<NeutralLanguage>en</NeutralLanguage>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<PackageReleaseNotes>5.1.9 - Added latency to the timesync calculation, Small fix for exception in socket close handling</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>5.1.11 - Added KeepAliveInterval setting, Fixed port not being copied when setting parameters on request, Fixed inconsistent PackageReference casing in csproj</PackageReleaseNotes>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>9.0</LangVersion>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
|
||||
@@ -426,6 +426,7 @@ namespace CryptoExchange.Net
|
||||
var uriBuilder = new UriBuilder();
|
||||
uriBuilder.Scheme = baseUri.Scheme;
|
||||
uriBuilder.Host = baseUri.Host;
|
||||
uriBuilder.Port = baseUri.Port;
|
||||
uriBuilder.Path = baseUri.AbsolutePath;
|
||||
var httpValueCollection = HttpUtility.ParseQueryString(string.Empty);
|
||||
foreach (var parameter in parameters)
|
||||
@@ -454,6 +455,7 @@ namespace CryptoExchange.Net
|
||||
var uriBuilder = new UriBuilder();
|
||||
uriBuilder.Scheme = baseUri.Scheme;
|
||||
uriBuilder.Host = baseUri.Host;
|
||||
uriBuilder.Port = baseUri.Port;
|
||||
uriBuilder.Path = baseUri.AbsolutePath;
|
||||
var httpValueCollection = HttpUtility.ParseQueryString(string.Empty);
|
||||
foreach (var parameter in parameters)
|
||||
|
||||
@@ -77,6 +77,10 @@ namespace CryptoExchange.Net.Interfaces
|
||||
/// </summary>
|
||||
TimeSpan Timeout { get; set; }
|
||||
/// <summary>
|
||||
/// The interval at which to send a ping frame to the server
|
||||
/// </summary>
|
||||
TimeSpan KeepAliveInterval { get; set; }
|
||||
/// <summary>
|
||||
/// Set a proxy to use when connecting
|
||||
/// </summary>
|
||||
/// <param name="proxy"></param>
|
||||
|
||||
@@ -258,24 +258,32 @@ namespace CryptoExchange.Net.OrderBook
|
||||
}
|
||||
|
||||
_subscription = startResult.Data;
|
||||
_subscription.ConnectionLost += () =>
|
||||
{
|
||||
log.Write(LogLevel.Warning, $"{Id} order book {Symbol} connection lost");
|
||||
Status = OrderBookStatus.Reconnecting;
|
||||
Reset();
|
||||
};
|
||||
_subscription.ConnectionClosed += () =>
|
||||
{
|
||||
log.Write(LogLevel.Warning, $"{Id} order book {Symbol} disconnected");
|
||||
Status = OrderBookStatus.Disconnected;
|
||||
_ = StopAsync();
|
||||
};
|
||||
_subscription.ConnectionLost += HandleConnectionLost;
|
||||
_subscription.ConnectionClosed += HandleConnectionClosed;
|
||||
_subscription.ConnectionRestored += HandleConnectionRestored;
|
||||
|
||||
_subscription.ConnectionRestored += async time => await ResyncAsync().ConfigureAwait(false);
|
||||
Status = OrderBookStatus.Synced;
|
||||
return new CallResult<bool>(true);
|
||||
}
|
||||
|
||||
private void HandleConnectionLost() {
|
||||
log.Write(LogLevel.Warning, $"{Id} order book {Symbol} connection lost");
|
||||
if (Status != OrderBookStatus.Disposed) {
|
||||
Status = OrderBookStatus.Reconnecting;
|
||||
Reset();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleConnectionClosed() {
|
||||
log.Write(LogLevel.Warning, $"{Id} order book {Symbol} disconnected");
|
||||
Status = OrderBookStatus.Disconnected;
|
||||
_ = StopAsync();
|
||||
}
|
||||
|
||||
private async void HandleConnectionRestored(TimeSpan _) {
|
||||
await ResyncAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task StopAsync()
|
||||
{
|
||||
@@ -286,8 +294,12 @@ namespace CryptoExchange.Net.OrderBook
|
||||
if (_processTask != null)
|
||||
await _processTask.ConfigureAwait(false);
|
||||
|
||||
if (_subscription != null)
|
||||
if (_subscription != null) {
|
||||
await _subscription.CloseAsync().ConfigureAwait(false);
|
||||
_subscription.ConnectionLost -= HandleConnectionLost;
|
||||
_subscription.ConnectionClosed -= HandleConnectionClosed;
|
||||
_subscription.ConnectionRestored -= HandleConnectionRestored;
|
||||
}
|
||||
log.Write(LogLevel.Trace, $"{Id} order book {Symbol} stopped");
|
||||
}
|
||||
|
||||
@@ -601,13 +613,13 @@ namespace CryptoExchange.Net.OrderBook
|
||||
|
||||
private async Task ProcessQueue()
|
||||
{
|
||||
while (Status != OrderBookStatus.Disconnected)
|
||||
while (Status != OrderBookStatus.Disconnected && Status != OrderBookStatus.Disposed)
|
||||
{
|
||||
await _queueEvent.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
while (_processQueue.TryDequeue(out var item))
|
||||
{
|
||||
if (Status == OrderBookStatus.Disconnected)
|
||||
if (Status == OrderBookStatus.Disconnected || Status == OrderBookStatus.Disposed)
|
||||
break;
|
||||
|
||||
if (_stopProcessing)
|
||||
|
||||
@@ -122,6 +122,9 @@ namespace CryptoExchange.Net.Sockets
|
||||
/// <inheritdoc />
|
||||
public TimeSpan Timeout { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public TimeSpan KeepAliveInterval { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public double IncomingKbps
|
||||
{
|
||||
@@ -332,7 +335,7 @@ namespace CryptoExchange.Net.Sockets
|
||||
socket.Options.Cookies = cookieContainer;
|
||||
foreach (var header in headers)
|
||||
socket.Options.SetRequestHeader(header.Key, header.Value);
|
||||
socket.Options.KeepAliveInterval = TimeSpan.FromSeconds(10);
|
||||
socket.Options.KeepAliveInterval = KeepAliveInterval;
|
||||
socket.Options.SetBuffer(65536, 65536); // Setting it to anything bigger than 65536 throws an exception in .net framework
|
||||
return socket;
|
||||
}
|
||||
|
||||
@@ -141,6 +141,8 @@ namespace CryptoExchange.Net.Sockets
|
||||
private readonly List<PendingRequest> pendingRequests;
|
||||
private Task? _socketProcessReconnectTask;
|
||||
|
||||
private SocketStatus _status;
|
||||
|
||||
/// <summary>
|
||||
/// The underlying websocket
|
||||
/// </summary>
|
||||
@@ -217,10 +219,24 @@ namespace CryptoExchange.Net.Sockets
|
||||
subscription.CancellationTokenRegistration.Value.Dispose();
|
||||
}
|
||||
}
|
||||
await _socket.CloseAsync().ConfigureAwait(false);
|
||||
|
||||
if (_socketProcessReconnectTask != null)
|
||||
await _socketProcessReconnectTask.ConfigureAwait(false);
|
||||
if (_status == SocketStatus.Reconnecting)
|
||||
{
|
||||
// Wait for reconnect task to finish
|
||||
log.Write(LogLevel.Trace, "In reconnecting state, waiting for reconnecting to end");
|
||||
if (_socketProcessReconnectTask != null)
|
||||
await _socketProcessReconnectTask.ConfigureAwait(false);
|
||||
|
||||
await _socket.CloseAsync().ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Close before waiting for process task to finish
|
||||
await _socket.CloseAsync().ConfigureAwait(false);
|
||||
|
||||
if (_socketProcessReconnectTask != null)
|
||||
await _socketProcessReconnectTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
_socket.Dispose();
|
||||
}
|
||||
@@ -232,7 +248,7 @@ namespace CryptoExchange.Net.Sockets
|
||||
/// <returns></returns>
|
||||
public async Task CloseAsync(SocketSubscription subscription)
|
||||
{
|
||||
if (!_socket.IsOpen)
|
||||
if (!_socket.IsOpen || _status == SocketStatus.Disposed)
|
||||
return;
|
||||
|
||||
if (subscription.CancellationTokenRegistration.HasValue)
|
||||
@@ -255,9 +271,11 @@ namespace CryptoExchange.Net.Sockets
|
||||
private void StartProcessingTask()
|
||||
{
|
||||
log.Write(LogLevel.Trace, $"Starting {SocketId} process/reconnect task");
|
||||
_status = SocketStatus.Processing;
|
||||
_socketProcessReconnectTask = Task.Run(async () =>
|
||||
{
|
||||
await _socket.ProcessAsync().ConfigureAwait(false);
|
||||
_status = SocketStatus.Reconnecting;
|
||||
await ReconnectAsync().ConfigureAwait(false);
|
||||
log.Write(LogLevel.Trace, $"Process/reconnect {SocketId} task finished");
|
||||
});
|
||||
@@ -283,7 +301,7 @@ namespace CryptoExchange.Net.Sockets
|
||||
if (!lostTriggered)
|
||||
{
|
||||
lostTriggered = true;
|
||||
ConnectionLost?.Invoke();
|
||||
_ = Task.Run(() => ConnectionLost?.Invoke());
|
||||
}
|
||||
|
||||
while (ShouldReconnect)
|
||||
@@ -372,7 +390,7 @@ namespace CryptoExchange.Net.Sockets
|
||||
if (lostTriggered)
|
||||
{
|
||||
lostTriggered = false;
|
||||
_ = Task.Run(() => ConnectionRestored?.Invoke(time.HasValue ? DateTime.UtcNow - time.Value : TimeSpan.FromSeconds(0))).ConfigureAwait(false);
|
||||
_ = Task.Run(() => ConnectionRestored?.Invoke(time.HasValue ? DateTime.UtcNow - time.Value : TimeSpan.FromSeconds(0)));
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -396,6 +414,7 @@ namespace CryptoExchange.Net.Sockets
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
_status = SocketStatus.Disposed;
|
||||
_socket.Dispose();
|
||||
}
|
||||
|
||||
@@ -458,7 +477,7 @@ namespace CryptoExchange.Net.Sockets
|
||||
|
||||
var total = DateTime.UtcNow - timestamp;
|
||||
if (userProcessTime.TotalMilliseconds > 500)
|
||||
log.Write(LogLevel.Debug, $"Socket {SocketId} message processing slow ({(int)total.TotalMilliseconds}ms), consider offloading data handling to another thread. " +
|
||||
log.Write(LogLevel.Debug, $"Socket {SocketId} message processing slow ({(int)total.TotalMilliseconds}ms, {(int)userProcessTime.TotalMilliseconds}ms user code), consider offloading data handling to another thread. " +
|
||||
"Data from this socket may arrive late or not at all if message processing is continuously slow.");
|
||||
|
||||
log.Write(LogLevel.Trace, $"Socket {SocketId} message processed in {(int)total.TotalMilliseconds}ms, ({(int)userProcessTime.TotalMilliseconds}ms user code)");
|
||||
@@ -671,5 +690,13 @@ namespace CryptoExchange.Net.Sockets
|
||||
|
||||
return await socketClient.SubscribeAndWaitAsync(this, socketSubscription.Request!, socketSubscription).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private enum SocketStatus
|
||||
{
|
||||
None,
|
||||
Processing,
|
||||
Reconnecting,
|
||||
Disposed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,17 @@ I develop and maintain this package on my own for free in my spare time. Donatio
|
||||
Alternatively, sponsor me on Github using [Github Sponsors](https://github.com/sponsors/JKorf)
|
||||
|
||||
## Release notes
|
||||
* Version 5.1.11 - 24 May 2022
|
||||
* Added KeepAliveInterval setting
|
||||
* Fixed port not being copied when setting parameters on request
|
||||
* Fixed inconsistent PackageReference casing in csproj
|
||||
|
||||
* Version 5.1.10 - 22 May 2022
|
||||
* Fixed order book reconnecting while Diposed
|
||||
* Fixed exception when disposing socket client while reconnecting
|
||||
* Added additional null/default checking in DateTimeConverter
|
||||
* Changed ConnectionLost subscription event to run in seperate task to prevent exception/longer operations from intervering with reconnecting
|
||||
|
||||
* Version 5.1.9 - 08 May 2022
|
||||
* Added latency to the timesync calculation
|
||||
* Small fix for exception in socket close handling
|
||||
|
||||
+4
-1
@@ -61,4 +61,7 @@ var client = new BinanceClient(new BinanceClientOptions
|
||||
BaseAddress = BinanceApiAddresses.TestNet.UsdFuturesRestClientAddress
|
||||
}
|
||||
});
|
||||
```
|
||||
```
|
||||
|
||||
### How are timezones handled / Timestamps are off by xx
|
||||
Exchange API's treat all timestamps as UTC, both incoming and outgoing. The client libraries do no conversion so be sure to use UTC as well.
|
||||
Reference in New Issue
Block a user