mirror of
				https://github.com/JKorf/CryptoExchange.Net
				synced 2025-11-03 20:07:33 +00:00 
			
		
		
		
	Finished up websocket refactoring
This commit is contained in:
		
							parent
							
								
									89b517c936
								
							
						
					
					
						commit
						70f8bd203a
					
				@ -3,6 +3,7 @@ using System.Collections.Concurrent;
 | 
				
			|||||||
using System.Collections.Generic;
 | 
					using System.Collections.Generic;
 | 
				
			||||||
using System.Diagnostics.CodeAnalysis;
 | 
					using System.Diagnostics.CodeAnalysis;
 | 
				
			||||||
using System.Linq;
 | 
					using System.Linq;
 | 
				
			||||||
 | 
					using System.Text;
 | 
				
			||||||
using System.Threading;
 | 
					using System.Threading;
 | 
				
			||||||
using System.Threading.Tasks;
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
using CryptoExchange.Net.Authentication;
 | 
					using CryptoExchange.Net.Authentication;
 | 
				
			||||||
@ -191,10 +192,14 @@ namespace CryptoExchange.Net
 | 
				
			|||||||
                while (true)
 | 
					                while (true)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    // Get a new or existing socket connection
 | 
					                    // Get a new or existing socket connection
 | 
				
			||||||
                    socketConnection = GetSocketConnection(apiClient, url, authenticated);
 | 
					                    var socketResult = await GetSocketConnection(apiClient, url, authenticated).ConfigureAwait(false);
 | 
				
			||||||
 | 
					                    if(!socketResult)
 | 
				
			||||||
 | 
					                        return socketResult.As<UpdateSubscription>(null);                    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    socketConnection = socketResult.Data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    // Add a subscription on the socket connection
 | 
					                    // Add a subscription on the socket connection
 | 
				
			||||||
                    subscription = AddSubscription(request, identifier, true, socketConnection, dataHandler);
 | 
					                    subscription = AddSubscription(request, identifier, true, socketConnection, dataHandler, authenticated);
 | 
				
			||||||
                    if (subscription == null)
 | 
					                    if (subscription == null)
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
                        log.Write(LogLevel.Trace, $"Socket {socketConnection.SocketId} failed to add subscription, retrying on different connection");
 | 
					                        log.Write(LogLevel.Trace, $"Socket {socketConnection.SocketId} failed to add subscription, retrying on different connection");
 | 
				
			||||||
@ -235,7 +240,7 @@ namespace CryptoExchange.Net
 | 
				
			|||||||
                var subResult = await SubscribeAndWaitAsync(socketConnection, request, subscription).ConfigureAwait(false);
 | 
					                var subResult = await SubscribeAndWaitAsync(socketConnection, request, subscription).ConfigureAwait(false);
 | 
				
			||||||
                if (!subResult)
 | 
					                if (!subResult)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    log.Write(LogLevel.Information, $"Socket {socketConnection.SocketId} failed to subscribe: {subResult.Error}");
 | 
					                    log.Write(LogLevel.Warning, $"Socket {socketConnection.SocketId} failed to subscribe: {subResult.Error}");
 | 
				
			||||||
                    await socketConnection.CloseAsync(subscription).ConfigureAwait(false);
 | 
					                    await socketConnection.CloseAsync(subscription).ConfigureAwait(false);
 | 
				
			||||||
                    return new CallResult<UpdateSubscription>(subResult.Error!);
 | 
					                    return new CallResult<UpdateSubscription>(subResult.Error!);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@ -315,7 +320,12 @@ namespace CryptoExchange.Net
 | 
				
			|||||||
            await semaphoreSlim.WaitAsync().ConfigureAwait(false);
 | 
					            await semaphoreSlim.WaitAsync().ConfigureAwait(false);
 | 
				
			||||||
            try
 | 
					            try
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                socketConnection = GetSocketConnection(apiClient, url, authenticated);
 | 
					                var socketResult = await GetSocketConnection(apiClient, url, authenticated).ConfigureAwait(false);
 | 
				
			||||||
 | 
					                if (!socketResult)
 | 
				
			||||||
 | 
					                    return socketResult.As<T>(default);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                socketConnection = socketResult.Data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (ClientOptions.SocketSubscriptionsCombineTarget == 1)
 | 
					                if (ClientOptions.SocketSubscriptionsCombineTarget == 1)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    // Can release early when only a single sub per connection
 | 
					                    // Can release early when only a single sub per connection
 | 
				
			||||||
