SocketManager.cs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759
  1. #if !BESTHTTP_DISABLE_SOCKETIO
  2. using System;
  3. using System.Collections.Generic;
  4. using BestHTTP.SocketIO3.Transports;
  5. using BestHTTP.Extensions;
  6. using BestHTTP.SocketIO3.Parsers;
  7. using BestHTTP.SocketIO3.Events;
  8. using BestHTTP.Logger;
  9. using BestHTTP.PlatformSupport.Memory;
  10. namespace BestHTTP.SocketIO3
  11. {
  12. public sealed class SocketManager : IHeartbeat, IManager
  13. {
  14. /// <summary>
  15. /// Possible states of a SocketManager instance.
  16. /// </summary>
  17. public enum States
  18. {
  19. /// <summary>
  20. /// Initial state of the SocketManager
  21. /// </summary>
  22. Initial,
  23. /// <summary>
  24. /// The SocketManager is currently opening.
  25. /// </summary>
  26. Opening,
  27. /// <summary>
  28. /// The SocketManager is open, events can be sent to the server.
  29. /// </summary>
  30. Open,
  31. /// <summary>
  32. /// Paused for transport upgrade
  33. /// </summary>
  34. Paused,
  35. /// <summary>
  36. /// An error occurred, the SocketManager now trying to connect again to the server.
  37. /// </summary>
  38. Reconnecting,
  39. /// <summary>
  40. /// The SocketManager is closed, initiated by the user or by the server
  41. /// </summary>
  42. Closed
  43. }
  44. /// <summary>
  45. /// Supported Socket.IO protocol version
  46. /// </summary>
  47. public int ProtocolVersion { get { return 4; } }
  48. #region Public Properties
  49. /// <summary>
  50. /// The current state of this Socket.IO manager.
  51. /// </summary>
  52. public States State { get { return state; } private set { PreviousState = state; state = value; } }
  53. private States state;
  54. /// <summary>
  55. /// The SocketOptions instance that this manager will use.
  56. /// </summary>
  57. public SocketOptions Options { get; private set; }
  58. /// <summary>
  59. /// The Uri to the Socket.IO endpoint.
  60. /// </summary>
  61. public Uri Uri { get; private set; }
  62. /// <summary>
  63. /// The server sent and parsed Handshake data.
  64. /// </summary>
  65. public HandshakeData Handshake { get; private set; }
  66. /// <summary>
  67. /// The currently used main transport instance.
  68. /// </summary>
  69. public ITransport Transport { get; private set; }
  70. /// <summary>
  71. /// The Request counter for request-based transports.
  72. /// </summary>
  73. public ulong RequestCounter { get; internal set; }
  74. /// <summary>
  75. /// The root("/") Socket.
  76. /// </summary>
  77. public Socket Socket { get { return GetSocket(); } }
  78. /// <summary>
  79. /// Indexer to access socket associated to the given namespace.
  80. /// </summary>
  81. public Socket this[string nsp] { get { return GetSocket(nsp); } }
  82. /// <summary>
  83. /// How many reconnect attempts made.
  84. /// </summary>
  85. public int ReconnectAttempts { get; private set; }
  86. /// <summary>
  87. /// Parser to encode and decode messages and create strongly typed objects.
  88. /// </summary>
  89. public IParser Parser { get; set; }
  90. /// <summary>
  91. /// Logging context of this socket.io connection.
  92. /// </summary>
  93. public LoggingContext Context { get; private set; }
  94. /// <summary>
  95. /// Called for every packet received from the server.
  96. /// </summary>
  97. public Action<SocketManager, IncomingPacket> OnIncomingPacket;
  98. #endregion
  99. #region Internal Properties
  100. /// <summary>
  101. /// Timestamp support to the request based transports.
  102. /// </summary>
  103. internal UInt64 Timestamp { get { return (UInt64)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalMilliseconds; } }
  104. /// <summary>
  105. /// Auto-incrementing property to return Ack ids.
  106. /// </summary>
  107. internal int NextAckId { get { return System.Threading.Interlocked.Increment(ref nextAckId); } }
  108. private int nextAckId;
  109. /// <summary>
  110. /// Internal property to store the previous state of the manager.
  111. /// </summary>
  112. internal States PreviousState { get; private set; }
  113. /// <summary>
  114. /// Transport currently upgrading.
  115. /// </summary>
  116. internal ITransport UpgradingTransport { get; set; }
  117. #endregion
  118. #region Privates
  119. /// <summary>
  120. /// Namespace name -> Socket mapping
  121. /// </summary>
  122. private Dictionary<string, Socket> Namespaces = new Dictionary<string, Socket>();
  123. /// <summary>
  124. /// List of the sockets to able to iterate over them easily.
  125. /// </summary>
  126. private List<Socket> Sockets = new List<Socket>();
  127. /// <summary>
  128. /// List of unsent packets. Only instantiated when we have to use it.
  129. /// </summary>
  130. private List<OutgoingPacket> OfflinePackets;
  131. /// <summary>
  132. /// When we sent out the last heartbeat(Ping) message.
  133. /// </summary>
  134. private DateTime LastHeartbeat = DateTime.MinValue;
  135. /// <summary>
  136. /// When we have to try to do a reconnect attempt
  137. /// </summary>
  138. private DateTime ReconnectAt;
  139. /// <summary>
  140. /// When we started to connect to the server.
  141. /// </summary>
  142. private DateTime ConnectionStarted;
  143. /// <summary>
  144. /// Private flag to avoid multiple Close call
  145. /// </summary>
  146. private bool closing;
  147. /// <summary>
  148. /// In Engine.io v4 / socket.io v3 the server sends the ping messages, not the client.
  149. /// </summary>
  150. private DateTime lastPingReceived;
  151. #endregion
  152. #region Constructors
  153. /// <summary>
  154. /// Constructor to create a SocketManager instance that will connect to the given uri.
  155. /// </summary>
  156. public SocketManager(Uri uri)
  157. :this(uri, new DefaultJsonParser(), new SocketOptions())
  158. { }
  159. public SocketManager(Uri uri, IParser parser)
  160. : this(uri, parser, new SocketOptions())
  161. { }
  162. public SocketManager(Uri uri, SocketOptions options)
  163. :this(uri, new DefaultJsonParser(), options)
  164. { }
  165. /// <summary>
  166. /// Constructor to create a SocketManager instance.
  167. /// </summary>
  168. public SocketManager(Uri uri, IParser parser, SocketOptions options)
  169. {
  170. this.Context = new LoggingContext(this);
  171. string path = uri.PathAndQuery;
  172. if (path.Length <= 1)
  173. {
  174. string append;
  175. if (uri.OriginalString[uri.OriginalString.Length - 1] == '/')
  176. append = "socket.io/";
  177. else
  178. append = "/socket.io/";
  179. uri = new Uri(uri.OriginalString + append);
  180. }
  181. this.Uri = uri;
  182. this.Options = options ?? new SocketOptions();
  183. this.State = States.Initial;
  184. this.PreviousState = States.Initial;
  185. this.Parser = parser ?? new DefaultJsonParser();
  186. #if !BESTHTTP_DISABLE_WEBSOCKET
  187. if (uri.Scheme.StartsWith("ws"))
  188. options.ConnectWith = TransportTypes.WebSocket;
  189. #endif
  190. }
  191. #endregion
  192. /// <summary>
  193. /// Returns with the "/" namespace, the same as the Socket property.
  194. /// </summary>
  195. public Socket GetSocket()
  196. {
  197. return GetSocket("/");
  198. }
  199. /// <summary>
  200. /// Returns with the specified namespace
  201. /// </summary>
  202. public Socket GetSocket(string nsp)
  203. {
  204. if (string.IsNullOrEmpty(nsp))
  205. throw new ArgumentNullException("Namespace parameter is null or empty!");
  206. /*if (nsp[0] != '/')
  207. nsp = "/" + nsp;*/
  208. Socket socket = null;
  209. if (!Namespaces.TryGetValue(nsp, out socket))
  210. {
  211. // No socket found, create one
  212. socket = new Socket(nsp, this);
  213. Namespaces.Add(nsp, socket);
  214. Sockets.Add(socket);
  215. (socket as ISocket).Open();
  216. }
  217. return socket;
  218. }
  219. /// <summary>
  220. /// Internal function to remove a Socket instance from this manager.
  221. /// </summary>
  222. /// <param name="socket"></param>
  223. void IManager.Remove(Socket socket)
  224. {
  225. Namespaces.Remove(socket.Namespace);
  226. Sockets.Remove(socket);
  227. if (Sockets.Count == 0)
  228. Close();
  229. }
  230. #region Connection to the server, and upgrading
  231. /// <summary>
  232. /// This function will begin to open the Socket.IO connection by sending out the handshake request.
  233. /// If the Options' AutoConnect is true, it will be called automatically.
  234. /// </summary>
  235. public void Open()
  236. {
  237. if (State != States.Initial &&
  238. State != States.Closed &&
  239. State != States.Reconnecting)
  240. return;
  241. HTTPManager.Logger.Information("SocketManager", "Opening", this.Context);
  242. ReconnectAt = DateTime.MinValue;
  243. switch (Options.ConnectWith)
  244. {
  245. case TransportTypes.Polling: Transport = new PollingTransport(this); break;
  246. #if !BESTHTTP_DISABLE_WEBSOCKET
  247. case TransportTypes.WebSocket:
  248. Transport = new WebSocketTransport(this);
  249. break;
  250. #endif
  251. }
  252. Transport.Open();
  253. (this as IManager).EmitEvent("connecting");
  254. State = States.Opening;
  255. ConnectionStarted = DateTime.UtcNow;
  256. HTTPManager.Heartbeats.Subscribe(this);
  257. // The root namespace will be opened by default
  258. //GetSocket("/");
  259. }
  260. /// <summary>
  261. /// Closes this Socket.IO connection.
  262. /// </summary>
  263. public void Close()
  264. {
  265. (this as IManager).Close(true);
  266. }
  267. /// <summary>
  268. /// Closes this Socket.IO connection.
  269. /// </summary>
  270. void IManager.Close(bool removeSockets)
  271. {
  272. if (State == States.Closed || closing)
  273. return;
  274. closing = true;
  275. HTTPManager.Logger.Information("SocketManager", "Closing", this.Context);
  276. HTTPManager.Heartbeats.Unsubscribe(this);
  277. // Disconnect the sockets. The Disconnect function will call the Remove function to remove it from the Sockets list.
  278. if (removeSockets)
  279. while (Sockets.Count > 0)
  280. (Sockets[Sockets.Count - 1] as ISocket).Disconnect(removeSockets);
  281. else
  282. for (int i = 0; i < Sockets.Count; ++i)
  283. (Sockets[i] as ISocket).Disconnect(removeSockets);
  284. // Set to Closed after Socket's Disconnect. This way we can send the disconnect events to the server.
  285. State = States.Closed;
  286. LastHeartbeat = DateTime.MinValue;
  287. lastPingReceived = DateTime.MinValue;
  288. if (removeSockets && OfflinePackets != null)
  289. {
  290. foreach (var packet in OfflinePackets)
  291. BufferPool.Release(packet.PayloadData);
  292. OfflinePackets.Clear();
  293. }
  294. // Remove the references from the dictionary too.
  295. if (removeSockets)
  296. Namespaces.Clear();
  297. Handshake = null;
  298. if (Transport != null)
  299. Transport.Close();
  300. Transport = null;
  301. if (UpgradingTransport != null)
  302. UpgradingTransport.Close();
  303. UpgradingTransport = null;
  304. closing = false;
  305. }
  306. /// <summary>
  307. /// Called from a ITransport implementation when an error occurs and we may have to try to reconnect.
  308. /// </summary>
  309. void IManager.TryToReconnect()
  310. {
  311. if (State == States.Reconnecting ||
  312. State == States.Closed)
  313. return;
  314. if (!Options.Reconnection || HTTPManager.IsQuitting)
  315. {
  316. Close();
  317. return;
  318. }
  319. if (++ReconnectAttempts >= Options.ReconnectionAttempts)
  320. {
  321. (this as IManager).EmitEvent("reconnect_failed");
  322. Close();
  323. return;
  324. }
  325. Random rand = new Random();
  326. int delay = (int)Options.ReconnectionDelay.TotalMilliseconds * ReconnectAttempts;
  327. ReconnectAt = DateTime.UtcNow +
  328. TimeSpan.FromMilliseconds(Math.Min(rand.Next(/*rand min:*/(int)(delay - (delay * Options.RandomizationFactor)),
  329. /*rand max:*/(int)(delay + (delay * Options.RandomizationFactor))),
  330. (int)Options.ReconnectionDelayMax.TotalMilliseconds));
  331. (this as IManager).Close(false);
  332. State = States.Reconnecting;
  333. for (int i = 0; i < Sockets.Count; ++i)
  334. (Sockets[i] as ISocket).Open();
  335. // In the Close() function we unregistered
  336. HTTPManager.Heartbeats.Subscribe(this);
  337. HTTPManager.Logger.Information("SocketManager", "Reconnecting", this.Context);
  338. }
  339. /// <summary>
  340. /// Called by transports when they are connected to the server.
  341. /// </summary>
  342. bool IManager.OnTransportConnected(ITransport trans)
  343. {
  344. HTTPManager.Logger.Information("SocketManager", string.Format("OnTransportConnected State: {0}, PreviousState: {1}, Current Transport: {2}, Upgrading Transport: {3}", this.State, this.PreviousState, trans.Type, UpgradingTransport != null ? UpgradingTransport.Type.ToString() : "null"), this.Context);
  345. if (State != States.Opening)
  346. return false;
  347. if (PreviousState == States.Reconnecting)
  348. (this as IManager).EmitEvent("reconnect");
  349. State = States.Open;
  350. if (PreviousState == States.Reconnecting)
  351. (this as IManager).EmitEvent("reconnect_before_offline_packets");
  352. for (int i = 0; i < Sockets.Count; ++i)
  353. {
  354. var socket = Sockets[i];
  355. if (socket != null)
  356. socket.OnTransportOpen();
  357. }
  358. ReconnectAttempts = 0;
  359. // Send out packets that we collected while there were no available transport.
  360. SendOfflinePackets();
  361. #if !BESTHTTP_DISABLE_WEBSOCKET
  362. // Can we upgrade to WebSocket transport?
  363. if (Transport.Type != TransportTypes.WebSocket &&
  364. Handshake.Upgrades.Contains("websocket"))
  365. {
  366. UpgradingTransport = new WebSocketTransport(this);
  367. UpgradingTransport.Open();
  368. }
  369. #endif
  370. return true;
  371. }
  372. void IManager.OnTransportError(ITransport trans, string err)
  373. {
  374. if (UpgradingTransport != null && trans != UpgradingTransport)
  375. return;
  376. (this as IManager).EmitError(err);
  377. trans.Close();
  378. (this as IManager).TryToReconnect();
  379. }
  380. void IManager.OnTransportProbed(ITransport trans)
  381. {
  382. HTTPManager.Logger.Information("SocketManager", "\"probe\" packet received", this.Context);
  383. // If we have to reconnect, we will go straight with the transport we were able to upgrade
  384. Options.ConnectWith = trans.Type;
  385. // Pause ourself to wait for any send and receive turn to finish.
  386. State = States.Paused;
  387. }
  388. #endregion
  389. #region Packet Handling
  390. /// <summary>
  391. /// Select the best transport to send out packets.
  392. /// </summary>
  393. private ITransport SelectTransport()
  394. {
  395. if (State != States.Open || Transport == null)
  396. return null;
  397. return Transport.IsRequestInProgress ? null : Transport;
  398. }
  399. /// <summary>
  400. /// Will select the best transport and sends out all packets that are in the OfflinePackets list.
  401. /// </summary>
  402. private void SendOfflinePackets()
  403. {
  404. ITransport trans = SelectTransport();
  405. // Send out packets that we not sent while no transport was available.
  406. // This function is called before the event handlers get the 'connected' event, so
  407. // theoretically the packet orders are remains.
  408. if (OfflinePackets != null && OfflinePackets.Count > 0 && trans != null)
  409. {
  410. trans.Send(OfflinePackets);
  411. OfflinePackets.Clear();
  412. }
  413. }
  414. /// <summary>
  415. /// Internal function that called from the Socket class. It will send out the packet instantly, or if no transport is available it will store
  416. /// the packet in the OfflinePackets list.
  417. /// </summary>
  418. void IManager.SendPacket(OutgoingPacket packet)
  419. {
  420. if (HTTPManager.Logger.Level <= Loglevels.Information)
  421. HTTPManager.Logger.Information("SocketManager", "SendPacket " + packet.ToString(), this.Context);
  422. ITransport trans = SelectTransport();
  423. if (trans != null)
  424. {
  425. try
  426. {
  427. trans.Send(packet);
  428. }
  429. catch(Exception ex)
  430. {
  431. (this as IManager).EmitError(ex.Message + " " + ex.StackTrace);
  432. }
  433. }
  434. else
  435. {
  436. if (packet.IsVolatile)
  437. {
  438. BufferPool.Release(packet.PayloadData);
  439. return;
  440. }
  441. HTTPManager.Logger.Information("SocketManager", "SendPacket - Offline stashing packet", this.Context);
  442. if (OfflinePackets == null)
  443. OfflinePackets = new List<OutgoingPacket>();
  444. // The same packet can be sent through multiple Sockets.
  445. OfflinePackets.Add(packet);
  446. }
  447. }
  448. /// <summary>
  449. /// Called from the currently operating Transport. Will pass forward to the Socket that has to call the callbacks.
  450. /// </summary>
  451. void IManager.OnPacket(IncomingPacket packet)
  452. {
  453. if (State == States.Closed)
  454. {
  455. HTTPManager.Logger.Information("SocketManager", "OnPacket - State == States.Closed", this.Context);
  456. return;
  457. }
  458. try
  459. {
  460. this.OnIncomingPacket?.Invoke(this, packet);
  461. }
  462. catch (Exception ex)
  463. {
  464. HTTPManager.Logger.Exception(nameof(SocketManager), $"{nameof(this.OnIncomingPacket)}", ex, this.Context);
  465. }
  466. switch (packet.TransportEvent)
  467. {
  468. case TransportEventTypes.Open:
  469. if (Handshake == null)
  470. {
  471. Handshake = packet.DecodedArg as HandshakeData;
  472. (this as IManager).OnTransportConnected(Transport);
  473. return;
  474. }
  475. else
  476. HTTPManager.Logger.Information("SocketManager", "OnPacket - Already received handshake data!", this.Context);
  477. break;
  478. case TransportEventTypes.Ping:
  479. lastPingReceived = DateTime.UtcNow;
  480. //IncomingPacket pingPacket = new Packet(TransportEventTypes.Pong, SocketIOEventTypes.Unknown, "/", 0);
  481. (this as IManager).SendPacket(this.Parser.CreateOutgoing(TransportEventTypes.Pong, null));
  482. break;
  483. case TransportEventTypes.Pong: break;
  484. }
  485. Socket socket = null;
  486. if (Namespaces.TryGetValue(packet.Namespace, out socket))
  487. (socket as ISocket).OnPacket(packet);
  488. else if (packet.TransportEvent == TransportEventTypes.Message)
  489. HTTPManager.Logger.Warning("SocketManager", "Namespace \"" + packet.Namespace + "\" not found!", this.Context);
  490. }
  491. #endregion
  492. /// <summary>
  493. /// Sends an event to all available namespaces.
  494. /// </summary>
  495. public void EmitAll(string eventName, params object[] args)
  496. {
  497. for (int i = 0; i < Sockets.Count; ++i)
  498. Sockets[i].Emit(eventName, args);
  499. }
  500. /// <summary>
  501. /// Emits an internal packet-less event to the root namespace without creating it if it isn't exists yet.
  502. /// </summary>
  503. void IManager.EmitEvent(string eventName, params object[] args)
  504. {
  505. Socket socket = null;
  506. if (Namespaces.TryGetValue("/", out socket))
  507. (socket as ISocket).EmitEvent(eventName, args);
  508. }
  509. /// <summary>
  510. /// Emits an internal packet-less event to the root namespace without creating it if it isn't exists yet.
  511. /// </summary>
  512. void IManager.EmitEvent(SocketIOEventTypes type, params object[] args)
  513. {
  514. (this as IManager).EmitEvent(EventNames.GetNameFor(type), args);
  515. }
  516. void IManager.EmitError(string msg)
  517. {
  518. var outcoming = this.Parser.CreateOutgoing(this.Sockets[0], SocketIOEventTypes.Error, -1, null, new Error(msg));
  519. IncomingPacket inc = IncomingPacket.Empty;
  520. if (outcoming.IsBinary)
  521. inc = this.Parser.Parse(this, outcoming.PayloadData);
  522. else
  523. inc = this.Parser.Parse(this, outcoming.Payload);
  524. (this as IManager).EmitEvent(SocketIOEventTypes.Error, inc.DecodedArg ?? inc.DecodedArgs);
  525. }
  526. void IManager.EmitAll(string eventName, params object[] args)
  527. {
  528. for (int i = 0; i < Sockets.Count; ++i)
  529. (Sockets[i] as ISocket).EmitEvent(eventName, args);
  530. }
  531. #region IHeartbeat Implementation
  532. /// <summary>
  533. /// Called from the HTTPManager's OnUpdate function every frame. It's main function is to send out heartbeat messages.
  534. /// </summary>
  535. void IHeartbeat.OnHeartbeatUpdate(TimeSpan dif)
  536. {
  537. switch (State)
  538. {
  539. case States.Paused:
  540. // To ensure no messages are lost, the upgrade packet will only be sent once all the buffers of the existing transport are flushed and the transport is considered paused.
  541. if (!Transport.IsRequestInProgress &&
  542. !Transport.IsPollingInProgress)
  543. {
  544. State = States.Open;
  545. // Close the current transport
  546. Transport.Close();
  547. // and switch to the newly upgraded one
  548. Transport = UpgradingTransport;
  549. UpgradingTransport = null;
  550. // We will send an Upgrade("5") packet.
  551. Transport.Send(this.Parser.CreateOutgoing(TransportEventTypes.Upgrade, null));
  552. goto case States.Open;
  553. }
  554. break;
  555. case States.Opening:
  556. if (DateTime.UtcNow - ConnectionStarted >= Options.Timeout)
  557. {
  558. (this as IManager).EmitError("Connection timed out!");
  559. (this as IManager).EmitEvent("connect_error");
  560. (this as IManager).EmitEvent("connect_timeout");
  561. (this as IManager).TryToReconnect();
  562. }
  563. break;
  564. case States.Reconnecting:
  565. if (ReconnectAt != DateTime.MinValue && DateTime.UtcNow >= ReconnectAt)
  566. {
  567. (this as IManager).EmitEvent("reconnect_attempt");
  568. (this as IManager).EmitEvent("reconnecting");
  569. Open();
  570. }
  571. break;
  572. case States.Open:
  573. ITransport trans = null;
  574. // Select transport to use
  575. if (Transport != null && Transport.State == TransportStates.Open)
  576. trans = Transport;
  577. // not yet open?
  578. if (trans == null || trans.State != TransportStates.Open)
  579. return;
  580. // Start to poll the server for events
  581. trans.Poll();
  582. // Start to send out unsent packets
  583. SendOfflinePackets();
  584. // First time we reached this point. Set the LastHeartbeat to the current time, 'cause we are just opened.
  585. if (LastHeartbeat == DateTime.MinValue)
  586. {
  587. LastHeartbeat = DateTime.UtcNow;
  588. lastPingReceived = DateTime.UtcNow;
  589. return;
  590. }
  591. if (DateTime.UtcNow - lastPingReceived > TimeSpan.FromMilliseconds(Handshake.PingInterval + Handshake.PingTimeout))
  592. (this as IManager).TryToReconnect();
  593. break; // case States.Open:
  594. }
  595. }
  596. #endregion
  597. }
  598. }
  599. #endif