WebSocketResponse.cs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739
  1. #if !BESTHTTP_DISABLE_WEBSOCKET && (!UNITY_WEBGL || UNITY_EDITOR)
  2. using System;
  3. using System.IO;
  4. using System.Threading;
  5. using System.Collections.Generic;
  6. using System.Text;
  7. using BestHTTP.Extensions;
  8. using BestHTTP.WebSocket.Frames;
  9. namespace BestHTTP.WebSocket
  10. {
  11. public sealed class WebSocketResponse : HTTPResponse, IHeartbeat, IProtocol
  12. {
  13. /// <summary>
  14. /// Capacity of the RTT buffer where the latencies are kept.
  15. /// </summary>
  16. public static int RTTBufferCapacity = 5;
  17. #region Public Interface
  18. /// <summary>
  19. /// A reference to the original WebSocket instance. Used for accessing extensions.
  20. /// </summary>
  21. public WebSocket WebSocket { get; internal set; }
  22. /// <summary>
  23. /// Called when a Text message received
  24. /// </summary>
  25. public Action<WebSocketResponse, string> OnText;
  26. /// <summary>
  27. /// Called when a Binary message received
  28. /// </summary>
  29. public Action<WebSocketResponse, byte[]> OnBinary;
  30. /// <summary>
  31. /// Called when an incomplete frame received. No attempt will be made to reassemble these fragments.
  32. /// </summary>
  33. public Action<WebSocketResponse, WebSocketFrameReader> OnIncompleteFrame;
  34. /// <summary>
  35. /// Called when the connection closed.
  36. /// </summary>
  37. public Action<WebSocketResponse, UInt16, string> OnClosed;
  38. /// <summary>
  39. /// Indicates whether the connection to the server is closed or not.
  40. /// </summary>
  41. public bool IsClosed { get { return closed; } }
  42. /// <summary>
  43. /// On what frequency we have to send a ping to the server.
  44. /// </summary>
  45. public TimeSpan PingFrequnecy { get; private set; }
  46. /// <summary>
  47. /// Maximum size of a fragment's payload data. Its default value is 32767.
  48. /// </summary>
  49. public UInt16 MaxFragmentSize { get; private set; }
  50. /// <summary>
  51. /// Length of unsent, buffered up data in bytes.
  52. /// </summary>
  53. public int BufferedAmount { get { return this._bufferedAmount; } }
  54. private int _bufferedAmount;
  55. /// <summary>
  56. /// Calculated latency from the Round-Trip Times we store in the rtts field.
  57. /// </summary>
  58. public int Latency { get; private set; }
  59. #endregion
  60. #region Private Fields
  61. private List<WebSocketFrameReader> IncompleteFrames = new List<WebSocketFrameReader>();
  62. private List<WebSocketFrameReader> CompletedFrames = new List<WebSocketFrameReader>();
  63. private List<WebSocketFrameReader> frameCache = new List<WebSocketFrameReader>();
  64. private WebSocketFrameReader CloseFrame;
  65. private object FrameLock = new object();
  66. private object SendLock = new object();
  67. private List<WebSocketFrame> unsentFrames = new List<WebSocketFrame>();
  68. private volatile AutoResetEvent newFrameSignal = new AutoResetEvent(false);
  69. private volatile bool sendThreadCreated = false;
  70. /// <summary>
  71. /// True if we sent out a Close message to the server
  72. /// </summary>
  73. private volatile bool closeSent;
  74. /// <summary>
  75. /// True if this WebSocket connection is closed
  76. /// </summary>
  77. private volatile bool closed;
  78. /// <summary>
  79. /// When we sent out the last ping.
  80. /// </summary>
  81. private DateTime lastPing = DateTime.MinValue;
  82. /// <summary>
  83. /// When we received the last pong.
  84. /// </summary>
  85. private DateTime lastMessage = DateTime.MinValue;
  86. /// <summary>
  87. /// A circular buffer to store the last N rtt times calculated by the pong messages.
  88. /// </summary>
  89. private CircularBuffer<int> rtts = new CircularBuffer<int>(WebSocketResponse.RTTBufferCapacity);
  90. #endregion
  91. internal WebSocketResponse(HTTPRequest request, Stream stream, bool isStreamed, bool isFromCache)
  92. : base(request, stream, isStreamed, isFromCache)
  93. {
  94. base.IsClosedManually = true;
  95. closed = false;
  96. MaxFragmentSize = UInt16.MaxValue / 2;
  97. }
  98. internal void StartReceive()
  99. {
  100. if (IsUpgraded)
  101. {
  102. #if NETFX_CORE
  103. #pragma warning disable 4014
  104. Windows.System.Threading.ThreadPool.RunAsync(ReceiveThreadFunc);
  105. #pragma warning restore 4014
  106. #else
  107. ThreadPool.QueueUserWorkItem(ReceiveThreadFunc);
  108. #endif
  109. }
  110. }
  111. internal void CloseStream()
  112. {
  113. var conn = HTTPManager.GetConnectionWith(this.baseRequest);
  114. if (conn != null)
  115. conn.Abort(HTTPConnectionStates.Closed);
  116. }
  117. #region Public interface for interacting with the server
  118. /// <summary>
  119. /// It will send the given message to the server in one frame.
  120. /// </summary>
  121. public void Send(string message)
  122. {
  123. if (message == null)
  124. throw new ArgumentNullException("message must not be null!");
  125. int count = System.Text.Encoding.UTF8.GetByteCount(message);
  126. byte[] data = VariableSizedBufferPool.Get(count, true);
  127. System.Text.Encoding.UTF8.GetBytes(message, 0, message.Length, data, 0);
  128. var frame = new WebSocketFrame(this.WebSocket, WebSocketFrameTypes.Text, data, 0, (ulong)count, true, true);
  129. if (frame.Data != null && frame.Data.Length > this.MaxFragmentSize)
  130. {
  131. WebSocketFrame[] additionalFrames = frame.Fragment(this.MaxFragmentSize);
  132. lock (SendLock)
  133. {
  134. Send(frame);
  135. if (additionalFrames != null)
  136. for (int i = 0; i < additionalFrames.Length; ++i)
  137. Send(additionalFrames[i]);
  138. }
  139. }
  140. else
  141. Send(frame);
  142. VariableSizedBufferPool.Release(data);
  143. }
  144. /// <summary>
  145. /// It will send the given data to the server in one frame.
  146. /// </summary>
  147. public void Send(byte[] data)
  148. {
  149. if (data == null)
  150. throw new ArgumentNullException("data must not be null!");
  151. WebSocketFrame frame = new WebSocketFrame(this.WebSocket, WebSocketFrameTypes.Binary, data);
  152. if (frame.Data != null && frame.Data.Length > this.MaxFragmentSize)
  153. {
  154. WebSocketFrame[] additionalFrames = frame.Fragment(this.MaxFragmentSize);
  155. lock(SendLock)
  156. {
  157. Send(frame);
  158. if (additionalFrames != null)
  159. for (int i = 0; i < additionalFrames.Length; ++i)
  160. Send(additionalFrames[i]);
  161. }
  162. }
  163. else
  164. Send(frame);
  165. }
  166. /// <summary>
  167. /// Will send count bytes from a byte array, starting from offset.
  168. /// </summary>
  169. public void Send(byte[] data, ulong offset, ulong count)
  170. {
  171. if (data == null)
  172. throw new ArgumentNullException("data must not be null!");
  173. if (offset + count > (ulong)data.Length)
  174. throw new ArgumentOutOfRangeException("offset + count >= data.Length");
  175. WebSocketFrame frame = new WebSocketFrame(this.WebSocket, WebSocketFrameTypes.Binary, data, offset, count, true, true);
  176. if (frame.Data != null && frame.Data.Length > this.MaxFragmentSize)
  177. {
  178. WebSocketFrame[] additionalFrames = frame.Fragment(this.MaxFragmentSize);
  179. lock (SendLock)
  180. {
  181. Send(frame);
  182. if (additionalFrames != null)
  183. for (int i = 0; i < additionalFrames.Length; ++i)
  184. Send(additionalFrames[i]);
  185. }
  186. }
  187. else
  188. Send(frame);
  189. }
  190. /// <summary>
  191. /// It will send the given frame to the server.
  192. /// </summary>
  193. public void Send(WebSocketFrame frame)
  194. {
  195. if (frame == null)
  196. throw new ArgumentNullException("frame is null!");
  197. if (closed || closeSent)
  198. return;
  199. lock (SendLock)
  200. {
  201. this.unsentFrames.Add(frame);
  202. if (!sendThreadCreated)
  203. {
  204. HTTPManager.Logger.Information("WebSocketResponse", "Send - Creating thread");
  205. #if NETFX_CORE
  206. #pragma warning disable 4014
  207. Windows.System.Threading.ThreadPool.RunAsync(SendThreadFunc);
  208. #pragma warning restore 4014
  209. #else
  210. ThreadPool.QueueUserWorkItem(SendThreadFunc);
  211. #endif
  212. sendThreadCreated = true;
  213. }
  214. }
  215. Interlocked.Add(ref this._bufferedAmount, frame.Data != null ? frame.DataLength : 0);
  216. //if (HTTPManager.Logger.Level <= Logger.Loglevels.All)
  217. // HTTPManager.Logger.Information("WebSocketResponse", "Signaling SendThread!");
  218. newFrameSignal.Set();
  219. }
  220. /// <summary>
  221. /// It will send the given frame to the server by inserting the frame into the queue as the first element.
  222. /// </summary>
  223. public void Insert(WebSocketFrame frame)
  224. {
  225. if (frame == null)
  226. throw new ArgumentNullException("frame is null!");
  227. if (closed || closeSent)
  228. return;
  229. lock (SendLock)
  230. {
  231. this.unsentFrames.Insert(0, frame);
  232. if (!sendThreadCreated)
  233. {
  234. HTTPManager.Logger.Information("WebSocketResponse", "Insert - Creating thread");
  235. #if NETFX_CORE
  236. #pragma warning disable 4014
  237. Windows.System.Threading.ThreadPool.RunAsync(SendThreadFunc);
  238. #pragma warning restore 4014
  239. #else
  240. ThreadPool.QueueUserWorkItem(SendThreadFunc);
  241. #endif
  242. sendThreadCreated = true;
  243. }
  244. }
  245. Interlocked.Add(ref this._bufferedAmount, frame.Data != null ? frame.DataLength : 0);
  246. newFrameSignal.Set();
  247. }
  248. public void SendNow(WebSocketFrame frame)
  249. {
  250. if (frame == null)
  251. throw new ArgumentNullException("frame is null!");
  252. if (closed || closeSent)
  253. return;
  254. using (var rawData = frame.Get())
  255. {
  256. Stream.Write(rawData.Data, 0, rawData.Length);
  257. Stream.Flush();
  258. }
  259. }
  260. /// <summary>
  261. /// It will initiate the closing of the connection to the server.
  262. /// </summary>
  263. public void Close()
  264. {
  265. Close(1000, "Bye!");
  266. }
  267. /// <summary>
  268. /// It will initiate the closing of the connection to the server.
  269. /// </summary>
  270. public void Close(UInt16 code, string msg)
  271. {
  272. if (closed)
  273. return;
  274. lock (SendLock)
  275. this.unsentFrames.Clear();
  276. Interlocked.Exchange(ref this._bufferedAmount, 0);
  277. Send(new WebSocketFrame(this.WebSocket, WebSocketFrameTypes.ConnectionClose, WebSocket.EncodeCloseData(code, msg)));
  278. }
  279. public void StartPinging(int frequency)
  280. {
  281. if (frequency < 100)
  282. throw new ArgumentException("frequency must be at least 100 milliseconds!");
  283. PingFrequnecy = TimeSpan.FromMilliseconds(frequency);
  284. lastMessage = DateTime.UtcNow;
  285. SendPing();
  286. HTTPManager.Heartbeats.Subscribe(this);
  287. HTTPUpdateDelegator.OnApplicationForegroundStateChanged += OnApplicationForegroundStateChanged;
  288. }
  289. #endregion
  290. #region Private Threading Functions
  291. private void SendThreadFunc(object param)
  292. {
  293. List<WebSocketFrame> localFrames = new List<WebSocketFrame>();
  294. try
  295. {
  296. while (!closed && !closeSent)
  297. {
  298. //if (HTTPManager.Logger.Level <= Logger.Loglevels.All)
  299. // HTTPManager.Logger.Information("WebSocketResponse", "SendThread - Waiting...");
  300. newFrameSignal.WaitOne();
  301. try
  302. {
  303. lock (SendLock)
  304. {
  305. // add frames int reversed order
  306. for (int i = this.unsentFrames.Count - 1; i >= 0; --i)
  307. localFrames.Add(this.unsentFrames[i]);
  308. this.unsentFrames.Clear();
  309. }
  310. //if (HTTPManager.Logger.Level <= Logger.Loglevels.All)
  311. // HTTPManager.Logger.Information("WebSocketResponse", "SendThread - Wait is over, " + localFrames.Count.ToString() + " new frames!");
  312. while (localFrames.Count > 0)
  313. {
  314. WebSocketFrame frame = localFrames[localFrames.Count - 1];
  315. localFrames.RemoveAt(localFrames.Count - 1);
  316. if (!closeSent)
  317. {
  318. using (var rawData = frame.Get())
  319. {
  320. Stream.Write(rawData.Data, 0, rawData.Length);
  321. }
  322. //if (frame.Type == WebSocketFrameTypes.Text)
  323. VariableSizedBufferPool.Release(frame.Data);
  324. if (frame.Type == WebSocketFrameTypes.ConnectionClose)
  325. closeSent = true;
  326. }
  327. Interlocked.Add(ref this._bufferedAmount, -frame.DataLength);
  328. }
  329. Stream.Flush();
  330. }
  331. catch(Exception ex)
  332. {
  333. if (HTTPUpdateDelegator.IsCreated)
  334. {
  335. this.baseRequest.Exception = ex;
  336. this.baseRequest.State = HTTPRequestStates.Error;
  337. }
  338. else
  339. this.baseRequest.State = HTTPRequestStates.Aborted;
  340. closed = true;
  341. }
  342. }
  343. }
  344. finally
  345. {
  346. sendThreadCreated = false;
  347. (newFrameSignal as IDisposable).Dispose();
  348. newFrameSignal = null;
  349. HTTPManager.Logger.Information("WebSocketResponse", "SendThread - Closed!");
  350. }
  351. }
  352. private void ReceiveThreadFunc(object param)
  353. {
  354. try
  355. {
  356. using (var bufferedStream = new ReadOnlyBufferedStream(this.Stream))
  357. {
  358. while (!closed)
  359. {
  360. try
  361. {
  362. WebSocketFrameReader frame = new WebSocketFrameReader();
  363. frame.Read(bufferedStream);
  364. lastMessage = DateTime.UtcNow;
  365. // A server MUST NOT mask any frames that it sends to the client. A client MUST close a connection if it detects a masked frame.
  366. // In this case, it MAY use the status code 1002 (protocol error)
  367. // (These rules might be relaxed in a future specification.)
  368. if (frame.HasMask)
  369. {
  370. Close(1002, "Protocol Error: masked frame received from server!");
  371. continue;
  372. }
  373. if (!frame.IsFinal)
  374. {
  375. if (OnIncompleteFrame == null)
  376. IncompleteFrames.Add(frame);
  377. else
  378. lock (FrameLock) CompletedFrames.Add(frame);
  379. continue;
  380. }
  381. switch (frame.Type)
  382. {
  383. // For a complete documentation and rules on fragmentation see http://tools.ietf.org/html/rfc6455#section-5.4
  384. // A fragmented Frame's last fragment's opcode is 0 (Continuation) and the FIN bit is set to 1.
  385. case WebSocketFrameTypes.Continuation:
  386. // Do an assemble pass only if OnFragment is not set. Otherwise put it in the CompletedFrames, we will handle it in the HandleEvent phase.
  387. if (OnIncompleteFrame == null)
  388. {
  389. frame.Assemble(IncompleteFrames);
  390. // Remove all incomplete frames
  391. IncompleteFrames.Clear();
  392. // Control frames themselves MUST NOT be fragmented. So, its a normal text or binary frame. Go, handle it as usual.
  393. goto case WebSocketFrameTypes.Binary;
  394. }
  395. else
  396. lock (FrameLock) CompletedFrames.Add(frame);
  397. break;
  398. case WebSocketFrameTypes.Text:
  399. case WebSocketFrameTypes.Binary:
  400. frame.DecodeWithExtensions(WebSocket);
  401. lock (FrameLock) CompletedFrames.Add(frame);
  402. break;
  403. // Upon receipt of a Ping frame, an endpoint MUST send a Pong frame in response, unless it already received a Close frame.
  404. case WebSocketFrameTypes.Ping:
  405. if (!closeSent && !closed)
  406. Send(new WebSocketFrame(this.WebSocket, WebSocketFrameTypes.Pong, frame.Data));
  407. break;
  408. case WebSocketFrameTypes.Pong:
  409. try
  410. {
  411. // Get the ticks from the frame's payload
  412. long ticksSent = BitConverter.ToInt64(frame.Data, 0);
  413. // the difference between the current time and the time when the ping message is sent
  414. TimeSpan diff = TimeSpan.FromTicks(lastMessage.Ticks - ticksSent);
  415. // add it to the buffer
  416. this.rtts.Add((int)diff.TotalMilliseconds);
  417. // and calculate the new latency
  418. this.Latency = CalculateLatency();
  419. }
  420. catch
  421. {
  422. // https://tools.ietf.org/html/rfc6455#section-5.5
  423. // A Pong frame MAY be sent unsolicited. This serves as a
  424. // unidirectional heartbeat. A response to an unsolicited Pong frame is
  425. // not expected.
  426. }
  427. break;
  428. // If an endpoint receives a Close frame and did not previously send a Close frame, the endpoint MUST send a Close frame in response.
  429. case WebSocketFrameTypes.ConnectionClose:
  430. CloseFrame = frame;
  431. if (!closeSent)
  432. Send(new WebSocketFrame(this.WebSocket, WebSocketFrameTypes.ConnectionClose, null));
  433. closed = true;
  434. break;
  435. }
  436. }
  437. #if !NETFX_CORE
  438. catch (ThreadAbortException)
  439. {
  440. IncompleteFrames.Clear();
  441. this.baseRequest.State = HTTPRequestStates.Aborted;
  442. closed = true;
  443. newFrameSignal.Set();
  444. }
  445. #endif
  446. catch (Exception e)
  447. {
  448. if (HTTPUpdateDelegator.IsCreated)
  449. {
  450. this.baseRequest.Exception = e;
  451. this.baseRequest.State = HTTPRequestStates.Error;
  452. }
  453. else
  454. this.baseRequest.State = HTTPRequestStates.Aborted;
  455. closed = true;
  456. newFrameSignal.Set();
  457. }
  458. }
  459. }
  460. }
  461. finally
  462. {
  463. HTTPManager.Heartbeats.Unsubscribe(this);
  464. HTTPUpdateDelegator.OnApplicationForegroundStateChanged -= OnApplicationForegroundStateChanged;
  465. HTTPManager.Logger.Information("WebSocketResponse", "ReceiveThread - Closed!");
  466. }
  467. }
  468. #endregion
  469. #region Sending Out Events
  470. /// <summary>
  471. /// Internal function to send out received messages.
  472. /// </summary>
  473. void IProtocol.HandleEvents()
  474. {
  475. frameCache.Clear();
  476. lock (FrameLock)
  477. {
  478. frameCache.AddRange(CompletedFrames);
  479. CompletedFrames.Clear();
  480. }
  481. for (int i = 0; i < frameCache.Count; ++i)
  482. {
  483. WebSocketFrameReader frame = frameCache[i];
  484. // Bugs in the clients shouldn't interrupt the code, so we need to try-catch and ignore any exception occurring here
  485. try
  486. {
  487. switch (frame.Type)
  488. {
  489. case WebSocketFrameTypes.Continuation:
  490. if (OnIncompleteFrame != null)
  491. OnIncompleteFrame(this, frame);
  492. break;
  493. case WebSocketFrameTypes.Text:
  494. // Any not Final frame is handled as a fragment
  495. if (!frame.IsFinal)
  496. goto case WebSocketFrameTypes.Continuation;
  497. if (OnText != null)
  498. OnText(this, frame.DataAsText);
  499. break;
  500. case WebSocketFrameTypes.Binary:
  501. // Any not Final frame is handled as a fragment
  502. if (!frame.IsFinal)
  503. goto case WebSocketFrameTypes.Continuation;
  504. if (OnBinary != null)
  505. OnBinary(this, frame.Data);
  506. break;
  507. }
  508. }
  509. catch (Exception ex)
  510. {
  511. HTTPManager.Logger.Exception("WebSocketResponse", "HandleEvents", ex);
  512. }
  513. }
  514. frameCache.Clear();
  515. // 2015.05.09
  516. // State checking added because if there is an error the OnClose called first, and then the OnError.
  517. // Now, when there is an error only the OnError event will be called!
  518. if (IsClosed && OnClosed != null && baseRequest.State == HTTPRequestStates.Processing)
  519. {
  520. try
  521. {
  522. UInt16 statusCode = 0;
  523. string msg = string.Empty;
  524. // If we received any data, we will get the status code and the message from it
  525. if (/*CloseFrame != null && */CloseFrame.Data != null && CloseFrame.Data.Length >= 2)
  526. {
  527. if (BitConverter.IsLittleEndian)
  528. Array.Reverse(CloseFrame.Data, 0, 2);
  529. statusCode = BitConverter.ToUInt16(CloseFrame.Data, 0);
  530. if (CloseFrame.Data.Length > 2)
  531. msg = Encoding.UTF8.GetString(CloseFrame.Data, 2, CloseFrame.Data.Length - 2);
  532. VariableSizedBufferPool.Release(CloseFrame.Data);
  533. }
  534. OnClosed(this, statusCode, msg);
  535. }
  536. catch (Exception ex)
  537. {
  538. HTTPManager.Logger.Exception("WebSocketResponse", "HandleEvents - OnClosed", ex);
  539. }
  540. }
  541. }
  542. #endregion
  543. #region IHeartbeat Implementation
  544. void IHeartbeat.OnHeartbeatUpdate(TimeSpan dif)
  545. {
  546. DateTime now = DateTime.UtcNow;
  547. if (now - lastPing >= PingFrequnecy)
  548. SendPing();
  549. if (now - (lastMessage + this.PingFrequnecy) > this.WebSocket.CloseAfterNoMesssage)
  550. {
  551. HTTPManager.Logger.Warning("WebSocketResponse",
  552. string.Format("No message received in the given time! Closing WebSocket. LastMessage: {0}, PingFrequency: {1}, Close After: {2}, Now: {3}",
  553. this.lastMessage, this.PingFrequnecy, this.WebSocket.CloseAfterNoMesssage, now));
  554. CloseWithError("No message received in the given time!");
  555. }
  556. }
  557. #endregion
  558. private void OnApplicationForegroundStateChanged(bool isPaused)
  559. {
  560. if (!isPaused)
  561. lastMessage = DateTime.UtcNow;
  562. }
  563. private void SendPing()
  564. {
  565. lastPing = DateTime.UtcNow;
  566. try
  567. {
  568. long ticks = DateTime.UtcNow.Ticks;
  569. var ticksBytes = BitConverter.GetBytes(ticks);
  570. var pingFrame = new WebSocketFrame(this.WebSocket, WebSocketFrameTypes.Ping, ticksBytes);
  571. Insert(pingFrame);
  572. }
  573. catch
  574. {
  575. HTTPManager.Logger.Information("WebSocketResponse", "Error while sending PING message! Closing WebSocket.");
  576. CloseWithError("Error while sending PING message!");
  577. }
  578. }
  579. private void CloseWithError(string message)
  580. {
  581. this.baseRequest.Exception = new Exception(message);
  582. this.baseRequest.State = HTTPRequestStates.Error;
  583. this.closed = true;
  584. HTTPManager.Heartbeats.Unsubscribe(this);
  585. HTTPUpdateDelegator.OnApplicationForegroundStateChanged -= OnApplicationForegroundStateChanged;
  586. newFrameSignal.Set();
  587. CloseStream();
  588. }
  589. private int CalculateLatency()
  590. {
  591. if (this.rtts.Count == 0)
  592. return 0;
  593. int sumLatency = 0;
  594. for (int i = 0; i < this.rtts.Count; ++i)
  595. sumLatency += this.rtts[i];
  596. return sumLatency / this.rtts.Count;
  597. }
  598. }
  599. }
  600. #endif