mirror of
https://github.com/JKorf/CryptoExchange.Net
synced 2025-06-07 16:06:15 +00:00
Updated socket reconnection
This commit is contained in:
parent
c2080ef75f
commit
c13dfa4461
@ -92,6 +92,18 @@ namespace CryptoExchange.Net
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int CurrentConnections => socketConnections.Count;
|
||||||
|
public int CurrentSubscriptions
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!socketConnections.Any())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return socketConnections.Sum(s => s.Value.SubscriptionCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Client options
|
/// Client options
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -164,7 +176,7 @@ namespace CryptoExchange.Net
|
|||||||
return new CallResult<UpdateSubscription>(new InvalidOperationError("Client disposed, can't subscribe"));
|
return new CallResult<UpdateSubscription>(new InvalidOperationError("Client disposed, can't subscribe"));
|
||||||
|
|
||||||
SocketConnection socketConnection;
|
SocketConnection socketConnection;
|
||||||
SocketSubscription subscription;
|
SocketSubscription? subscription;
|
||||||
var released = false;
|
var released = false;
|
||||||
// Wait for a semaphore here, so we only connect 1 socket at a time.
|
// Wait for a semaphore here, so we only connect 1 socket at a time.
|
||||||
// This is necessary for being able to see if connections can be combined
|
// This is necessary for being able to see if connections can be combined
|
||||||
@ -179,23 +191,34 @@ namespace CryptoExchange.Net
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Get a new or existing socket connection
|
while (true)
|
||||||
socketConnection = GetSocketConnection(apiClient, url, authenticated);
|
|
||||||
|
|
||||||
// Add a subscription on the socket connection
|
|
||||||
subscription = AddSubscription(request, identifier, true, socketConnection, dataHandler);
|
|
||||||
if (ClientOptions.SocketSubscriptionsCombineTarget == 1)
|
|
||||||
{
|
{
|
||||||
// Only 1 subscription per connection, so no need to wait for connection since a new subscription will create a new connection anyway
|
// Get a new or existing socket connection
|
||||||
semaphoreSlim.Release();
|
socketConnection = GetSocketConnection(apiClient, url, authenticated);
|
||||||
released = true;
|
|
||||||
|
// Add a subscription on the socket connection
|
||||||
|
subscription = AddSubscription(request, identifier, true, socketConnection, dataHandler);
|
||||||
|
if (subscription == null)
|
||||||
|
{
|
||||||
|
log.Write(LogLevel.Trace, $"Socket {socketConnection.SocketId} failed to add subscription, retrying on different connection");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ClientOptions.SocketSubscriptionsCombineTarget == 1)
|
||||||
|
{
|
||||||
|
// Only 1 subscription per connection, so no need to wait for connection since a new subscription will create a new connection anyway
|
||||||
|
semaphoreSlim.Release();
|
||||||
|
released = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var needsConnecting = !socketConnection.Connected;
|
||||||
|
|
||||||
|
var connectResult = await ConnectIfNeededAsync(socketConnection, authenticated).ConfigureAwait(false);
|
||||||
|
if (!connectResult)
|
||||||
|
return new CallResult<UpdateSubscription>(connectResult.Error!);
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
var needsConnecting = !socketConnection.Connected;
|
|
||||||
|
|
||||||
var connectResult = await ConnectIfNeededAsync(socketConnection, authenticated).ConfigureAwait(false);
|
|
||||||
if (!connectResult)
|
|
||||||
return new CallResult<UpdateSubscription>(connectResult.Error!);
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@ -443,9 +466,6 @@ namespace CryptoExchange.Net
|
|||||||
/// <param name="message"></param>
|
/// <param name="message"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected internal virtual JToken ProcessTokenData(JToken message)
|
protected internal virtual JToken ProcessTokenData(JToken message)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
@ -460,7 +480,7 @@ namespace CryptoExchange.Net
|
|||||||
/// <param name="connection">The socket connection the handler is on</param>
|
/// <param name="connection">The socket connection the handler is on</param>
|
||||||
/// <param name="dataHandler">The handler of the data received</param>
|
/// <param name="dataHandler">The handler of the data received</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected virtual SocketSubscription AddSubscription<T>(object? request, string? identifier, bool userSubscription, SocketConnection connection, Action<DataEvent<T>> dataHandler)
|
protected virtual SocketSubscription? AddSubscription<T>(object? request, string? identifier, bool userSubscription, SocketConnection connection, Action<DataEvent<T>> dataHandler)
|
||||||
{
|
{
|
||||||
void InternalHandler(MessageEvent messageEvent)
|
void InternalHandler(MessageEvent messageEvent)
|
||||||
{
|
{
|
||||||
@ -484,7 +504,8 @@ namespace CryptoExchange.Net
|
|||||||
var subscription = request == null
|
var subscription = request == null
|
||||||
? SocketSubscription.CreateForIdentifier(NextId(), identifier!, userSubscription, InternalHandler)
|
? SocketSubscription.CreateForIdentifier(NextId(), identifier!, userSubscription, InternalHandler)
|
||||||
: SocketSubscription.CreateForRequest(NextId(), request, userSubscription, InternalHandler);
|
: SocketSubscription.CreateForRequest(NextId(), request, userSubscription, InternalHandler);
|
||||||
connection.AddSubscription(subscription);
|
if (!connection.AddSubscription(subscription))
|
||||||
|
return null;
|
||||||
return subscription;
|
return subscription;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -510,7 +531,8 @@ namespace CryptoExchange.Net
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected virtual SocketConnection GetSocketConnection(SocketApiClient apiClient, string address, bool authenticated)
|
protected virtual SocketConnection GetSocketConnection(SocketApiClient apiClient, string address, bool authenticated)
|
||||||
{
|
{
|
||||||
var socketResult = socketConnections.Where(s => s.Value.Uri.ToString().TrimEnd('/') == address.TrimEnd('/')
|
var socketResult = socketConnections.Where(s => (s.Value.Status == SocketConnection.SocketStatus.None || s.Value.Status == SocketConnection.SocketStatus.Connected)
|
||||||
|
&& s.Value.Uri.ToString().TrimEnd('/') == address.TrimEnd('/')
|
||||||
&& (s.Value.ApiClient.GetType() == apiClient.GetType())
|
&& (s.Value.ApiClient.GetType() == apiClient.GetType())
|
||||||
&& (s.Value.Authenticated == authenticated || !authenticated) && s.Value.Connected).OrderBy(s => s.Value.SubscriptionCount).FirstOrDefault();
|
&& (s.Value.Authenticated == authenticated || !authenticated) && s.Value.Connected).OrderBy(s => s.Value.SubscriptionCount).FirstOrDefault();
|
||||||
var result = socketResult.Equals(default(KeyValuePair<int, SocketConnection>)) ? null : socketResult.Value;
|
var result = socketResult.Equals(default(KeyValuePair<int, SocketConnection>)) ? null : socketResult.Value;
|
||||||
|
@ -34,6 +34,8 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
|
|
||||||
private readonly List<DateTime> _outgoingMessages;
|
private readonly List<DateTime> _outgoingMessages;
|
||||||
private DateTime _lastReceivedMessagesUpdate;
|
private DateTime _lastReceivedMessagesUpdate;
|
||||||
|
private bool _closed;
|
||||||
|
private bool _disposed;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Received messages, the size and the timstamp
|
/// Received messages, the size and the timstamp
|
||||||
@ -279,6 +281,10 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private async Task CloseInternalAsync()
|
private async Task CloseInternalAsync()
|
||||||
{
|
{
|
||||||
|
if (_closed || _disposed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_closed = true;
|
||||||
_ctsSource.Cancel();
|
_ctsSource.Cancel();
|
||||||
_sendEvent.Set();
|
_sendEvent.Set();
|
||||||
|
|
||||||
@ -291,6 +297,15 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
catch(Exception)
|
catch(Exception)
|
||||||
{ } // Can sometimes throw an exception when socket is in aborted state due to timing
|
{ } // Can sometimes throw an exception when socket is in aborted state due to timing
|
||||||
}
|
}
|
||||||
|
else if(_socket.State == WebSocketState.CloseReceived)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", default).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{ } // Can sometimes throw an exception when socket is in aborted state due to timing
|
||||||
|
}
|
||||||
log.Write(LogLevel.Debug, $"Socket {Id} closed");
|
log.Write(LogLevel.Debug, $"Socket {Id} closed");
|
||||||
Handle(closeHandlers);
|
Handle(closeHandlers);
|
||||||
}
|
}
|
||||||
@ -300,7 +315,11 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
if (_disposed)
|
||||||
|
return;
|
||||||
|
|
||||||
log.Write(LogLevel.Debug, $"Socket {Id} disposing");
|
log.Write(LogLevel.Debug, $"Socket {Id} disposing");
|
||||||
|
_disposed = true;
|
||||||
_socket.Dispose();
|
_socket.Dispose();
|
||||||
_ctsSource.Dispose();
|
_ctsSource.Dispose();
|
||||||
|
|
||||||
@ -320,6 +339,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
while (_sendBuffer.TryDequeue(out _)) { } // Clear send buffer
|
while (_sendBuffer.TryDequeue(out _)) { } // Clear send buffer
|
||||||
|
|
||||||
_socket = CreateSocket();
|
_socket = CreateSocket();
|
||||||
|
_closed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -139,10 +139,26 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
private readonly BaseSocketClient socketClient;
|
private readonly BaseSocketClient socketClient;
|
||||||
|
|
||||||
private readonly List<PendingRequest> pendingRequests;
|
private readonly List<PendingRequest> pendingRequests;
|
||||||
private Task? _socketProcessReconnectTask;
|
private Task? _socketProcessTask;
|
||||||
|
private Task? _socketReconnectTask;
|
||||||
|
private readonly AsyncResetEvent _reconnectWaitEvent;
|
||||||
|
|
||||||
private SocketStatus _status;
|
private SocketStatus _status;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Status of the socket connection
|
||||||
|
/// </summary>
|
||||||
|
public SocketStatus Status
|
||||||
|
{
|
||||||
|
get => _status;
|
||||||
|
private set
|
||||||
|
{
|
||||||
|
var oldStatus = _status;
|
||||||
|
_status = value;
|
||||||
|
log.Write(LogLevel.Trace, $"Socket {SocketId} status changed from {oldStatus} to {_status}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The underlying websocket
|
/// The underlying websocket
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -165,9 +181,13 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
subscriptions = new List<SocketSubscription>();
|
subscriptions = new List<SocketSubscription>();
|
||||||
_socket = socket;
|
_socket = socket;
|
||||||
|
|
||||||
|
_reconnectWaitEvent = new AsyncResetEvent(false, true);
|
||||||
|
|
||||||
_socket.Timeout = client.ClientOptions.SocketNoDataTimeout;
|
_socket.Timeout = client.ClientOptions.SocketNoDataTimeout;
|
||||||
_socket.OnMessage += ProcessMessage;
|
_socket.OnMessage += ProcessMessage;
|
||||||
_socket.OnOpen += SocketOnOpen;
|
_socket.OnOpen += SocketOnOpen;
|
||||||
|
_socket.OnClose += () => _reconnectWaitEvent.Set();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -178,7 +198,11 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
{
|
{
|
||||||
var connected = await _socket.ConnectAsync().ConfigureAwait(false);
|
var connected = await _socket.ConnectAsync().ConfigureAwait(false);
|
||||||
if (connected)
|
if (connected)
|
||||||
StartProcessingTask();
|
{
|
||||||
|
Status = SocketStatus.Connected;
|
||||||
|
_socketReconnectTask = ReconnectWatcherAsync();
|
||||||
|
_socketProcessTask = _socket.ProcessAsync();
|
||||||
|
}
|
||||||
|
|
||||||
return connected;
|
return connected;
|
||||||
}
|
}
|
||||||
@ -207,6 +231,9 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task CloseAsync()
|
public async Task CloseAsync()
|
||||||
{
|
{
|
||||||
|
if (Status == SocketStatus.Closed || Status == SocketStatus.Disposed)
|
||||||
|
return;
|
||||||
|
|
||||||
ShouldReconnect = false;
|
ShouldReconnect = false;
|
||||||
if (socketClient.socketConnections.ContainsKey(SocketId))
|
if (socketClient.socketConnections.ContainsKey(SocketId))
|
||||||
socketClient.socketConnections.TryRemove(SocketId, out _);
|
socketClient.socketConnections.TryRemove(SocketId, out _);
|
||||||
@ -220,24 +247,13 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_status == SocketStatus.Reconnecting)
|
while (Status == SocketStatus.Reconnecting)
|
||||||
{
|
// Wait for reconnecting to finish
|
||||||
// Wait for reconnect task to finish
|
await Task.Delay(100).ConfigureAwait(false);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
await _socket.CloseAsync().ConfigureAwait(false);
|
||||||
|
if(_socketProcessTask != null)
|
||||||
|
await _socketProcessTask.ConfigureAwait(false);
|
||||||
_socket.Dispose();
|
_socket.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,39 +264,40 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task CloseAsync(SocketSubscription subscription)
|
public async Task CloseAsync(SocketSubscription subscription)
|
||||||
{
|
{
|
||||||
if (!_socket.IsOpen || _status == SocketStatus.Disposed)
|
if (Status == SocketStatus.Closing || Status == SocketStatus.Closed || Status == SocketStatus.Disposed)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
log.Write(LogLevel.Trace, $"Socket {SocketId} closing subscription {subscription.Id}");
|
||||||
if (subscription.CancellationTokenRegistration.HasValue)
|
if (subscription.CancellationTokenRegistration.HasValue)
|
||||||
subscription.CancellationTokenRegistration.Value.Dispose();
|
subscription.CancellationTokenRegistration.Value.Dispose();
|
||||||
|
|
||||||
if (subscription.Confirmed)
|
if (subscription.Confirmed && _socket.IsOpen)
|
||||||
await socketClient.UnsubscribeAsync(this, subscription).ConfigureAwait(false);
|
await socketClient.UnsubscribeAsync(this, subscription).ConfigureAwait(false);
|
||||||
|
|
||||||
bool shouldCloseConnection;
|
bool shouldCloseConnection;
|
||||||
lock (subscriptionLock)
|
lock (subscriptionLock)
|
||||||
shouldCloseConnection = !subscriptions.Any(r => r.UserSubscription && subscription != r);
|
{
|
||||||
|
if (Status == SocketStatus.Closing)
|
||||||
|
{
|
||||||
|
log.Write(LogLevel.Trace, $"Socket {SocketId} already closing");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldCloseConnection = subscriptions.All(r => !r.UserSubscription || r == subscription);
|
||||||
|
if (shouldCloseConnection)
|
||||||
|
Status = SocketStatus.Closing;
|
||||||
|
}
|
||||||
|
|
||||||
if (shouldCloseConnection)
|
if (shouldCloseConnection)
|
||||||
|
{
|
||||||
|
log.Write(LogLevel.Trace, $"Socket {SocketId} closing as there are no more subscriptions");
|
||||||
await CloseAsync().ConfigureAwait(false);
|
await CloseAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
lock (subscriptionLock)
|
lock (subscriptionLock)
|
||||||
subscriptions.Remove(subscription);
|
subscriptions.Remove(subscription);
|
||||||
}
|
}
|
||||||
|
|
||||||
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");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ReconnectAsync()
|
private async Task ReconnectAsync()
|
||||||
{
|
{
|
||||||
// Fail all pending requests
|
// Fail all pending requests
|
||||||
@ -344,7 +361,8 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Successfully reconnected, start processing
|
// Successfully reconnected, start processing
|
||||||
StartProcessingTask();
|
Status = SocketStatus.Connected;
|
||||||
|
_socketProcessTask = _socket.ProcessAsync();
|
||||||
|
|
||||||
ReconnectTry = 0;
|
ReconnectTry = 0;
|
||||||
var time = DisconnectTime;
|
var time = DisconnectTime;
|
||||||
@ -414,7 +432,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_status = SocketStatus.Disposed;
|
Status = SocketStatus.Disposed;
|
||||||
_socket.Dispose();
|
_socket.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -487,10 +505,17 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
/// Add a subscription to this connection
|
/// Add a subscription to this connection
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="subscription"></param>
|
/// <param name="subscription"></param>
|
||||||
public void AddSubscription(SocketSubscription subscription)
|
public bool AddSubscription(SocketSubscription subscription)
|
||||||
{
|
{
|
||||||
lock(subscriptionLock)
|
lock (subscriptionLock)
|
||||||
|
{
|
||||||
|
if (Status != SocketStatus.None && Status != SocketStatus.Connected)
|
||||||
|
return false;
|
||||||
|
|
||||||
subscriptions.Add(subscription);
|
subscriptions.Add(subscription);
|
||||||
|
log.Write(LogLevel.Trace, $"Socket {SocketId} adding new subscription with id {subscription.Id}, total subscriptions on connection: {subscriptions.Count}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -633,6 +658,22 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
PausedActivity = false;
|
PausedActivity = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task ReconnectWatcherAsync()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
await _reconnectWaitEvent.WaitAsync().ConfigureAwait(false);
|
||||||
|
if (!ShouldReconnect)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Status = SocketStatus.Reconnecting;
|
||||||
|
await ReconnectAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (!ShouldReconnect)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<CallResult<bool>> ProcessReconnectAsync()
|
private async Task<CallResult<bool>> ProcessReconnectAsync()
|
||||||
{
|
{
|
||||||
if (!_socket.IsOpen)
|
if (!_socket.IsOpen)
|
||||||
@ -691,11 +732,34 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
return await socketClient.SubscribeAndWaitAsync(this, socketSubscription.Request!, socketSubscription).ConfigureAwait(false);
|
return await socketClient.SubscribeAndWaitAsync(this, socketSubscription.Request!, socketSubscription).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum SocketStatus
|
/// <summary>
|
||||||
|
/// Status of the socket connection
|
||||||
|
/// </summary>
|
||||||
|
public enum SocketStatus
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// None/Initial
|
||||||
|
/// </summary>
|
||||||
None,
|
None,
|
||||||
Processing,
|
/// <summary>
|
||||||
|
/// Connected
|
||||||
|
/// </summary>
|
||||||
|
Connected,
|
||||||
|
/// <summary>
|
||||||
|
/// Reconnecting
|
||||||
|
/// </summary>
|
||||||
Reconnecting,
|
Reconnecting,
|
||||||
|
/// <summary>
|
||||||
|
/// Closing
|
||||||
|
/// </summary>
|
||||||
|
Closing,
|
||||||
|
/// <summary>
|
||||||
|
/// Closed
|
||||||
|
/// </summary>
|
||||||
|
Closed,
|
||||||
|
/// <summary>
|
||||||
|
/// Disposed
|
||||||
|
/// </summary>
|
||||||
Disposed
|
Disposed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user