123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742 |
- using System;
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.Net.Http;
- using System.Net.WebSockets;
- using System.Threading;
- using System.Threading.Tasks;
- using SocketIOClient.JsonSerializer;
- using SocketIOClient.Messages;
- using SocketIOClient.Transport;
- using SocketIOClient.UriConverters;
- namespace SocketIOClient
- {
- /// <summary>
- /// socket.io client class
- /// </summary>
- public class SocketIO : IDisposable
- {
- /// <summary>
- /// Create SocketIO object with default options
- /// </summary>
- /// <param name="uri"></param>
- public SocketIO(string uri) : this(new Uri(uri)) { }
- /// <summary>
- /// Create SocketIO object with options
- /// </summary>
- /// <param name="uri"></param>
- public SocketIO(Uri uri) : this(uri, new SocketIOOptions()) { }
- /// <summary>
- /// Create SocketIO object with options
- /// </summary>
- /// <param name="uri"></param>
- /// <param name="options"></param>
- public SocketIO(string uri, SocketIOOptions options) : this(new Uri(uri), options) { }
- /// <summary>
- /// Create SocketIO object with options
- /// </summary>
- /// <param name="uri"></param>
- /// <param name="options"></param>
- public SocketIO(Uri uri, SocketIOOptions options)
- {
- ServerUri = uri ?? throw new ArgumentNullException("uri");
- Options = options ?? throw new ArgumentNullException("options");
- Initialize();
- }
- Uri _serverUri;
- public Uri ServerUri
- {
- get => _serverUri;
- set
- {
- if (_serverUri != value)
- {
- _serverUri = value;
- if (value != null && value.AbsolutePath != "/")
- {
- Namespace = value.AbsolutePath;
- }
- }
- }
- }
- /// <summary>
- /// An unique identifier for the socket session. Set after the connect event is triggered, and updated after the reconnect event.
- /// </summary>
- public string Id { get; set; }
- public string Namespace { get; private set; }
- /// <summary>
- /// Whether or not the socket is connected to the server.
- /// </summary>
- public bool Connected { get; private set; }
- /// <summary>
- /// Gets current attempt of reconnection.
- /// </summary>
- public int Attempts { get; private set; }
- /// <summary>
- /// Whether or not the socket is disconnected from the server.
- /// </summary>
- public bool Disconnected => !Connected;
- public SocketIOOptions Options { get; }
- public IJsonSerializer JsonSerializer { get; set; }
- public IUriConverter UriConverter { get; set; }
- public HttpClient HttpClient { get; set; }
- public Func<IClientWebSocket> ClientWebSocketProvider { get; set; }
- private IClientWebSocket _clientWebsocket;
- public BaseTransport _transport;
- List<Type> _expectedExceptions;
- int _packetId;
- bool _isConnectCoreRunning;
- Uri _realServerUri;
- Exception _connectCoreException;
- Dictionary<int, Action<SocketIOResponse>> _ackHandlers;
- List<OnAnyHandler> _onAnyHandlers;
- Dictionary<string, Action<SocketIOResponse>> _eventHandlers;
- CancellationTokenSource _connectionTokenSource;
- double _reconnectionDelay;
- #region Socket.IO event
- public event EventHandler OnConnected;
- //public event EventHandler<string> OnConnectError;
- //public event EventHandler<string> OnConnectTimeout;
- public event EventHandler<string> OnError;
- public event EventHandler<string> OnDisconnected;
- /// <summary>
- /// Fired upon a successful reconnection.
- /// </summary>
- public event EventHandler<int> OnReconnected;
- /// <summary>
- /// Fired upon an attempt to reconnect.
- /// </summary>
- public event EventHandler<int> OnReconnectAttempt;
- /// <summary>
- /// Fired upon a reconnection attempt error.
- /// </summary>
- public event EventHandler<Exception> OnReconnectError;
- /// <summary>
- /// Fired when couldn’t reconnect within reconnectionAttempts
- /// </summary>
- public event EventHandler OnReconnectFailed;
- public event EventHandler OnPing;
- public event EventHandler<TimeSpan> OnPong;
- #endregion
- #region Observable Event
- //Subject<Unit> _onConnected;
- //public IObservable<Unit> ConnectedObservable { get; private set; }
- #endregion
- private void Initialize()
- {
- _packetId = -1;
- _ackHandlers = new Dictionary<int, Action<SocketIOResponse>>();
- _eventHandlers = new Dictionary<string, Action<SocketIOResponse>>();
- _onAnyHandlers = new List<OnAnyHandler>();
- JsonSerializer = new SystemTextJsonSerializer();
- UriConverter = new UriConverter();
- HttpClient = new HttpClient();
- ClientWebSocketProvider = () => new SystemNetWebSocketsClientWebSocket(Options.EIO);
- _expectedExceptions = new List<Type>
- {
- typeof(TimeoutException),
- typeof(WebSocketException),
- typeof(HttpRequestException),
- typeof(OperationCanceledException),
- typeof(TaskCanceledException)
- };
- }
- private async Task CreateTransportAsync()
- {
- Options.Transport = await GetProtocolAsync();
- if (Options.Transport == TransportProtocol.Polling)
- {
- HttpPollingHandler handler;
- if (Options.EIO == 3)
- handler = new Eio3HttpPollingHandler(HttpClient);
- else
- handler = new Eio4HttpPollingHandler(HttpClient);
- _transport = new HttpTransport(HttpClient, handler, Options, JsonSerializer);
- }
- else
- {
- _clientWebsocket = ClientWebSocketProvider();
- _transport = new WebSocketTransport(_clientWebsocket, Options, JsonSerializer);
- }
- _transport.Namespace = Namespace;
- SetHeaders();
- }
- private void SetHeaders()
- {
- if (Options.ExtraHeaders != null)
- {
- foreach (var item in Options.ExtraHeaders)
- {
- _transport.AddHeader(item.Key, item.Value);
- }
- }
- }
- private void SyncExceptionToMain(Exception e)
- {
- _connectCoreException = e;
- _isConnectCoreRunning = false;
- }
- private void ConnectCore()
- {
- DisposeForReconnect();
- _reconnectionDelay = Options.ReconnectionDelay;
- _connectionTokenSource = new CancellationTokenSource();
- var cct = _connectionTokenSource.Token;
- _isConnectCoreRunning = true;
- _connectCoreException = null;
- Task.Factory.StartNew(async () =>
- {
- while (true)
- {
- _clientWebsocket?.Dispose();
- _transport?.Dispose();
- CreateTransportAsync().Wait();
- _realServerUri = UriConverter.GetServerUri(Options.Transport == TransportProtocol.WebSocket, ServerUri, Options.EIO, Options.Path, Options.Query);
- try
- {
- if (cct.IsCancellationRequested)
- break;
- if (Attempts > 0)
- OnReconnectAttempt?.Invoke(this, Attempts);
- var timeoutCts = new CancellationTokenSource(Options.ConnectionTimeout);
- _transport.Subscribe(OnMessageReceived, OnErrorReceived);
- await _transport.ConnectAsync(_realServerUri, timeoutCts.Token).ConfigureAwait(false);
- break;
- }
- catch (Exception e)
- {
- if (_expectedExceptions.Contains(e.GetType()))
- {
- if (!Options.Reconnection)
- {
- SyncExceptionToMain(e);
- throw;
- }
- if (Attempts > 0)
- {
- OnReconnectError?.Invoke(this, e);
- }
- Attempts++;
- if (Attempts <= Options.ReconnectionAttempts)
- {
- if (_reconnectionDelay < Options.ReconnectionDelayMax)
- {
- _reconnectionDelay += 2 * Options.RandomizationFactor;
- }
- if (_reconnectionDelay > Options.ReconnectionDelayMax)
- {
- _reconnectionDelay = Options.ReconnectionDelayMax;
- }
- await Task.Delay((int)_reconnectionDelay).ConfigureAwait(false);
- }
- else
- {
- OnReconnectFailed?.Invoke(this, EventArgs.Empty);
- break;
- }
- }
- else
- {
- SyncExceptionToMain(e);
- throw;
- }
- }
- }
- _isConnectCoreRunning = false;
- });
- }
- private async Task<TransportProtocol> GetProtocolAsync()
- {
- if (Options.Transport == TransportProtocol.Polling && Options.AutoUpgrade)
- {
- Uri uri = UriConverter.GetServerUri(false, ServerUri, Options.EIO, Options.Path, Options.Query);
- try
- {
- string text = await HttpClient.GetStringAsync(uri);
- if (text.Contains("websocket"))
- {
- return TransportProtocol.WebSocket;
- }
- }
- catch { }
- }
- return Options.Transport;
- }
- public async Task ConnectAsync()
- {
- ConnectCore();
- while (_isConnectCoreRunning)
- {
- await Task.Delay(20);
- }
- if (_connectCoreException != null)
- {
- throw _connectCoreException;
- }
- }
- private void PingHandler()
- {
- OnPing?.Invoke(this, EventArgs.Empty);
- }
- private void PongHandler(PongMessage msg)
- {
- OnPong?.Invoke(this, msg.Duration);
- }
- private void ConnectedHandler(ConnectedMessage msg)
- {
- Id = msg.Sid;
- Connected = true;
- OnConnected?.Invoke(this, EventArgs.Empty);
- if (Attempts > 0)
- {
- OnReconnected?.Invoke(this, Attempts);
- }
- Attempts = 0;
- }
- private void DisconnectedHandler()
- {
- InvokeDisconnect(DisconnectReason.IOServerDisconnect);
- }
- private void EventMessageHandler(EventMessage m)
- {
- var res = new SocketIOResponse(m.JsonElements, this)
- {
- PacketId = m.Id
- };
- foreach (var item in _onAnyHandlers)
- {
- try
- {
- item(m.Event, res);
- }
- catch (Exception e)
- {
- Debug.WriteLine(e);
- }
- }
- if (_eventHandlers.ContainsKey(m.Event))
- {
- try
- {
- _eventHandlers[m.Event](res);
- }
- catch (Exception e)
- {
- Debug.WriteLine(e);
- }
- }
- }
- private void AckMessageHandler(ClientAckMessage m)
- {
- if (_ackHandlers.ContainsKey(m.Id))
- {
- var res = new SocketIOResponse(m.JsonElements, this);
- try
- {
- _ackHandlers[m.Id](res);
- }
- finally
- {
- _ackHandlers.Remove(m.Id);
- }
- }
- }
- private void ErrorMessageHandler(ErrorMessage msg)
- {
- OnError?.Invoke(this, msg.Message);
- }
- private void BinaryMessageHandler(BinaryMessage msg)
- {
- if (_eventHandlers.ContainsKey(msg.Event))
- {
- try
- {
- var response = new SocketIOResponse(msg.JsonElements, this)
- {
- PacketId = msg.Id
- };
- response.InComingBytes.AddRange(msg.IncomingBytes);
- _eventHandlers[msg.Event](response);
- }
- catch (Exception e)
- {
- Debug.WriteLine(e);
- }
- }
- }
- private void BinaryAckMessageHandler(ClientBinaryAckMessage msg)
- {
- if (_ackHandlers.ContainsKey(msg.Id))
- {
- try
- {
- var response = new SocketIOResponse(msg.JsonElements, this)
- {
- PacketId = msg.Id,
- };
- response.InComingBytes.AddRange(msg.IncomingBytes);
- _ackHandlers[msg.Id](response);
- }
- catch (Exception e)
- {
- Debug.WriteLine(e);
- }
- }
- }
- private void OnErrorReceived(Exception ex)
- {
- InvokeDisconnect(DisconnectReason.TransportClose);
- }
- private void OnMessageReceived(IMessage msg)
- {
- try
- {
- switch (msg.Type)
- {
- case MessageType.Ping:
- PingHandler();
- break;
- case MessageType.Pong:
- PongHandler(msg as PongMessage);
- break;
- case MessageType.Connected:
- ConnectedHandler(msg as ConnectedMessage);
- break;
- case MessageType.Disconnected:
- DisconnectedHandler();
- break;
- case MessageType.EventMessage:
- EventMessageHandler(msg as EventMessage);
- break;
- case MessageType.AckMessage:
- AckMessageHandler(msg as ClientAckMessage);
- break;
- case MessageType.ErrorMessage:
- ErrorMessageHandler(msg as ErrorMessage);
- break;
- case MessageType.BinaryMessage:
- BinaryMessageHandler(msg as BinaryMessage);
- break;
- case MessageType.BinaryAckMessage:
- BinaryAckMessageHandler(msg as ClientBinaryAckMessage);
- break;
- }
- }
- catch (Exception e)
- {
- Debug.WriteLine(e);
- }
- }
- public async Task DisconnectAsync()
- {
- if (Connected)
- {
- var msg = new DisconnectedMessage
- {
- Namespace = Namespace
- };
- try
- {
- await _transport.SendAsync(msg, CancellationToken.None).ConfigureAwait(false);
- }
- catch (Exception e)
- {
- Debug.WriteLine(e);
- }
- InvokeDisconnect(DisconnectReason.IOClientDisconnect);
- }
- }
- /// <summary>
- /// Register a new handler for the given event.
- /// </summary>
- /// <param name="eventName"></param>
- /// <param name="callback"></param>
- public void On(string eventName, Action<SocketIOResponse> callback)
- {
- if (_eventHandlers.ContainsKey(eventName))
- {
- _eventHandlers.Remove(eventName);
- }
- _eventHandlers.Add(eventName, callback);
- }
- /// <summary>
- /// Unregister a new handler for the given event.
- /// </summary>
- /// <param name="eventName"></param>
- public void Off(string eventName)
- {
- if (_eventHandlers.ContainsKey(eventName))
- {
- _eventHandlers.Remove(eventName);
- }
- }
- public void OnAny(OnAnyHandler handler)
- {
- if (handler != null)
- {
- _onAnyHandlers.Add(handler);
- }
- }
- public void PrependAny(OnAnyHandler handler)
- {
- if (handler != null)
- {
- _onAnyHandlers.Insert(0, handler);
- }
- }
- public void OffAny(OnAnyHandler handler)
- {
- if (handler != null)
- {
- _onAnyHandlers.Remove(handler);
- }
- }
- public OnAnyHandler[] ListenersAny() => _onAnyHandlers.ToArray();
- internal async Task ClientAckAsync(int packetId, CancellationToken cancellationToken, params object[] data)
- {
- IMessage msg;
- if (data != null && data.Length > 0)
- {
- var result = JsonSerializer.Serialize(data);
- if (result.Bytes.Count > 0)
- {
- msg = new ServerBinaryAckMessage
- {
- Id = packetId,
- Namespace = Namespace,
- Json = result.Json
- };
- msg.OutgoingBytes = new List<byte[]>(result.Bytes);
- }
- else
- {
- msg = new ServerAckMessage
- {
- Namespace = Namespace,
- Id = packetId,
- Json = result.Json
- };
- }
- }
- else
- {
- msg = new ServerAckMessage
- {
- Namespace = Namespace,
- Id = packetId
- };
- }
- await _transport.SendAsync(msg, cancellationToken).ConfigureAwait(false);
- }
- /// <summary>
- /// Emits an event to the socket
- /// </summary>
- /// <param name="eventName"></param>
- /// <param name="data">Any other parameters can be included. All serializable datastructures are supported, including byte[]</param>
- /// <returns></returns>
- public async Task EmitAsync(string eventName, params object[] data)
- {
- await EmitAsync(eventName, CancellationToken.None, data).ConfigureAwait(false);
- }
- public async Task EmitAsync(string eventName, CancellationToken cancellationToken, params object[] data)
- {
- if (data != null && data.Length > 0)
- {
- var result = JsonSerializer.Serialize(data);
- if (result.Bytes.Count > 0)
- {
- var msg = new BinaryMessage
- {
- Namespace = Namespace,
- OutgoingBytes = new List<byte[]>(result.Bytes),
- Event = eventName,
- Json = result.Json
- };
- await _transport.SendAsync(msg, cancellationToken).ConfigureAwait(false);
- }
- else
- {
- var msg = new EventMessage
- {
- Namespace = Namespace,
- Event = eventName,
- Json = result.Json
- };
- await _transport.SendAsync(msg, cancellationToken).ConfigureAwait(false);
- }
- }
- else
- {
- var msg = new EventMessage
- {
- Namespace = Namespace,
- Event = eventName
- };
- await _transport.SendAsync(msg, cancellationToken).ConfigureAwait(false);
- }
- }
- /// <summary>
- /// Emits an event to the socket
- /// </summary>
- /// <param name="eventName"></param>
- /// <param name="ack">will be called with the server answer.</param>
- /// <param name="data">Any other parameters can be included. All serializable datastructures are supported, including byte[]</param>
- /// <returns></returns>
- public async Task EmitAsync(string eventName, Action<SocketIOResponse> ack, params object[] data)
- {
- await EmitAsync(eventName, CancellationToken.None, ack, data).ConfigureAwait(false);
- }
- public async Task EmitAsync(string eventName, CancellationToken cancellationToken, Action<SocketIOResponse> ack, params object[] data)
- {
- _ackHandlers.Add(++_packetId, ack);
- if (data != null && data.Length > 0)
- {
- var result = JsonSerializer.Serialize(data);
- if (result.Bytes.Count > 0)
- {
- var msg = new ClientBinaryAckMessage
- {
- Event = eventName,
- Namespace = Namespace,
- Json = result.Json,
- Id = _packetId,
- OutgoingBytes = new List<byte[]>(result.Bytes)
- };
- await _transport.SendAsync(msg, cancellationToken).ConfigureAwait(false);
- }
- else
- {
- var msg = new ClientAckMessage
- {
- Event = eventName,
- Namespace = Namespace,
- Id = _packetId,
- Json = result.Json
- };
- await _transport.SendAsync(msg, cancellationToken).ConfigureAwait(false);
- }
- }
- else
- {
- var msg = new ClientAckMessage
- {
- Event = eventName,
- Namespace = Namespace,
- Id = _packetId
- };
- await _transport.SendAsync(msg, cancellationToken).ConfigureAwait(false);
- }
- }
- private async void InvokeDisconnect(string reason)
- {
- if (Connected)
- {
- Connected = false;
- OnDisconnected?.Invoke(this, reason);
- try
- {
- await _transport.DisconnectAsync(CancellationToken.None).ConfigureAwait(false);
- }
- catch { }
- if (reason != DisconnectReason.IOServerDisconnect && reason != DisconnectReason.IOClientDisconnect)
- {
- //In the this cases (explicit disconnection), the client will not try to reconnect and you need to manually call socket.connect().
- if (Options.Reconnection)
- {
- ConnectCore();
- }
- }
- }
- }
- public void AddExpectedException(Type type)
- {
- if (!_expectedExceptions.Contains(type))
- {
- _expectedExceptions.Add(type);
- }
- }
- private void DisposeForReconnect()
- {
- _packetId = -1;
- _ackHandlers.Clear();
- if (_connectionTokenSource != null)
- {
- _connectionTokenSource.Cancel();
- _connectionTokenSource.Dispose();
- }
- }
- public void Dispose()
- {
- HttpClient.Dispose();
- _transport.Dispose();
- _ackHandlers.Clear();
- _onAnyHandlers.Clear();
- _eventHandlers.Clear();
- _connectionTokenSource.Cancel();
- _connectionTokenSource.Dispose();
- }
- }
- }
|