1
0
mirror of https://github.com/JKorf/CryptoExchange.Net synced 2025-06-09 08:56:13 +00:00
This commit is contained in:
JKorf 2023-11-02 22:19:42 +01:00
parent 35f7dbf9fb
commit 312d54cf04
9 changed files with 411 additions and 260 deletions

View File

@ -170,7 +170,6 @@ 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;
MessageListener? messageListener;
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
@ -195,8 +194,8 @@ namespace CryptoExchange.Net
socketConnection = socketResult.Data; socketConnection = socketResult.Data;
// Add a subscription on the socket connection // Add a subscription on the socket connection
messageListener = AddSubscription(subscription, true, socketConnection); var success = AddSubscription(subscription, true, socketConnection);
if (messageListener == null) if (!success)
{ {
_logger.Log(LogLevel.Trace, $"Socket {socketConnection.SocketId} failed to add subscription, retrying on different connection"); _logger.Log(LogLevel.Trace, $"Socket {socketConnection.SocketId} failed to add subscription, retrying on different connection");
continue; continue;
@ -234,31 +233,31 @@ namespace CryptoExchange.Net
if (request != null) if (request != null)
{ {
// Send the request and wait for answer // Send the request and wait for answer
var subResult = await SubscribeAndWaitAsync(socketConnection, request, messageListener).ConfigureAwait(false); var subResult = await SubscribeAndWaitAsync(socketConnection, subscription).ConfigureAwait(false);
if (!subResult) if (!subResult)
{ {
_logger.Log(LogLevel.Warning, $"Socket {socketConnection.SocketId} failed to subscribe: {subResult.Error}"); _logger.Log(LogLevel.Warning, $"Socket {socketConnection.SocketId} failed to subscribe: {subResult.Error}");
await socketConnection.CloseAsync(messageListener).ConfigureAwait(false); await socketConnection.CloseAsync(subscription).ConfigureAwait(false);
return new CallResult<UpdateSubscription>(subResult.Error!); return new CallResult<UpdateSubscription>(subResult.Error!);
} }
} }
else else
{ {
// No request to be sent, so just mark the subscription as comfirmed // No request to be sent, so just mark the subscription as comfirmed
messageListener.Confirmed = true; subscription.Confirmed = true;
} }
if (ct != default) if (ct != default)
{ {
messageListener.CancellationTokenRegistration = ct.Register(async () => subscription.CancellationTokenRegistration = ct.Register(async () =>
{ {
_logger.Log(LogLevel.Information, $"Socket {socketConnection.SocketId} Cancellation token set, closing subscription {messageListener.Id}"); _logger.Log(LogLevel.Information, $"Socket {socketConnection.SocketId} Cancellation token set, closing subscription {subscription.Id}");
await socketConnection.CloseAsync(messageListener).ConfigureAwait(false); await socketConnection.CloseAsync(subscription).ConfigureAwait(false);
}, false); }, false);
} }
_logger.Log(LogLevel.Information, $"Socket {socketConnection.SocketId} subscription {messageListener.Id} completed successfully"); _logger.Log(LogLevel.Information, $"Socket {socketConnection.SocketId} subscription {subscription.Id} completed successfully");
return new CallResult<UpdateSubscription>(new UpdateSubscription(socketConnection, messageListener)); return new CallResult<UpdateSubscription>(new UpdateSubscription(socketConnection, subscription));
} }
/// <summary> /// <summary>
@ -268,27 +267,13 @@ namespace CryptoExchange.Net
/// <param name="request">The request to send, will be serialized to json</param> /// <param name="request">The request to send, will be serialized to json</param>
/// <param name="listener">The message listener for the subscription</param> /// <param name="listener">The message listener for the subscription</param>
/// <returns></returns> /// <returns></returns>
protected internal virtual async Task<CallResult<bool>> SubscribeAndWaitAsync(SocketConnection socketConnection, object request, MessageListener listener) protected internal virtual async Task<CallResult> SubscribeAndWaitAsync(SocketConnection socketConnection, Subscription subscription)
{ {
CallResult? callResult = null; var callResult = await socketConnection.SendAndWaitSubAsync(subscription).ConfigureAwait(false);
await socketConnection.SendAndWaitAsync(request, ClientOptions.RequestTimeout, listener, 1, x => if (callResult)
{ subscription.Confirmed = true;
var (matches, result) = listener.Subscription!.MessageMatchesSubRequest(x);
if (matches)
callResult = result;
return matches;
}).ConfigureAwait(false);
if (callResult?.Success == true) return callResult;
{
listener.Confirmed = true;
return new CallResult<bool>(true);
}
if (callResult == null)
return new CallResult<bool>(new ServerError("No response on subscription request received"));
return new CallResult<bool>(callResult.Error!);
} }
/// <summary> /// <summary>
@ -297,7 +282,7 @@ namespace CryptoExchange.Net
/// <typeparam name="T">Expected result type</typeparam> /// <typeparam name="T">Expected result type</typeparam>
/// <param name="query">The query</param> /// <param name="query">The query</param>
/// <returns></returns> /// <returns></returns>
protected virtual Task<CallResult<T>> QueryAsync<T>(Query query) protected virtual Task<CallResult<T>> QueryAsync<T>(Query<T> query)
{ {
return QueryAsync<T>(BaseAddress, query); return QueryAsync<T>(BaseAddress, query);
} }
@ -309,7 +294,7 @@ namespace CryptoExchange.Net
/// <param name="url">The url for the request</param> /// <param name="url">The url for the request</param>
/// <param name="query">The query</param> /// <param name="query">The query</param>
/// <returns></returns> /// <returns></returns>
protected virtual async Task<CallResult<T>> QueryAsync<T>(string url, Query query) protected virtual async Task<CallResult<T>> QueryAsync<T>(string url, Query<T> query)
{ {
if (_disposing) if (_disposing)
return new CallResult<T>(new InvalidOperationError("Client disposed, can't query")); return new CallResult<T>(new InvalidOperationError("Client disposed, can't query"));
@ -358,22 +343,10 @@ namespace CryptoExchange.Net
/// <param name="socket">The connection to send and wait on</param> /// <param name="socket">The connection to send and wait on</param>
/// <param name="query">The query</param> /// <param name="query">The query</param>
/// <returns></returns> /// <returns></returns>
protected virtual async Task<CallResult<T>> QueryAndWaitAsync<T>(SocketConnection socket, Query query) protected virtual async Task<CallResult<T>> QueryAndWaitAsync<T>(SocketConnection socket, Query<T> query)
{ {
var dataResult = new CallResult<T>(new ServerError("No response on query received")); var dataResult = new CallResult<T>(new ServerError("No response on query received"));
await socket.SendAndWaitAsync(query.Request, ClientOptions.RequestTimeout, null, query.Weight, x => return await socket.SendAndWaitQueryAsync<T>(query).ConfigureAwait(false);
{
var matches = query.MessageMatchesQuery(x);
if (matches)
{
query.HandleResponse(x);
return true;
}
return false;
}).ConfigureAwait(false);
return dataResult;
} }
/// <summary> /// <summary>
@ -409,27 +382,16 @@ namespace CryptoExchange.Net
{ {
_logger.Log(LogLevel.Debug, $"Socket {socket.SocketId} Attempting to authenticate"); _logger.Log(LogLevel.Debug, $"Socket {socket.SocketId} Attempting to authenticate");
var authRequest = GetAuthenticationRequest(); var authRequest = GetAuthenticationRequest();
var authResult = new CallResult(new ServerError("No response from server")); var result = await socket.SendAndWaitQueryAsync(authRequest).ConfigureAwait(false);
await socket.SendAndWaitAsync(authRequest.Request, ClientOptions.RequestTimeout, null, 1, x =>
{
var matches = authRequest.MessageMatchesQuery(x);
if (matches)
{
authResult = authRequest.HandleResponse(x);
return true;
}
return false; if (!result)
}).ConfigureAwait(false);
if (!authResult)
{ {
_logger.Log(LogLevel.Warning, $"Socket {socket.SocketId} authentication failed"); _logger.Log(LogLevel.Warning, $"Socket {socket.SocketId} authentication failed");
if (socket.Connected) if (socket.Connected)
await socket.CloseAsync().ConfigureAwait(false); await socket.CloseAsync().ConfigureAwait(false);
authResult.Error!.Message = "Authentication failed: " + authResult.Error.Message; result.Error!.Message = "Authentication failed: " + result.Error.Message;
return new CallResult<bool>(authResult.Error); return new CallResult<bool>(result.Error)!;
} }
_logger.Log(LogLevel.Debug, $"Socket {socket.SocketId} authenticated"); _logger.Log(LogLevel.Debug, $"Socket {socket.SocketId} authenticated");
@ -451,13 +413,12 @@ 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>
/// <returns></returns> /// <returns></returns>
protected virtual MessageListener? AddSubscription(Subscription subscription, bool userSubscription, SocketConnection connection) protected virtual bool AddSubscription(Subscription subscription, bool userSubscription, SocketConnection connection)
{ {
var messageListener = new MessageListener(ExchangeHelpers.NextId(), subscription, userSubscription); if (!connection.AddListener(subscription))
if (!connection.AddListener(messageListener)) return false;
return null;
return messageListener; return false;
} }
/// <summary> /// <summary>
@ -467,9 +428,8 @@ namespace CryptoExchange.Net
protected void AddSystemSubscription(SystemSubscription systemSubscription) protected void AddSystemSubscription(SystemSubscription systemSubscription)
{ {
systemSubscriptions.Add(systemSubscription); systemSubscriptions.Add(systemSubscription);
var subscription = new MessageListener(ExchangeHelpers.NextId(), systemSubscription, false);
foreach (var connection in socketConnections.Values) foreach (var connection in socketConnections.Values)
connection.AddListener(subscription); connection.AddListener(systemSubscription);
} }
/// <summary> /// <summary>
@ -542,10 +502,7 @@ namespace CryptoExchange.Net
socketConnection.UnparsedMessage += HandleUnparsedMessage; socketConnection.UnparsedMessage += HandleUnparsedMessage;
foreach (var systemSubscription in systemSubscriptions) foreach (var systemSubscription in systemSubscriptions)
{ socketConnection.AddListener(systemSubscription);
var handler = new MessageListener(ExchangeHelpers.NextId(), systemSubscription, false);
socketConnection.AddListener(handler);
}
return new CallResult<SocketConnection>(socketConnection); return new CallResult<SocketConnection>(socketConnection);
} }
@ -665,7 +622,7 @@ namespace CryptoExchange.Net
/// <returns></returns> /// <returns></returns>
public virtual async Task<bool> UnsubscribeAsync(int subscriptionId) public virtual async Task<bool> UnsubscribeAsync(int subscriptionId)
{ {
MessageListener? subscription = null; Subscription? subscription = null;
SocketConnection? connection = null; SocketConnection? connection = null;
foreach (var socket in socketConnections.Values.ToList()) foreach (var socket in socketConnections.Values.ToList())
{ {
@ -747,7 +704,7 @@ namespace CryptoExchange.Net
foreach (var connection in socketConnections) foreach (var connection in socketConnections)
{ {
sb.AppendLine($" Connection {connection.Key}: {connection.Value.UserListenerCount} subscriptions, status: {connection.Value.Status}, authenticated: {connection.Value.Authenticated}, kbps: {connection.Value.IncomingKbps}"); sb.AppendLine($" Connection {connection.Key}: {connection.Value.UserListenerCount} subscriptions, status: {connection.Value.Status}, authenticated: {connection.Value.Authenticated}, kbps: {connection.Value.IncomingKbps}");
foreach (var subscription in connection.Value.MessageListeners) foreach (var subscription in connection.Value.Subscriptions)
sb.AppendLine($" Subscription {subscription.Id}, authenticated: {subscription.Authenticated}, confirmed: {subscription.Confirmed}"); sb.AppendLine($" Subscription {subscription.Id}, authenticated: {subscription.Authenticated}, confirmed: {subscription.Confirmed}");
} }
return sb.ToString(); return sb.ToString();

View File

@ -1,4 +1,5 @@
using CryptoExchange.Net.Objects.Sockets; using CryptoExchange.Net.Objects.Sockets;
using CryptoExchange.Net.Sockets;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using System; using System;
@ -28,10 +29,10 @@ namespace CryptoExchange.Net.Converters
/// <param name="idValues"></param> /// <param name="idValues"></param>
/// <param name="listeners"></param> /// <param name="listeners"></param>
/// <returns></returns> /// <returns></returns>
public abstract Type? GetDeserializationType(Dictionary<string, string?> idValues, List<MessageListener> listeners); public abstract Type? GetDeserializationType(Dictionary<string, string?> idValues, List<BasePendingRequest> pendingRequests, List<Subscription> listeners);
/// <inheritdoc /> /// <inheritdoc />
public ParsedMessage? ReadJson(Stream stream, List<MessageListener> listeners, bool outputOriginalData) public ParsedMessage? ReadJson(Stream stream, List<BasePendingRequest> pendingRequests, List<Subscription> listeners, bool outputOriginalData)
{ {
// Start reading the data // Start reading the data
// Once we reach the properties that identify the message we save those in a dict // Once we reach the properties that identify the message we save those in a dict
@ -81,7 +82,7 @@ namespace CryptoExchange.Net.Converters
} }
result.Identifier = idString; result.Identifier = idString;
var resultType = GetDeserializationType(typeIdDict, listeners); var resultType = GetDeserializationType(typeIdDict, pendingRequests, listeners);
result.Data = resultType == null ? null : token.ToObject(resultType); result.Data = resultType == null ? null : token.ToObject(resultType);
return result; return result;
} }

View File

@ -1,43 +1,47 @@
using CryptoExchange.Net.Converters; using CryptoExchange.Net.Converters;
using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Sockets;
using System; using System;
using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace CryptoExchange.Net.Objects.Sockets namespace CryptoExchange.Net.Objects.Sockets
{ {
internal class PendingRequest public abstract class BasePendingRequest
{ {
public int Id { get; set; } public int Id { get; set; }
public Func<ParsedMessage, bool> MessageMatchesHandler { get; } public Func<ParsedMessage, bool> MessageMatchesHandler { get; }
public bool Completed { get; private set; } public bool Completed { get; private set; }
public abstract Type ResponseType { get; }
public AsyncResetEvent Event { get; } public AsyncResetEvent Event { get; }
public DateTime RequestTimestamp { get; set; } public DateTime RequestTimestamp { get; set; }
public TimeSpan Timeout { get; } public TimeSpan Timeout { get; }
public MessageListener? MessageListener { get; } public object Request { get; set; }
private CancellationTokenSource? _cts; private CancellationTokenSource? _cts;
public int Priority => 100; public int Priority => 100;
public PendingRequest(int id, Func<ParsedMessage, bool> messageMatchesHandler, TimeSpan timeout, MessageListener? subscription) protected BasePendingRequest(int id, object request, Func<ParsedMessage, bool> messageMatchesHandler, TimeSpan timeout)
{ {
Id = id; Id = id;
MessageMatchesHandler = messageMatchesHandler; MessageMatchesHandler = messageMatchesHandler;
Event = new AsyncResetEvent(false, false); Event = new AsyncResetEvent(false, false);
Timeout = timeout; Timeout = timeout;
Request = request;
RequestTimestamp = DateTime.UtcNow; RequestTimestamp = DateTime.UtcNow;
MessageListener = subscription;
} }
public void IsSend() public void IsSend()
{ {
// Start timeout countdown // Start timeout countdown
_cts = new CancellationTokenSource(Timeout); _cts = new CancellationTokenSource(Timeout);
_cts.Token.Register(Fail, false); _cts.Token.Register(() => Fail("No response"), false);
} }
public void Fail() public virtual void Fail(string error)
{ {
Completed = true; Completed = true;
Event.Set(); Event.Set();
@ -48,11 +52,85 @@ namespace CryptoExchange.Net.Objects.Sockets
return MessageMatchesHandler(message); return MessageMatchesHandler(message);
} }
public Task ProcessAsync(ParsedMessage message) public virtual Task ProcessAsync(ParsedMessage message)
{ {
Completed = true; Completed = true;
Event.Set(); Event.Set();
return Task.CompletedTask; return Task.CompletedTask;
} }
} }
public class PendingRequest : BasePendingRequest
{
public CallResult Result { get; set; }
public Func<ParsedMessage, CallResult> Handler { get; }
public override Type? ResponseType => null;
private PendingRequest(int id, object request, Func<ParsedMessage, bool> messageMatchesHandler, Func<ParsedMessage, CallResult> messageHandler, TimeSpan timeout)
: base(id, request, messageMatchesHandler, timeout)
{
Handler = messageHandler;
}
public static PendingRequest CreateForQuery(Query query)
{
return new PendingRequest(ExchangeHelpers.NextId(), query.Request, query.MessageMatchesQuery, query.HandleResult, TimeSpan.FromSeconds(5));
}
public static PendingRequest CreateForSubRequest(Subscription subscription)
{
return new PendingRequest(ExchangeHelpers.NextId(), subscription.GetSubRequest, subscription.MessageMatchesSubRequest, subscription.HandleSubResponse, TimeSpan.FromSeconds(5));
}
public static PendingRequest CreateForUnsubRequest(Subscription subscription)
{
return new PendingRequest(ExchangeHelpers.NextId(), subscription.GetUnsubRequest, subscription.MessageMatchesUnsubRequest, subscription.HandleUnsubResponse, TimeSpan.FromSeconds(5));
}
public override void Fail(string error)
{
Result = new CallResult(new ServerError(error));
base.Fail(error);
}
public override Task ProcessAsync(ParsedMessage message)
{
Result = Handler(message);
return base.ProcessAsync(message);
}
}
public class PendingRequest<T> : BasePendingRequest
{
public CallResult<T> Result { get; set; }
public Func<ParsedMessage, CallResult<T>> Handler { get; }
public override Type? ResponseType => typeof(T);
public PendingRequest(int id, object request, Func<ParsedMessage, bool> messageMatchesHandler, Func<ParsedMessage, CallResult<T>> messageHandler, TimeSpan timeout)
: base(id, request, messageMatchesHandler, timeout)
{
Handler = messageHandler;
}
public static PendingRequest<T> CreateForQuery<T>(Query<T> query)
{
return new PendingRequest<T>(ExchangeHelpers.NextId(), query.Request, query.MessageMatchesQuery, x =>
{
var response = query.HandleResponse(x);
return response.As((T)response.Data);
}, TimeSpan.FromSeconds(5));
}
public override void Fail(string error)
{
Result = new CallResult<T>(new ServerError(error));
base.Fail(error);
}
public override Task ProcessAsync(ParsedMessage message)
{
Result = Handler(message);
return base.ProcessAsync(message);
}
}
} }

View File

@ -1,95 +1,95 @@
using CryptoExchange.Net.Converters; //using CryptoExchange.Net.Converters;
using CryptoExchange.Net.Interfaces; //using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Sockets; //using CryptoExchange.Net.Sockets;
using System; //using System;
using System.Threading; //using System.Threading;
using System.Threading.Tasks; //using System.Threading.Tasks;
namespace CryptoExchange.Net.Objects.Sockets //namespace CryptoExchange.Net.Objects.Sockets
{ //{
/// <summary> // /// <summary>
/// Socket listener // /// Socket listener
/// </summary> // /// </summary>
public class MessageListener // public class MessageListener
{ // {
/// <summary> // /// <summary>
/// Unique listener id // /// Unique listener id
/// </summary> // /// </summary>
public int Id { get; } // public int Id { get; }
/// <summary> // /// <summary>
/// Exception event // /// Exception event
/// </summary> // /// </summary>
public event Action<Exception>? Exception; // public event Action<Exception>? Exception;
/// <summary> // /// <summary>
/// The request object send when subscribing on the server. Either this or the `Identifier` property should be set // /// The request object send when subscribing on the server. Either this or the `Identifier` property should be set
/// </summary> // /// </summary>
public Subscription Subscription { get; set; } // public Subscription Subscription { get; set; }
/// <summary> // /// <summary>
/// Whether this is a user subscription or an internal listener // /// Whether this is a user subscription or an internal listener
/// </summary> // /// </summary>
public bool UserListener { get; set; } // public bool UserListener { get; set; }
/// <summary> // /// <summary>
/// If the subscription has been confirmed to be subscribed by the server // /// If the subscription has been confirmed to be subscribed by the server
/// </summary> // /// </summary>
public bool Confirmed { get; set; } // public bool Confirmed { get; set; }
/// <summary> // /// <summary>
/// Whether authentication is needed for this subscription // /// Whether authentication is needed for this subscription
/// </summary> // /// </summary>
public bool Authenticated => Subscription.Authenticated; // public bool Authenticated => Subscription.Authenticated;
/// <summary> // /// <summary>
/// Whether we're closing this subscription and a socket connection shouldn't be kept open for it // /// Whether we're closing this subscription and a socket connection shouldn't be kept open for it
/// </summary> // /// </summary>
public bool Closed { get; set; } // public bool Closed { 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; }
/// <summary> // /// <summary>
/// ctor // /// ctor
/// </summary> // /// </summary>
/// <param name="id"></param> // /// <param name="id"></param>
/// <param name="request"></param> // /// <param name="request"></param>
/// <param name="userSubscription"></param> // /// <param name="userSubscription"></param>
public MessageListener(int id, Subscription request, bool userSubscription) // public MessageListener(int id, Subscription request, bool userSubscription)
{ // {
Id = id; // Id = id;
UserListener = userSubscription; // UserListener = userSubscription;
Subscription = request; // Subscription = request;
} // }
/// <summary> // /// <summary>
/// Invoke the exception event // /// Invoke the exception event
/// </summary> // /// </summary>
/// <param name="e"></param> // /// <param name="e"></param>
public void InvokeExceptionHandler(Exception e) // public void InvokeExceptionHandler(Exception e)
{ // {
Exception?.Invoke(e); // Exception?.Invoke(e);
} // }
/// <summary> // /// <summary>
/// The priority of this subscription // /// The priority of this subscription
/// </summary> // /// </summary>
public int Priority => Subscription is SystemSubscription ? 50 : 1; // public int Priority => Subscription is SystemSubscription ? 50 : 1;
/// <summary> // /// <summary>
/// Process the message // /// Process the message
/// </summary> // /// </summary>
/// <param name="message"></param> // /// <param name="message"></param>
/// <returns></returns> // /// <returns></returns>
public Task ProcessAsync(ParsedMessage message) // public Task ProcessAsync(ParsedMessage message)
{ // {
// TODO // // TODO
var dataEvent = new DataEvent<ParsedMessage>(message, null, message.OriginalData, DateTime.UtcNow, null); // var dataEvent = new DataEvent<ParsedMessage>(message, null, message.OriginalData, DateTime.UtcNow, null);
return Subscription.HandleEventAsync(dataEvent); // return Subscription.HandleEventAsync(dataEvent);
} // }
} // }
} //}

View File

@ -11,7 +11,7 @@ namespace CryptoExchange.Net.Objects.Sockets
public class UpdateSubscription public class UpdateSubscription
{ {
private readonly SocketConnection _connection; private readonly SocketConnection _connection;
private readonly MessageListener _listener; private readonly Subscription _listener;
/// <summary> /// <summary>
/// Event when the connection is lost. The socket will automatically reconnect when possible. /// Event when the connection is lost. The socket will automatically reconnect when possible.
@ -84,7 +84,7 @@ namespace CryptoExchange.Net.Objects.Sockets
/// </summary> /// </summary>
/// <param name="connection">The socket connection the subscription is on</param> /// <param name="connection">The socket connection the subscription is on</param>
/// <param name="subscription">The subscription</param> /// <param name="subscription">The subscription</param>
public UpdateSubscription(SocketConnection connection, MessageListener subscription) public UpdateSubscription(SocketConnection connection, Subscription subscription)
{ {
_connection = connection; _connection = connection;
_listener = subscription; _listener = subscription;
@ -121,7 +121,7 @@ namespace CryptoExchange.Net.Objects.Sockets
/// Resubscribe this subscription /// Resubscribe this subscription
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
internal async Task<CallResult<bool>> ResubscribeAsync() internal async Task<CallResult> ResubscribeAsync()
{ {
return await _connection.ResubscribeAsync(_listener).ConfigureAwait(false); return await _connection.ResubscribeAsync(_listener).ConfigureAwait(false);
} }

View File

@ -1,13 +1,14 @@
using CryptoExchange.Net.Converters; using CryptoExchange.Net.Converters;
using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects.Sockets; using CryptoExchange.Net.Objects.Sockets;
using System;
namespace CryptoExchange.Net.Sockets namespace CryptoExchange.Net.Sockets
{ {
/// <summary> /// <summary>
/// Query /// Query
/// </summary> /// </summary>
public abstract class Query public abstract class BaseQuery
{ {
/// <summary> /// <summary>
/// The query request /// The query request
@ -30,12 +31,6 @@ namespace CryptoExchange.Net.Sockets
/// <param name="message"></param> /// <param name="message"></param>
/// <returns></returns> /// <returns></returns>
public abstract bool MessageMatchesQuery(ParsedMessage message); public abstract bool MessageMatchesQuery(ParsedMessage message);
/// <summary>
/// Handle the query response
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
public abstract CallResult HandleResponse(ParsedMessage message);
/// <summary> /// <summary>
/// ctor /// ctor
@ -43,11 +38,39 @@ namespace CryptoExchange.Net.Sockets
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="authenticated"></param> /// <param name="authenticated"></param>
/// <param name="weight"></param> /// <param name="weight"></param>
public Query(object request, bool authenticated, int weight = 1) public BaseQuery(object request, bool authenticated, int weight = 1)
{ {
Authenticated = authenticated; Authenticated = authenticated;
Request = request; Request = request;
Weight = weight; Weight = weight;
} }
} }
public abstract class Query : BaseQuery
{
protected Query(object request, bool authenticated, int weight = 1) : base(request, authenticated, weight)
{
}
/// <summary>
/// Handle the query response
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
public abstract CallResult HandleResult(ParsedMessage message);
}
public abstract class Query<TResponse> : BaseQuery
{
protected Query(object request, bool authenticated, int weight = 1) : base(request, authenticated, weight)
{
}
/// <summary>
/// Handle the query response
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
public abstract CallResult<TResponse> HandleResponse(ParsedMessage message);
}
} }

View File

@ -62,18 +62,18 @@ namespace CryptoExchange.Net.Sockets
public int UserListenerCount public int UserListenerCount
{ {
get { lock (_listenerLock) get { lock (_listenerLock)
return _messageIdentifierListeners.Values.Count(h => h.UserListener); } return _messageIdentifierListeners.Values.Count(h => h.UserSubscription); }
} }
/// <summary> /// <summary>
/// Get a copy of the current message listeners /// Get a copy of the current message listeners
/// </summary> /// </summary>
public MessageListener[] MessageListeners public Subscription[] Subscriptions
{ {
get get
{ {
lock (_listenerLock) lock (_listenerLock)
return _messageIdentifierListeners.Values.Where(h => h.UserListener).ToArray(); return _messageIdentifierListeners.Values.Where(h => h.UserSubscription).ToArray();
} }
} }
@ -158,9 +158,9 @@ namespace CryptoExchange.Net.Sockets
} }
private bool _pausedActivity; private bool _pausedActivity;
private readonly List<PendingRequest> _pendingRequests; private readonly List<BasePendingRequest> _pendingRequests;
private readonly List<MessageListener> _messageListeners; private readonly List<Subscription> _messageListeners;
private readonly Dictionary<string, MessageListener> _messageIdentifierListeners; private readonly Dictionary<string, Subscription> _messageIdentifierListeners;
private readonly object _listenerLock = new(); private readonly object _listenerLock = new();
private readonly ILogger _logger; private readonly ILogger _logger;
@ -186,9 +186,9 @@ namespace CryptoExchange.Net.Sockets
Tag = tag; Tag = tag;
Properties = new Dictionary<string, object>(); Properties = new Dictionary<string, object>();
_pendingRequests = new List<PendingRequest>(); _pendingRequests = new List<BasePendingRequest>();
_messageListeners = new List<MessageListener>(); _messageListeners = new List<Subscription>();
_messageIdentifierListeners = new Dictionary<string, MessageListener>(); _messageIdentifierListeners = new Dictionary<string, Subscription>();
_socket = socket; _socket = socket;
_socket.OnStreamMessage += HandleStreamMessage; _socket.OnStreamMessage += HandleStreamMessage;
@ -261,7 +261,7 @@ namespace CryptoExchange.Net.Sockets
{ {
foreach (var pendingRequest in _pendingRequests.ToList()) foreach (var pendingRequest in _pendingRequests.ToList())
{ {
pendingRequest.Fail(); pendingRequest.Fail("Connection interupted");
// Remove? // Remove?
} }
} }
@ -301,7 +301,7 @@ namespace CryptoExchange.Net.Sockets
/// <param name="requestId">Id of the request sent</param> /// <param name="requestId">Id of the request sent</param>
protected virtual void HandleRequestSent(int requestId) protected virtual void HandleRequestSent(int requestId)
{ {
PendingRequest pendingRequest; BasePendingRequest pendingRequest;
lock (_pendingRequests) lock (_pendingRequests)
pendingRequest = _pendingRequests.SingleOrDefault(p => p.Id == requestId); pendingRequest = _pendingRequests.SingleOrDefault(p => p.Id == requestId);
@ -324,11 +324,11 @@ namespace CryptoExchange.Net.Sockets
var timestamp = DateTime.UtcNow; var timestamp = DateTime.UtcNow;
TimeSpan userCodeDuration = TimeSpan.Zero; TimeSpan userCodeDuration = TimeSpan.Zero;
List<MessageListener> listeners; List<Subscription> listeners;
lock (_listenerLock) lock (_listenerLock)
listeners = _messageListeners.OrderByDescending(x => x.Priority).ToList(); listeners = _messageListeners.OrderByDescending(x => !x.UserSubscription).ToList();
var result = ApiClient.StreamConverter.ReadJson(stream, listeners, ApiClient.ApiOptions.OutputOriginalData ?? ApiClient.ClientOptions.OutputOriginalData); // TODO var result = ApiClient.StreamConverter.ReadJson(stream, _pendingRequests, listeners, ApiClient.ApiOptions.OutputOriginalData ?? ApiClient.ClientOptions.OutputOriginalData);
if(result == null) if(result == null)
{ {
stream.Position = 0; stream.Position = 0;
@ -352,12 +352,13 @@ namespace CryptoExchange.Net.Sockets
{ {
// Matched based on identifier // Matched based on identifier
var userSw = Stopwatch.StartNew(); var userSw = Stopwatch.StartNew();
await idListener.ProcessAsync(result).ConfigureAwait(false); var dataEvent = new DataEvent<ParsedMessage>(result, null, result.OriginalData, DateTime.UtcNow, null);
await idListener.HandleEventAsync(dataEvent).ConfigureAwait(false);
userSw.Stop(); userSw.Stop();
return; return;
} }
List<PendingRequest> pendingRequests; List<BasePendingRequest> pendingRequests;
lock (_pendingRequests) lock (_pendingRequests)
pendingRequests = _pendingRequests.ToList(); pendingRequests = _pendingRequests.ToList();
@ -371,11 +372,12 @@ namespace CryptoExchange.Net.Sockets
if (pendingRequest.Completed) if (pendingRequest.Completed)
{ {
// Answer to a timed out request, unsub if it is a subscription request // Answer to a timed out request, unsub if it is a subscription request
if (pendingRequest.MessageListener != null) // TODO
{ //if (pendingRequest.MessageListener != null)
_logger.Log(LogLevel.Warning, $"Socket {SocketId} Received subscription info after request timed out; unsubscribing. Consider increasing the RequestTimeout"); //{
_ = UnsubscribeAsync(pendingRequest.MessageListener).ConfigureAwait(false); // _logger.Log(LogLevel.Warning, $"Socket {SocketId} Received subscription info after request timed out; unsubscribing. Consider increasing the RequestTimeout");
} // _ = UnsubscribeAsync(pendingRequest.MessageListener).ConfigureAwait(false);
//}
} }
else else
{ {
@ -443,25 +445,25 @@ namespace CryptoExchange.Net.Sockets
/// </summary> /// </summary>
/// <param name="listener">Listener to close</param> /// <param name="listener">Listener to close</param>
/// <returns></returns> /// <returns></returns>
public async Task CloseAsync(MessageListener listener) public async Task CloseAsync(Subscription subscription)
{ {
lock (_listenerLock) lock (_listenerLock)
{ {
if (!_messageListeners.Contains(listener)) if (!_messageListeners.Contains(subscription))
return; return;
listener.Closed = true; subscription.Closed = true;
} }
if (Status == SocketStatus.Closing || Status == SocketStatus.Closed || Status == SocketStatus.Disposed) if (Status == SocketStatus.Closing || Status == SocketStatus.Closed || Status == SocketStatus.Disposed)
return; return;
_logger.Log(LogLevel.Debug, $"Socket {SocketId} closing listener {listener.Id}"); _logger.Log(LogLevel.Debug, $"Socket {SocketId} closing listener {subscription.Id}");
if (listener.CancellationTokenRegistration.HasValue) if (subscription.CancellationTokenRegistration.HasValue)
listener.CancellationTokenRegistration.Value.Dispose(); subscription.CancellationTokenRegistration.Value.Dispose();
if (listener.Confirmed && _socket.IsOpen) if (subscription.Confirmed && _socket.IsOpen)
await UnsubscribeAsync(listener).ConfigureAwait(false); await UnsubscribeAsync(subscription).ConfigureAwait(false);
bool shouldCloseConnection; bool shouldCloseConnection;
lock (_listenerLock) lock (_listenerLock)
@ -472,7 +474,7 @@ namespace CryptoExchange.Net.Sockets
return; return;
} }
shouldCloseConnection = _messageIdentifierListeners.All(r => !r.Value.UserListener || r.Value.Closed); shouldCloseConnection = _messageIdentifierListeners.All(r => !r.Value.UserSubscription || r.Value.Closed);
if (shouldCloseConnection) if (shouldCloseConnection)
Status = SocketStatus.Closing; Status = SocketStatus.Closing;
} }
@ -485,8 +487,8 @@ namespace CryptoExchange.Net.Sockets
lock (_listenerLock) lock (_listenerLock)
{ {
_messageListeners.Remove(listener); _messageListeners.Remove(subscription);
foreach (var id in listener.Subscription.Identifiers) foreach (var id in subscription.Identifiers)
_messageIdentifierListeners.Remove(id); _messageIdentifierListeners.Remove(id);
} }
} }
@ -504,22 +506,22 @@ namespace CryptoExchange.Net.Sockets
/// Add a listener to this connection /// Add a listener to this connection
/// </summary> /// </summary>
/// <param name="listener"></param> /// <param name="listener"></param>
public bool AddListener(MessageListener listener) public bool AddListener(Subscription subscription)
{ {
lock (_listenerLock) lock (_listenerLock)
{ {
if (Status != SocketStatus.None && Status != SocketStatus.Connected) if (Status != SocketStatus.None && Status != SocketStatus.Connected)
return false; return false;
_messageListeners.Add(listener); _messageListeners.Add(subscription);
if (listener.Subscription.Identifiers != null) if (subscription.Identifiers != null)
{ {
foreach (var id in listener.Subscription.Identifiers) foreach (var id in subscription.Identifiers)
_messageIdentifierListeners.Add(id.ToLowerInvariant(), listener); _messageIdentifierListeners.Add(id.ToLowerInvariant(), subscription);
} }
if (listener.UserListener) if (subscription.UserSubscription)
_logger.Log(LogLevel.Debug, $"Socket {SocketId} adding new listener with id {listener.Id}, total listeners on connection: {_messageListeners.Count(s => s.UserListener)}"); _logger.Log(LogLevel.Debug, $"Socket {SocketId} adding new listener with id {subscription.Id}, total listeners on connection: {_messageListeners.Count(s => s.UserSubscription)}");
return true; return true;
} }
} }
@ -528,7 +530,7 @@ namespace CryptoExchange.Net.Sockets
/// Get a listener on this connection by id /// Get a listener on this connection by id
/// </summary> /// </summary>
/// <param name="id"></param> /// <param name="id"></param>
public MessageListener? GetListener(int id) public Subscription? GetListener(int id)
{ {
lock (_listenerLock) lock (_listenerLock)
return _messageListeners.SingleOrDefault(s => s.Id == id); return _messageListeners.SingleOrDefault(s => s.Id == id);
@ -539,34 +541,51 @@ namespace CryptoExchange.Net.Sockets
/// </summary> /// </summary>
/// <param name="predicate">Filter for a request</param> /// <param name="predicate">Filter for a request</param>
/// <returns></returns> /// <returns></returns>
public MessageListener? GetListenerByRequest(Func<object?, bool> predicate) public Subscription? GetListenerByRequest(Func<object?, bool> predicate)
{ {
lock(_listenerLock) lock(_listenerLock)
return _messageListeners.SingleOrDefault(s => predicate(s.Subscription)); return _messageListeners.SingleOrDefault(s => predicate(s));
} }
/// <summary> public virtual async Task<CallResult<T>> SendAndWaitQueryAsync<T>(Query<T> query)
/// Send data and wait for an answer {
/// </summary> var pendingRequest = PendingRequest<T>.CreateForQuery(query);
/// <typeparam name="T">The data type expected in response</typeparam> await SendAndWaitAsync(pendingRequest, query.Weight).ConfigureAwait(false);
/// <param name="obj">The object to send</param> return pendingRequest.Result;
/// <param name="timeout">The timeout for response</param> }
/// <param name="listener">Listener if this is a subscribe request</param>
/// <param name="handler">The response handler</param> public virtual async Task<CallResult> SendAndWaitQueryAsync(Query query)
/// <param name="weight">The weight of the message</param> {
/// <returns></returns> var pendingRequest = PendingRequest.CreateForQuery(query);
public virtual async Task SendAndWaitAsync<T>(T obj, TimeSpan timeout, MessageListener? listener, int weight, Func<ParsedMessage, bool> handler) await SendAndWaitAsync(pendingRequest, query.Weight).ConfigureAwait(false);
return pendingRequest.Result;
}
public virtual async Task<CallResult> SendAndWaitSubAsync(Subscription subscription)
{
var pendingRequest = PendingRequest.CreateForSubRequest(subscription);
await SendAndWaitAsync(pendingRequest, 1).ConfigureAwait(false);
return pendingRequest.Result;
}
public virtual async Task<CallResult> SendAndWaitUnsubAsync(Subscription subscription)
{
var pendingRequest = PendingRequest.CreateForUnsubRequest(subscription);
await SendAndWaitAsync(pendingRequest, 1).ConfigureAwait(false);
return pendingRequest.Result;
}
private async Task SendAndWaitAsync(BasePendingRequest pending, int weight)
{ {
var pending = new PendingRequest(ExchangeHelpers.NextId(), handler, timeout, listener);
lock (_messageListeners) lock (_messageListeners)
{ {
_pendingRequests.Add(pending); _pendingRequests.Add(pending);
} }
var sendOk = Send(pending.Id, obj, weight); var sendOk = Send(pending.Id, pending.Request, weight);
if (!sendOk) if (!sendOk)
{ {
pending.Fail(); pending.Fail("Failed to send");
return; return;
} }
@ -574,7 +593,7 @@ namespace CryptoExchange.Net.Sockets
{ {
if (!_socket.IsOpen) if (!_socket.IsOpen)
{ {
pending.Fail(); pending.Fail("Socket not open");
return; return;
} }
@ -588,6 +607,52 @@ namespace CryptoExchange.Net.Sockets
} }
} }
///// <summary>
///// Send data and wait for an answer
///// </summary>
///// <typeparam name="T">The data type expected in response</typeparam>
///// <param name="obj">The object to send</param>
///// <param name="timeout">The timeout for response</param>
///// <param name="listener">Listener if this is a subscribe request</param>
///// <param name="handler">The response handler</param>
///// <param name="weight">The weight of the message</param>
///// <returns></returns>
//public virtual async Task SendAndWaitAsync<T>(T obj, TimeSpan timeout, MessageListener? listener, int weight, Func<ParsedMessage, bool> handler)
//{
// // TODO either Query<T> or Subscription<T> should be passed here instead of T obj
// // That would allow to track the Query/Subscription on the PendingRequest instead of the listener, which allow us to match the pending request in the Converter
// var pending = new PendingRequest(ExchangeHelpers.NextId(), handler, timeout, listener);
// lock (_messageListeners)
// {
// _pendingRequests.Add(pending);
// }
// var sendOk = Send(pending.Id, obj, weight);
// if (!sendOk)
// {
// pending.Fail();
// return;
// }
// while (true)
// {
// if(!_socket.IsOpen)
// {
// pending.Fail();
// return;
// }
// if (pending.Completed)
// return;
// await pending.Event.WaitAsync(TimeSpan.FromMilliseconds(500)).ConfigureAwait(false);
// if (pending.Completed)
// return;
// }
//}
/// <summary> /// <summary>
/// Send data over the websocket connection /// Send data over the websocket connection
/// </summary> /// </summary>
@ -624,14 +689,14 @@ namespace CryptoExchange.Net.Sockets
} }
} }
private async Task<CallResult<bool>> ProcessReconnectAsync() private async Task<CallResult> ProcessReconnectAsync()
{ {
if (!_socket.IsOpen) if (!_socket.IsOpen)
return new CallResult<bool>(new WebError("Socket not connected")); return new CallResult<bool>(new WebError("Socket not connected"));
bool anySubscriptions = false; bool anySubscriptions = false;
lock (_listenerLock) lock (_listenerLock)
anySubscriptions = _messageListeners.Any(s => s.UserListener); anySubscriptions = _messageListeners.Any(s => s.UserSubscription);
if (!anySubscriptions) if (!anySubscriptions)
{ {
@ -660,21 +725,22 @@ namespace CryptoExchange.Net.Sockets
} }
// Get a list of all subscriptions on the socket // Get a list of all subscriptions on the socket
List<MessageListener> listenerList = new List<MessageListener>(); List<Subscription> listenerList = new List<Subscription>();
lock (_listenerLock) lock (_listenerLock)
{ {
// ?
foreach (var listener in _messageListeners) foreach (var listener in _messageListeners)
{ {
if (listener.Subscription != null) if (listener != null)
listenerList.Add(listener); listenerList.Add(listener);
else else
listener.Confirmed = true; listener.Confirmed = true;
} }
} }
foreach(var listener in listenerList.Where(s => s.Subscription != null)) foreach(var listener in listenerList)
{ {
var result = await ApiClient.RevitalizeRequestAsync(listener.Subscription!).ConfigureAwait(false); var result = await ApiClient.RevitalizeRequestAsync(listener).ConfigureAwait(false);
if (!result) if (!result)
{ {
_logger.Log(LogLevel.Warning, $"Socket {SocketId} Failed request revitalization: " + result.Error); _logger.Log(LogLevel.Warning, $"Socket {SocketId} Failed request revitalization: " + result.Error);
@ -688,9 +754,9 @@ namespace CryptoExchange.Net.Sockets
if (!_socket.IsOpen) if (!_socket.IsOpen)
return new CallResult<bool>(new WebError("Socket not connected")); return new CallResult<bool>(new WebError("Socket not connected"));
var taskList = new List<Task<CallResult<bool>>>(); var taskList = new List<Task<CallResult>>();
foreach (var listener in listenerList.Skip(i).Take(ApiClient.ClientOptions.MaxConcurrentResubscriptionsPerSocket)) foreach (var listener in listenerList.Skip(i).Take(ApiClient.ClientOptions.MaxConcurrentResubscriptionsPerSocket))
taskList.Add(ApiClient.SubscribeAndWaitAsync(this, listener.Subscription!, listener)); taskList.Add(ApiClient.SubscribeAndWaitAsync(this, listener));
await Task.WhenAll(taskList).ConfigureAwait(false); await Task.WhenAll(taskList).ConfigureAwait(false);
if (taskList.Any(t => !t.Result.Success)) if (taskList.Any(t => !t.Result.Success))
@ -707,28 +773,24 @@ namespace CryptoExchange.Net.Sockets
return new CallResult<bool>(true); return new CallResult<bool>(true);
} }
internal async Task UnsubscribeAsync(MessageListener listener) internal async Task UnsubscribeAsync(Subscription subscription)
{ {
var unsubscribeRequest = listener.Subscription?.GetUnsubRequest(); var unsubscribeRequest = subscription?.GetUnsubRequest();
if (unsubscribeRequest != null) if (unsubscribeRequest != null)
{ {
await SendAndWaitAsync(unsubscribeRequest, TimeSpan.FromSeconds(10), listener, 0, x => var pendingRequest = PendingRequest.CreateForUnsubRequest(subscription!);
{ await SendAndWaitAsync(pendingRequest, 1).ConfigureAwait(false);
var (matches, result) = listener.Subscription!.MessageMatchesUnsubRequest(x);
// TODO check result?
return matches;
}).ConfigureAwait(false);
_logger.Log(LogLevel.Information, $"Socket {SocketId} subscription {listener.Id} unsubscribed"); _logger.Log(LogLevel.Information, $"Socket {SocketId} subscription {subscription.Id} unsubscribed");
} }
} }
internal async Task<CallResult<bool>> ResubscribeAsync(MessageListener listener) internal async Task<CallResult> ResubscribeAsync(Subscription subscription)
{ {
if (!_socket.IsOpen) if (!_socket.IsOpen)
return new CallResult<bool>(new UnknownError("Socket is not connected")); return new CallResult(new UnknownError("Socket is not connected"));
return await ApiClient.SubscribeAndWaitAsync(this, listener.Subscription!, listener).ConfigureAwait(false); return await ApiClient.SubscribeAndWaitAsync(this, subscription).ConfigureAwait(false);
} }
/// <summary> /// <summary>

View File

@ -1,7 +1,9 @@
using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects.Sockets; using CryptoExchange.Net.Objects.Sockets;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace CryptoExchange.Net.Sockets namespace CryptoExchange.Net.Sockets
@ -11,6 +13,12 @@ namespace CryptoExchange.Net.Sockets
/// </summary> /// </summary>
public abstract class Subscription public abstract class Subscription
{ {
public int Id { get; set; }
public bool UserSubscription { get; set; }
public bool Confirmed { get; set; }
public bool Closed { get; set; }
/// <summary> /// <summary>
/// Logger /// Logger
/// </summary> /// </summary>
@ -26,6 +34,13 @@ namespace CryptoExchange.Net.Sockets
/// </summary> /// </summary>
public abstract List<string> Identifiers { get; } public abstract List<string> Identifiers { get; }
public CancellationTokenRegistration? CancellationTokenRegistration { get; set; }
/// <summary>
/// Exception event
/// </summary>
public event Action<Exception>? Exception;
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
@ -47,7 +62,8 @@ namespace CryptoExchange.Net.Sockets
/// </summary> /// </summary>
/// <param name="message"></param> /// <param name="message"></param>
/// <returns></returns> /// <returns></returns>
public abstract (bool, CallResult?) MessageMatchesSubRequest(ParsedMessage message); public abstract bool MessageMatchesSubRequest(ParsedMessage message);
public abstract CallResult HandleSubResponse(ParsedMessage message);
/// <summary> /// <summary>
/// Get the unsubscribe object to send when unsubscribing /// Get the unsubscribe object to send when unsubscribing
@ -59,7 +75,8 @@ namespace CryptoExchange.Net.Sockets
/// </summary> /// </summary>
/// <param name="message"></param> /// <param name="message"></param>
/// <returns></returns> /// <returns></returns>
public abstract (bool, CallResult?) MessageMatchesUnsubRequest(ParsedMessage message); public abstract bool MessageMatchesUnsubRequest(ParsedMessage message);
public abstract CallResult HandleUnsubResponse(ParsedMessage message);
/// <summary> /// <summary>
/// Handle the update message /// Handle the update message
@ -67,5 +84,14 @@ namespace CryptoExchange.Net.Sockets
/// <param name="message"></param> /// <param name="message"></param>
/// <returns></returns> /// <returns></returns>
public abstract Task HandleEventAsync(DataEvent<ParsedMessage> message); public abstract Task HandleEventAsync(DataEvent<ParsedMessage> message);
/// <summary>
/// Invoke the exception event
/// </summary>
/// <param name="e"></param>
public void InvokeExceptionHandler(Exception e)
{
Exception?.Invoke(e);
}
} }
} }

View File

@ -24,11 +24,15 @@ namespace CryptoExchange.Net.Sockets
/// <inheritdoc /> /// <inheritdoc />
public override object? GetSubRequest() => null; public override object? GetSubRequest() => null;
/// <inheritdoc /> /// <inheritdoc />
public override (bool, CallResult?) MessageMatchesSubRequest(ParsedMessage message) => throw new NotImplementedException(); public override bool MessageMatchesSubRequest(ParsedMessage message) => throw new NotImplementedException();
/// <inheritdoc />
public override CallResult HandleSubResponse(ParsedMessage message) => throw new NotImplementedException();
/// <inheritdoc /> /// <inheritdoc />
public override object? GetUnsubRequest() => null; public override object? GetUnsubRequest() => null;
/// <inheritdoc /> /// <inheritdoc />
public override (bool, CallResult?) MessageMatchesUnsubRequest(ParsedMessage message) => throw new NotImplementedException(); public override bool MessageMatchesUnsubRequest(ParsedMessage message) => throw new NotImplementedException();
/// <inheritdoc />
public override CallResult HandleUnsubResponse(ParsedMessage message) => throw new NotImplementedException();
} }
} }