@ -474,8 +484,9 @@ namespace CryptoExchange.Net
 | 
				
			|||||||
        /// <param name="userSubscription">Whether or not this is a user subscription (counts towards the max amount of handlers on a socket)</param>
 | 
					        /// <param name="userSubscription">Whether or not this is a user subscription (counts towards the max amount of handlers on a socket)</param>
 | 
				
			||||||
        /// <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>
 | 
				
			||||||
 | 
					        /// <param name="authenticated">Whether the subscription needs authentication</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, bool authenticated)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            void InternalHandler(MessageEvent messageEvent)
 | 
					            void InternalHandler(MessageEvent messageEvent)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@ -497,8 +508,8 @@ namespace CryptoExchange.Net
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var subscription = request == null
 | 
					            var subscription = request == null
 | 
				
			||||||
                ? SocketSubscription.CreateForIdentifier(NextId(), identifier!, userSubscription, InternalHandler)
 | 
					                ? SocketSubscription.CreateForIdentifier(NextId(), identifier!, userSubscription, authenticated, InternalHandler)
 | 
				
			||||||
                : SocketSubscription.CreateForRequest(NextId(), request, userSubscription, InternalHandler);
 | 
					                : SocketSubscription.CreateForRequest(NextId(), request, userSubscription, authenticated, InternalHandler);
 | 
				
			||||||
            if (!connection.AddSubscription(subscription))
 | 
					            if (!connection.AddSubscription(subscription))
 | 
				
			||||||
                return null;
 | 
					                return null;
 | 
				
			||||||
            return subscription;
 | 
					            return subscription;
 | 
				
			||||||
@ -512,11 +523,23 @@ namespace CryptoExchange.Net
 | 
				
			|||||||
        protected void AddGenericHandler(string identifier, Action<MessageEvent> action)
 | 
					        protected void AddGenericHandler(string identifier, Action<MessageEvent> action)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            genericHandlers.Add(identifier, action);
 | 
					            genericHandlers.Add(identifier, action);
 | 
				
			||||||
            var subscription = SocketSubscription.CreateForIdentifier(NextId(), identifier, false, action);
 | 
					            var subscription = SocketSubscription.CreateForIdentifier(NextId(), identifier, false, false, action);
 | 
				
			||||||
            foreach (var connection in socketConnections.Values)
 | 
					            foreach (var connection in socketConnections.Values)
 | 
				
			||||||
                connection.AddSubscription(subscription);
 | 
					                connection.AddSubscription(subscription);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Get the url to connect to (defaults to BaseAddress form the client options)
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="apiClient"></param>
 | 
				
			||||||
 | 
					        /// <param name="address"></param>
 | 
				
			||||||
 | 
					        /// <param name="authentication"></param>
 | 
				
			||||||
 | 
					        /// <returns></returns>
 | 
				
			||||||
 | 
					        protected virtual Task<CallResult<string?>> GetConnectionUrlAsync(SocketApiClient apiClient, string address, bool authentication)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return Task.FromResult(new CallResult<string?>(address));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Gets a connection for a new subscription or query. Can be an existing if there are open position or a new one.
 | 
					        /// Gets a connection for a new subscription or query. Can be an existing if there are open position or a new one.
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
@ -524,10 +547,10 @@ namespace CryptoExchange.Net
 | 
				
			|||||||
        /// <param name="address">The address the socket is for</param>
 | 
					        /// <param name="address">The address the socket is for</param>
 | 
				
			||||||
        /// <param name="authenticated">Whether the socket should be authenticated</param>
 | 
					        /// <param name="authenticated">Whether the socket should be authenticated</param>
 | 
				
			||||||
        /// <returns></returns>
 | 
					        /// <returns></returns>
 | 
				
			||||||
        protected virtual SocketConnection GetSocketConnection(SocketApiClient apiClient, string address, bool authenticated)
 | 
					        protected virtual async Task<CallResult<SocketConnection>> GetSocketConnection(SocketApiClient apiClient, string address, bool authenticated)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var socketResult = socketConnections.Where(s => (s.Value.Status == SocketConnection.SocketStatus.None || s.Value.Status == SocketConnection.SocketStatus.Connected)
 | 
					            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.Tag.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;
 | 
				
			||||||
@ -536,21 +559,31 @@ namespace CryptoExchange.Net
 | 
				
			|||||||
                if (result.SubscriptionCount < ClientOptions.SocketSubscriptionsCombineTarget || (socketConnections.Count >= ClientOptions.MaxSocketConnections && socketConnections.All(s => s.Value.SubscriptionCount >= ClientOptions.SocketSubscriptionsCombineTarget)))
 | 
					                if (result.SubscriptionCount < ClientOptions.SocketSubscriptionsCombineTarget || (socketConnections.Count >= ClientOptions.MaxSocketConnections && socketConnections.All(s => s.Value.SubscriptionCount >= ClientOptions.SocketSubscriptionsCombineTarget)))
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    // Use existing socket if it has less than target connections OR it has the least connections and we can't make new
 | 
					                    // Use existing socket if it has less than target connections OR it has the least connections and we can't make new
 | 
				
			||||||
                    return result;
 | 
					                    return new CallResult<SocketConnection>(result);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var connectionAddress = await GetConnectionUrlAsync(apiClient, address, authenticated).ConfigureAwait(false);
 | 
				
			||||||
 | 
					            if (!connectionAddress)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                log.Write(LogLevel.Warning, $"Failed to determine connection url: " + connectionAddress.Error);
 | 
				
			||||||
 | 
					                return connectionAddress.As<SocketConnection>(null);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (connectionAddress.Data != address)
 | 
				
			||||||
 | 
					                log.Write(LogLevel.Debug, $"Connection address set to " + connectionAddress.Data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Create new socket
 | 
					            // Create new socket
 | 
				
			||||||
            var socket = CreateSocket(address);
 | 
					            var socket = CreateSocket(connectionAddress.Data!);
 | 
				
			||||||
            var socketConnection = new SocketConnection(this, apiClient, socket);
 | 
					            var socketConnection = new SocketConnection(this, apiClient, socket, address);
 | 
				
			||||||
            socketConnection.UnhandledMessage += HandleUnhandledMessage;
 | 
					            socketConnection.UnhandledMessage += HandleUnhandledMessage;
 | 
				
			||||||
            foreach (var kvp in genericHandlers)
 | 
					            foreach (var kvp in genericHandlers)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                var handler = SocketSubscription.CreateForIdentifier(NextId(), kvp.Key, false, kvp.Value);
 | 
					                var handler = SocketSubscription.CreateForIdentifier(NextId(), kvp.Key, false, false, kvp.Value);
 | 
				
			||||||
                socketConnection.AddSubscription(handler);
 | 
					                socketConnection.AddSubscription(handler);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return socketConnection;
 | 
					            return new CallResult<SocketConnection>(socketConnection);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
@ -700,7 +733,7 @@ namespace CryptoExchange.Net
 | 
				
			|||||||
        /// <returns></returns>
 | 
					        /// <returns></returns>
 | 
				
			||||||
        public virtual async Task UnsubscribeAllAsync()
 | 
					        public virtual async Task UnsubscribeAllAsync()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            log.Write(LogLevel.Information, $"Closing all {socketConnections.Sum(s => s.Value.SubscriptionCount)} subscriptions");
 | 
					            log.Write(LogLevel.Information, $"Unsubscribing all {socketConnections.Sum(s => s.Value.SubscriptionCount)} subscriptions");
 | 
				
			||||||
            var tasks = new List<Task>();
 | 
					            var tasks = new List<Task>();
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                var socketList = socketConnections.Values;
 | 
					                var socketList = socketConnections.Values;
 | 
				
			||||||
@ -711,6 +744,39 @@ namespace CryptoExchange.Net
 | 
				
			|||||||
            await Task.WhenAll(tasks.ToArray()).ConfigureAwait(false);
 | 
					            await Task.WhenAll(tasks.ToArray()).ConfigureAwait(false);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Reconnect all connections
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <returns></returns>
 | 
				
			||||||
 | 
					        public virtual async Task ReconnectAsync()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            log.Write(LogLevel.Information, $"Reconnecting all {socketConnections.Count} connections");
 | 
				
			||||||
 | 
					            var tasks = new List<Task>();
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var socketList = socketConnections.Values;
 | 
				
			||||||
 | 
					                foreach (var sub in socketList)
 | 
				
			||||||
 | 
					                    tasks.Add(sub.TriggerReconnectAsync());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await Task.WhenAll(tasks.ToArray()).ConfigureAwait(false);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Log the current state of connections and subscriptions
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        public string GetSubscriptionsState()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var sb = new StringBuilder();
 | 
				
			||||||
 | 
					            sb.AppendLine($"{socketConnections.Count} connections, {CurrentSubscriptions} subscriptions, kbps: {IncomingKbps}");
 | 
				
			||||||
 | 
					            foreach(var connection in socketConnections)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                sb.AppendLine($"  Connection {connection.Key}: {connection.Value.SubscriptionCount} subscriptions, status: {connection.Value.Status}, authenticated: {connection.Value.Authenticated}, kbps: {connection.Value.IncomingKbps}");
 | 
				
			||||||
 | 
					                foreach (var subscription in connection.Value.Subscriptions)
 | 
				
			||||||
 | 
					                    sb.AppendLine($"    Subscription {subscription.Id}, authenticated: {subscription.Authenticated}, confirmed: {subscription.Confirmed}");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return sb.ToString();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Dispose the client
 | 
					        /// Dispose the client
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
@ -719,8 +785,11 @@ namespace CryptoExchange.Net
 | 
				
			|||||||
            disposing = true;
 | 
					            disposing = true;
 | 
				
			||||||
            periodicEvent?.Set();
 | 
					            periodicEvent?.Set();
 | 
				
			||||||
            periodicEvent?.Dispose();
 | 
					            periodicEvent?.Dispose();
 | 
				
			||||||
 | 
					            if (socketConnections.Sum(s => s.Value.SubscriptionCount) > 0)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
                log.Write(LogLevel.Debug, "Disposing socket client, closing all subscriptions");
 | 
					                log.Write(LogLevel.Debug, "Disposing socket client, closing all subscriptions");
 | 
				
			||||||
                _ = UnsubscribeAllAsync();
 | 
					                _ = UnsubscribeAllAsync();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            semaphoreSlim?.Dispose();
 | 
					            semaphoreSlim?.Dispose();
 | 
				
			||||||
            base.Dispose();
 | 
					            base.Dispose();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -27,13 +27,6 @@ namespace CryptoExchange.Net.Sockets
 | 
				
			|||||||
            Reconnecting
 | 
					            Reconnecting
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        enum CloseState
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            Idle,
 | 
					 | 
				
			||||||
            Closing,
 | 
					 | 
				
			||||||
            Closed
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        internal static int lastStreamId;
 | 
					        internal static int lastStreamId;
 | 
				
			||||||
        private static readonly object streamIdLock = new();
 | 
					        private static readonly object streamIdLock = new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -191,13 +184,13 @@ namespace CryptoExchange.Net.Sockets
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            while (!_stopRequested)
 | 
					            while (!_stopRequested)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                _log.Write(LogLevel.Trace, $"Socket {Id} ProcessAsync started");
 | 
					                _log.Write(LogLevel.Debug, $"Socket {Id} starting processing tasks");
 | 
				
			||||||
                _processState = ProcessState.Processing;
 | 
					                _processState = ProcessState.Processing;
 | 
				
			||||||
                var sendTask = SendLoopAsync();
 | 
					                var sendTask = SendLoopAsync();
 | 
				
			||||||
                var receiveTask = ReceiveLoopAsync();
 | 
					                var receiveTask = ReceiveLoopAsync();
 | 
				
			||||||
                var timeoutTask = _parameters.Timeout != null && _parameters.Timeout > TimeSpan.FromSeconds(0) ? CheckTimeoutAsync() : Task.CompletedTask;
 | 
					                var timeoutTask = _parameters.Timeout != null && _parameters.Timeout > TimeSpan.FromSeconds(0) ? CheckTimeoutAsync() : Task.CompletedTask;
 | 
				
			||||||
                await Task.WhenAll(sendTask, receiveTask, timeoutTask).ConfigureAwait(false);
 | 
					                await Task.WhenAll(sendTask, receiveTask, timeoutTask).ConfigureAwait(false);
 | 
				
			||||||
                _log.Write(LogLevel.Trace, $"Socket {Id} ProcessAsync finished");
 | 
					                _log.Write(LogLevel.Debug, $"Socket {Id} processing tasks finished");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                _processState = ProcessState.WaitingForClose;
 | 
					                _processState = ProcessState.WaitingForClose;
 | 
				
			||||||
                while (_closeTask == null)
 | 
					                while (_closeTask == null)
 | 
				
			||||||
@ -221,7 +214,7 @@ namespace CryptoExchange.Net.Sockets
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                while (!_stopRequested)
 | 
					                while (!_stopRequested)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    _log.Write(LogLevel.Trace, $"Socket {Id} attempting to reconnect");
 | 
					                    _log.Write(LogLevel.Debug, $"Socket {Id} attempting to reconnect");
 | 
				
			||||||
                    _socket = CreateSocket();
 | 
					                    _socket = CreateSocket();
 | 
				
			||||||
                    _ctsSource.Dispose();
 | 
					                    _ctsSource.Dispose();
 | 
				
			||||||
                    _ctsSource = new CancellationTokenSource();
 | 
					                    _ctsSource = new CancellationTokenSource();
 | 
				
			||||||
@ -260,7 +253,7 @@ namespace CryptoExchange.Net.Sockets
 | 
				
			|||||||
            if (_processState != ProcessState.Processing)
 | 
					            if (_processState != ProcessState.Processing)
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            _log.Write(LogLevel.Debug, $"Socket {Id} reconnecting");
 | 
					            _log.Write(LogLevel.Debug, $"Socket {Id} reconnect requested");
 | 
				
			||||||
            _closeTask = CloseInternalAsync();
 | 
					            _closeTask = CloseInternalAsync();
 | 
				
			||||||
            await _closeTask.ConfigureAwait(false);
 | 
					            await _closeTask.ConfigureAwait(false);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -318,7 +311,6 @@ namespace CryptoExchange.Net.Sockets
 | 
				
			|||||||
            {
 | 
					            {
 | 
				
			||||||
                try
 | 
					                try
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    _log.Write(LogLevel.Trace, $"Socket {Id} normal closure 1");
 | 
					 | 
				
			||||||
                    await _socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "Closing", default).ConfigureAwait(false);
 | 
					                    await _socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "Closing", default).ConfigureAwait(false);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                catch (Exception)
 | 
					                catch (Exception)
 | 
				
			||||||
@ -332,7 +324,6 @@ namespace CryptoExchange.Net.Sockets
 | 
				
			|||||||
            {
 | 
					            {
 | 
				
			||||||
                try
 | 
					                try
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    _log.Write(LogLevel.Trace, $"Socket {Id} normal closure 2");
 | 
					 | 
				
			||||||
                    await _socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", default).ConfigureAwait(false);
 | 
					                    await _socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", default).ConfigureAwait(false);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                catch (Exception)
 | 
					                catch (Exception)
 | 
				
			||||||
@ -390,7 +381,7 @@ namespace CryptoExchange.Net.Sockets
 | 
				
			|||||||
                            }
 | 
					                            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            if (start != null)
 | 
					                            if (start != null)
 | 
				
			||||||
                                _log.Write(LogLevel.Trace, $"Socket {Id} sent delayed {Math.Round((DateTime.UtcNow - start.Value).TotalMilliseconds)}ms because of rate limit");
 | 
					                                _log.Write(LogLevel.Debug, $"Socket {Id} sent delayed {Math.Round((DateTime.UtcNow - start.Value).TotalMilliseconds)}ms because of rate limit");
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        try
 | 
					                        try
 | 
				
			||||||
@ -424,7 +415,7 @@ namespace CryptoExchange.Net.Sockets
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
            finally
 | 
					            finally
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                _log.Write(LogLevel.Trace, $"Socket {Id} Send loop finished");
 | 
					                _log.Write(LogLevel.Debug, $"Socket {Id} Send loop finished");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -542,7 +533,7 @@ namespace CryptoExchange.Net.Sockets
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
            finally
 | 
					            finally
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                _log.Write(LogLevel.Trace, $"Socket {Id} Receive loop finished");
 | 
					                _log.Write(LogLevel.Debug, $"Socket {Id} Receive loop finished");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -57,10 +57,22 @@ namespace CryptoExchange.Net.Sockets
 | 
				
			|||||||
                return subscriptions.Count(h => h.UserSubscription); }
 | 
					                return subscriptions.Count(h => h.UserSubscription); }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Get a copy of the current subscriptions
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        public SocketSubscription[] Subscriptions
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            get
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                lock (subscriptionLock)
 | 
				
			||||||
 | 
					                    return subscriptions.Where(h => h.UserSubscription).ToArray();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// If the connection has been authenticated
 | 
					        /// If the connection has been authenticated
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        public bool Authenticated { get; set; }
 | 
					        public bool Authenticated { get; internal set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// If connection is made
 | 
					        /// If connection is made
 | 
				
			||||||
@ -80,7 +92,7 @@ namespace CryptoExchange.Net.Sockets
 | 
				
			|||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// The connection uri
 | 
					        /// The connection uri
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        public Uri Uri => _socket.Uri;
 | 
					        public Uri ConnectionUri => _socket.Uri;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// The API client the connection is for
 | 
					        /// The API client the connection is for
 | 
				
			||||||
@ -95,7 +107,7 @@ namespace CryptoExchange.Net.Sockets
 | 
				
			|||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Tag for identificaion
 | 
					        /// Tag for identificaion
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        public string? Tag { get; set; }
 | 
					        public string Tag { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// If activity is paused
 | 
					        /// If activity is paused
 | 
				
			||||||
@ -128,7 +140,7 @@ namespace CryptoExchange.Net.Sockets
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                var oldStatus = _status;
 | 
					                var oldStatus = _status;
 | 
				
			||||||
                _status = value;
 | 
					                _status = value;
 | 
				
			||||||
                log.Write(LogLevel.Trace, $"Socket {SocketId} status changed from {oldStatus} to {_status}");
 | 
					                log.Write(LogLevel.Debug, $"Socket {SocketId} status changed from {oldStatus} to {_status}");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -154,11 +166,12 @@ namespace CryptoExchange.Net.Sockets
 | 
				
			|||||||
        /// <param name="client">The socket client</param>
 | 
					        /// <param name="client">The socket client</param>
 | 
				
			||||||
        /// <param name="apiClient">The api client</param>
 | 
					        /// <param name="apiClient">The api client</param>
 | 
				
			||||||
        /// <param name="socket">The socket</param>
 | 
					        /// <param name="socket">The socket</param>
 | 
				
			||||||
        public SocketConnection(BaseSocketClient client, SocketApiClient apiClient, IWebsocket socket)
 | 
					        public SocketConnection(BaseSocketClient client, SocketApiClient apiClient, IWebsocket socket, string tag)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            log = client.log;
 | 
					            log = client.log;
 | 
				
			||||||
            socketClient = client;
 | 
					            socketClient = client;
 | 
				
			||||||
            ApiClient = apiClient;
 | 
					            ApiClient = apiClient;
 | 
				
			||||||
 | 
					            Tag = tag;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            pendingRequests = new List<PendingRequest>();
 | 
					            pendingRequests = new List<PendingRequest>();
 | 
				
			||||||
            subscriptions = new List<SocketSubscription>();
 | 
					            subscriptions = new List<SocketSubscription>();
 | 
				
			||||||
@ -187,6 +200,12 @@ namespace CryptoExchange.Net.Sockets
 | 
				
			|||||||
        protected virtual void HandleClose()
 | 
					        protected virtual void HandleClose()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            Status = SocketStatus.Closed;
 | 
					            Status = SocketStatus.Closed;
 | 
				
			||||||
 | 
					            Authenticated = false;
 | 
				
			||||||
 | 
					            lock(subscriptionLock)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                foreach (var sub in subscriptions)
 | 
				
			||||||
 | 
					                    sub.Confirmed = false;
 | 
				
			||||||
 | 
					            }    
 | 
				
			||||||
            Task.Run(() => ConnectionClosed?.Invoke());
 | 
					            Task.Run(() => ConnectionClosed?.Invoke());
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -197,6 +216,12 @@ namespace CryptoExchange.Net.Sockets
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            Status = SocketStatus.Reconnecting;
 | 
					            Status = SocketStatus.Reconnecting;
 | 
				
			||||||
            DisconnectTime = DateTime.UtcNow;
 | 
					            DisconnectTime = DateTime.UtcNow;
 | 
				
			||||||
 | 
					            Authenticated = false;
 | 
				
			||||||
 | 
					            lock (subscriptionLock)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                foreach (var sub in subscriptions)
 | 
				
			||||||
 | 
					                    sub.Confirmed = false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            Task.Run(() => ConnectionLost?.Invoke());
 | 
					            Task.Run(() => ConnectionLost?.Invoke());
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -365,7 +390,7 @@ namespace CryptoExchange.Net.Sockets
 | 
				
			|||||||
            if (Status == SocketStatus.Closing || Status == SocketStatus.Closed || 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}");
 | 
					            log.Write(LogLevel.Debug, $"Socket {SocketId} closing subscription {subscription.Id}");
 | 
				
			||||||
            if (subscription.CancellationTokenRegistration.HasValue)
 | 
					            if (subscription.CancellationTokenRegistration.HasValue)
 | 
				
			||||||
                subscription.CancellationTokenRegistration.Value.Dispose();
 | 
					                subscription.CancellationTokenRegistration.Value.Dispose();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -377,7 +402,7 @@ namespace CryptoExchange.Net.Sockets
 | 
				
			|||||||
            {
 | 
					            {
 | 
				
			||||||
                if (Status == SocketStatus.Closing)
 | 
					                if (Status == SocketStatus.Closing)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    log.Write(LogLevel.Trace, $"Socket {SocketId} already closing");
 | 
					                    log.Write(LogLevel.Debug, $"Socket {SocketId} already closing");
 | 
				
			||||||
                    return;
 | 
					                    return;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -388,7 +413,7 @@ namespace CryptoExchange.Net.Sockets
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            if (shouldCloseConnection)
 | 
					            if (shouldCloseConnection)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                log.Write(LogLevel.Trace, $"Socket {SocketId} closing as there are no more subscriptions");
 | 
					                log.Write(LogLevel.Debug, $"Socket {SocketId} closing as there are no more subscriptions");
 | 
				
			||||||
                await CloseAsync().ConfigureAwait(false);
 | 
					                await CloseAsync().ConfigureAwait(false);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -414,7 +439,8 @@ namespace CryptoExchange.Net.Sockets
 | 
				
			|||||||
                    return false;
 | 
					                    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}");
 | 
					                if(subscription.UserSubscription)
 | 
				
			||||||
 | 
					                    log.Write(LogLevel.Debug, $"Socket {SocketId} adding new subscription with id {subscription.Id}, total subscriptions on connection: {subscriptions.Count(s => s.UserSubscription)}");
 | 
				
			||||||
                return true;
 | 
					                return true;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -567,7 +593,7 @@ namespace CryptoExchange.Net.Sockets
 | 
				
			|||||||
                return new CallResult<bool>(true);
 | 
					                return new CallResult<bool>(true);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (Authenticated)
 | 
					            if (subscriptions.Any(s => s.Authenticated))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                // If we reconnected a authenticated connection we need to re-authenticate
 | 
					                // If we reconnected a authenticated connection we need to re-authenticate
 | 
				
			||||||
                var authResult = await socketClient.AuthenticateSocketAsync(this).ConfigureAwait(false);
 | 
					                var authResult = await socketClient.AuthenticateSocketAsync(this).ConfigureAwait(false);
 | 
				
			||||||
@ -577,13 +603,22 @@ namespace CryptoExchange.Net.Sockets
 | 
				
			|||||||
                    return authResult;
 | 
					                    return authResult;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Authenticated = true;
 | 
				
			||||||
                log.Write(LogLevel.Debug, $"Socket {SocketId} authentication succeeded on reconnected socket.");
 | 
					                log.Write(LogLevel.Debug, $"Socket {SocketId} authentication succeeded on reconnected socket.");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Get a list of all subscriptions on the socket
 | 
					            // Get a list of all subscriptions on the socket
 | 
				
			||||||
            List<SocketSubscription> subscriptionList;
 | 
					            List<SocketSubscription> subscriptionList = new List<SocketSubscription>();
 | 
				
			||||||
            lock (subscriptionLock)
 | 
					            lock (subscriptionLock)
 | 
				
			||||||
                subscriptionList = subscriptions.Where(h => h.Request != null).ToList();
 | 
					            {
 | 
				
			||||||
 | 
					                foreach (var subscription in subscriptions)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    if (subscription.Request != null)
 | 
				
			||||||
 | 
					                        subscriptionList.Add(subscription);
 | 
				
			||||||
 | 
					                    else
 | 
				
			||||||
 | 
					                        subscription.Confirmed = true;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Foreach subscription which is subscribed by a subscription request we will need to resend that request to resubscribe
 | 
					            // Foreach subscription which is subscribed by a subscription request we will need to resend that request to resubscribe
 | 
				
			||||||
            for (var i = 0; i < subscriptionList.Count; i += socketClient.ClientOptions.MaxConcurrentResubscriptionsPerSocket)
 | 
					            for (var i = 0; i < subscriptionList.Count; i += socketClient.ClientOptions.MaxConcurrentResubscriptionsPerSocket)
 | 
				
			||||||
@ -600,6 +635,9 @@ namespace CryptoExchange.Net.Sockets
 | 
				
			|||||||
                    return taskList.First(t => !t.Result.Success).Result;
 | 
					                    return taskList.First(t => !t.Result.Success).Result;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            foreach (var subscription in subscriptionList)
 | 
				
			||||||
 | 
					                subscription.Confirmed = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (!_socket.IsOpen)
 | 
					            if (!_socket.IsOpen)
 | 
				
			||||||
                return new CallResult<bool>(new WebError("Socket not connected"));
 | 
					                return new CallResult<bool>(new WebError("Socket not connected"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -43,19 +43,25 @@ namespace CryptoExchange.Net.Sockets
 | 
				
			|||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        public bool Confirmed { get; set; }
 | 
					        public bool Confirmed { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Whether authentication is needed for this subscription
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        public bool Authenticated { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Cancellation token registration, should be disposed when subscription is closed. Used for closing the subscription with 
 | 
					        /// Cancellation token registration, should be disposed when subscription is closed. Used for closing the subscription with 
 | 
				
			||||||
        /// a provided cancelation token
 | 
					        /// a provided cancelation token
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        public CancellationTokenRegistration? CancellationTokenRegistration { get; set; }
 | 
					        public CancellationTokenRegistration? CancellationTokenRegistration { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private SocketSubscription(int id, object? request, string? identifier, bool userSubscription, Action<MessageEvent> dataHandler)
 | 
					        private SocketSubscription(int id, object? request, string? identifier, bool userSubscription, bool authenticated, Action<MessageEvent> dataHandler)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            Id = id;
 | 
					            Id = id;
 | 
				
			||||||
            UserSubscription = userSubscription;
 | 
					            UserSubscription = userSubscription;
 | 
				
			||||||
            MessageHandler = dataHandler;
 | 
					            MessageHandler = dataHandler;
 | 
				
			||||||
            Request = request;
 | 
					            Request = request;
 | 
				
			||||||
            Identifier = identifier;
 | 
					            Identifier = identifier;
 | 
				
			||||||
 | 
					            Authenticated = authenticated;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
@ -67,9 +73,9 @@ namespace CryptoExchange.Net.Sockets
 | 
				
			|||||||
        /// <param name="dataHandler"></param>
 | 
					        /// <param name="dataHandler"></param>
 | 
				
			||||||
        /// <returns></returns>
 | 
					        /// <returns></returns>
 | 
				
			||||||
        public static SocketSubscription CreateForRequest(int id, object request, bool userSubscription,
 | 
					        public static SocketSubscription CreateForRequest(int id, object request, bool userSubscription,
 | 
				
			||||||
            Action<MessageEvent> dataHandler)
 | 
					            bool authenticated, Action<MessageEvent> dataHandler)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return new SocketSubscription(id, request, null, userSubscription, dataHandler);
 | 
					            return new SocketSubscription(id, request, null, userSubscription, authenticated, dataHandler);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
@ -81,9 +87,9 @@ namespace CryptoExchange.Net.Sockets
 | 
				
			|||||||
        /// <param name="dataHandler"></param>
 | 
					        /// <param name="dataHandler"></param>
 | 
				
			||||||
        /// <returns></returns>
 | 
					        /// <returns></returns>
 | 
				
			||||||
        public static SocketSubscription CreateForIdentifier(int id, string identifier, bool userSubscription,
 | 
					        public static SocketSubscription CreateForIdentifier(int id, string identifier, bool userSubscription,
 | 
				
			||||||
            Action<MessageEvent> dataHandler)
 | 
					            bool authenticated, Action<MessageEvent> dataHandler)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return new SocketSubscription(id, null, identifier, userSubscription, dataHandler);
 | 
					            return new SocketSubscription(id, null, identifier, userSubscription, authenticated, dataHandler);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user