WssClient.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. using System;
  2. using System.Net;
  3. using System.Net.Sockets;
  4. using System.Text;
  5. namespace NetCoreServer
  6. {
  7. /// <summary>
  8. /// WebSocket secure client
  9. /// </summary>
  10. /// <remarks>WebSocket secure client is used to communicate with secure WebSocket server. Thread-safe.</remarks>
  11. public class WssClient : HttpsClient, IWebSocket
  12. {
  13. internal readonly WebSocket WebSocket;
  14. /// <summary>
  15. /// Initialize WebSocket client with a given IP address and port number
  16. /// </summary>
  17. /// <param name="context">SSL context</param>
  18. /// <param name="address">IP address</param>
  19. /// <param name="port">Port number</param>
  20. public WssClient(SslContext context, IPAddress address, int port) : base(context, address, port) { WebSocket = new WebSocket(this); }
  21. /// <summary>
  22. /// Initialize WebSocket client with a given IP address and port number
  23. /// </summary>
  24. /// <param name="context">SSL context</param>
  25. /// <param name="address">IP address</param>
  26. /// <param name="port">Port number</param>
  27. public WssClient(SslContext context, string address, int port) : base(context, address, port) { WebSocket = new WebSocket(this); }
  28. /// <summary>
  29. /// Initialize WebSocket client with a given DNS endpoint
  30. /// </summary>
  31. /// <param name="context">SSL context</param>
  32. /// <param name="endpoint">DNS endpoint</param>
  33. public WssClient(SslContext context, DnsEndPoint endpoint) : base(context, endpoint) { WebSocket = new WebSocket(this); }
  34. /// <summary>
  35. /// Initialize WebSocket client with a given IP endpoint
  36. /// </summary>
  37. /// <param name="context">SSL context</param>
  38. /// <param name="endpoint">IP endpoint</param>
  39. public WssClient(SslContext context, IPEndPoint endpoint) : base(context, endpoint) { WebSocket = new WebSocket(this); }
  40. /// <summary>
  41. /// WebSocket random nonce
  42. /// </summary>
  43. public byte[] WsNonce => WebSocket.WsNonce;
  44. #region WebSocket connection methods
  45. public override bool Connect() { _syncConnect = true; return base.Connect(); }
  46. public override bool ConnectAsync() { _syncConnect = false; return base.ConnectAsync(); }
  47. public virtual bool Close() => Close(0, Span<byte>.Empty);
  48. public virtual bool Close(int status) => Close(status, Span<byte>.Empty);
  49. public virtual bool Close(int status, string text) => Close(status, Encoding.UTF8.GetBytes(text));
  50. public virtual bool Close(int status, ReadOnlySpan<char> text) => Close(status, Encoding.UTF8.GetBytes(text.ToArray()));
  51. public virtual bool Close(int status, byte[] buffer) => Close(status, buffer.AsSpan());
  52. public virtual bool Close(int status, byte[] buffer, long offset, long size) => Close(status, buffer.AsSpan((int)offset, (int)size));
  53. public virtual bool Close(int status, ReadOnlySpan<byte> buffer) { SendClose(status, buffer); base.Disconnect(); return true; }
  54. public virtual bool CloseAsync() => CloseAsync(0, Span<byte>.Empty);
  55. public virtual bool CloseAsync(int status) => CloseAsync(status, Span<byte>.Empty);
  56. public virtual bool CloseAsync(int status, string text) => CloseAsync(status, Encoding.UTF8.GetBytes(text));
  57. public virtual bool CloseAsync(int status, ReadOnlySpan<char> text) => CloseAsync(status, Encoding.UTF8.GetBytes(text.ToArray()));
  58. public virtual bool CloseAsync(int status, byte[] buffer) => CloseAsync(status, buffer.AsSpan());
  59. public virtual bool CloseAsync(int status, byte[] buffer, long offset, long size) => CloseAsync(status, buffer.AsSpan((int)offset, (int)size));
  60. public virtual bool CloseAsync(int status, ReadOnlySpan<byte> buffer) { SendClose(status, buffer); base.DisconnectAsync(); return true; }
  61. #endregion
  62. #region WebSocket send text methods
  63. public long SendText(string text) => SendText(Encoding.UTF8.GetBytes(text));
  64. public long SendText(ReadOnlySpan<char> text) => SendText(Encoding.UTF8.GetBytes(text.ToArray()));
  65. public long SendText(byte[] buffer) => SendText(buffer.AsSpan());
  66. public long SendText(byte[] buffer, long offset, long size) => SendText(buffer.AsSpan((int)offset, (int)size));
  67. public long SendText(ReadOnlySpan<byte> buffer)
  68. {
  69. lock (WebSocket.WsSendLock)
  70. {
  71. WebSocket.PrepareSendFrame(WebSocket.WS_FIN | WebSocket.WS_TEXT, true, buffer);
  72. return base.Send(WebSocket.WsSendBuffer.AsSpan());
  73. }
  74. }
  75. public bool SendTextAsync(string text) => SendTextAsync(Encoding.UTF8.GetBytes(text));
  76. public bool SendTextAsync(ReadOnlySpan<char> text) => SendTextAsync(Encoding.UTF8.GetBytes(text.ToArray()));
  77. public bool SendTextAsync(byte[] buffer) => SendTextAsync(buffer.AsSpan());
  78. public bool SendTextAsync(byte[] buffer, long offset, long size) => SendTextAsync(buffer.AsSpan((int)offset, (int)size));
  79. public bool SendTextAsync(ReadOnlySpan<byte> buffer)
  80. {
  81. lock (WebSocket.WsSendLock)
  82. {
  83. WebSocket.PrepareSendFrame(WebSocket.WS_FIN | WebSocket.WS_TEXT, true, buffer);
  84. return base.SendAsync(WebSocket.WsSendBuffer.AsSpan());
  85. }
  86. }
  87. #endregion
  88. #region WebSocket send binary methods
  89. public long SendBinary(string text) => SendBinary(Encoding.UTF8.GetBytes(text));
  90. public long SendBinary(ReadOnlySpan<char> text) => SendBinary(Encoding.UTF8.GetBytes(text.ToArray()));
  91. public long SendBinary(byte[] buffer) => SendBinary(buffer.AsSpan());
  92. public long SendBinary(byte[] buffer, long offset, long size) => SendBinary(buffer.AsSpan((int)offset, (int)size));
  93. public long SendBinary(ReadOnlySpan<byte> buffer)
  94. {
  95. lock (WebSocket.WsSendLock)
  96. {
  97. WebSocket.PrepareSendFrame(WebSocket.WS_FIN | WebSocket.WS_BINARY, true, buffer);
  98. return base.Send(WebSocket.WsSendBuffer.AsSpan());
  99. }
  100. }
  101. public bool SendBinaryAsync(string text) => SendBinaryAsync(Encoding.UTF8.GetBytes(text));
  102. public bool SendBinaryAsync(ReadOnlySpan<char> text) => SendBinaryAsync(Encoding.UTF8.GetBytes(text.ToArray()));
  103. public bool SendBinaryAsync(byte[] buffer) => SendBinaryAsync(buffer.AsSpan());
  104. public bool SendBinaryAsync(byte[] buffer, long offset, long size) => SendBinaryAsync(buffer.AsSpan((int)offset, (int)size));
  105. public bool SendBinaryAsync(ReadOnlySpan<byte> buffer)
  106. {
  107. lock (WebSocket.WsSendLock)
  108. {
  109. WebSocket.PrepareSendFrame(WebSocket.WS_FIN | WebSocket.WS_BINARY, true, buffer);
  110. return base.SendAsync(WebSocket.WsSendBuffer.AsSpan());
  111. }
  112. }
  113. #endregion
  114. #region WebSocket send close methods
  115. public long SendClose(int status, string text) => SendClose(status, Encoding.UTF8.GetBytes(text));
  116. public long SendClose(int status, ReadOnlySpan<char> text) => SendClose(status, Encoding.UTF8.GetBytes(text.ToArray()));
  117. public long SendClose(int status, byte[] buffer) => SendClose(status, buffer.AsSpan());
  118. public long SendClose(int status, byte[] buffer, long offset, long size) => SendClose(status, buffer.AsSpan((int)offset, (int)size));
  119. public long SendClose(int status, ReadOnlySpan<byte> buffer)
  120. {
  121. lock (WebSocket.WsSendLock)
  122. {
  123. WebSocket.PrepareSendFrame(WebSocket.WS_FIN | WebSocket.WS_CLOSE, true, buffer, status);
  124. return base.Send(WebSocket.WsSendBuffer.AsSpan());
  125. }
  126. }
  127. public bool SendCloseAsync(int status, string text) => SendCloseAsync(status, Encoding.UTF8.GetBytes(text));
  128. public bool SendCloseAsync(int status, ReadOnlySpan<char> text) => SendCloseAsync(status, Encoding.UTF8.GetBytes(text.ToArray()));
  129. public bool SendCloseAsync(int status, byte[] buffer) => SendCloseAsync(status, buffer.AsSpan());
  130. public bool SendCloseAsync(int status, byte[] buffer, long offset, long size) => SendCloseAsync(status, buffer.AsSpan((int)offset, (int)size));
  131. public bool SendCloseAsync(int status, ReadOnlySpan<byte> buffer)
  132. {
  133. lock (WebSocket.WsSendLock)
  134. {
  135. WebSocket.PrepareSendFrame(WebSocket.WS_FIN | WebSocket.WS_CLOSE, true, buffer, status);
  136. return base.SendAsync(WebSocket.WsSendBuffer.AsSpan());
  137. }
  138. }
  139. #endregion
  140. #region WebSocket send ping methods
  141. public long SendPing(string text) => SendPing(Encoding.UTF8.GetBytes(text));
  142. public long SendPing(ReadOnlySpan<char> text) => SendPing(Encoding.UTF8.GetBytes(text.ToArray()));
  143. public long SendPing(byte[] buffer) => SendPing(buffer.AsSpan());
  144. public long SendPing(byte[] buffer, long offset, long size) => SendPing(buffer.AsSpan((int)offset, (int)size));
  145. public long SendPing(ReadOnlySpan<byte> buffer)
  146. {
  147. lock (WebSocket.WsSendLock)
  148. {
  149. WebSocket.PrepareSendFrame(WebSocket.WS_FIN | WebSocket.WS_PING, true, buffer);
  150. return base.Send(WebSocket.WsSendBuffer.AsSpan());
  151. }
  152. }
  153. public bool SendPingAsync(string text) => SendPingAsync(Encoding.UTF8.GetBytes(text));
  154. public bool SendPingAsync(ReadOnlySpan<char> text) => SendPingAsync(Encoding.UTF8.GetBytes(text.ToArray()));
  155. public bool SendPingAsync(byte[] buffer) => SendPingAsync(buffer.AsSpan());
  156. public bool SendPingAsync(byte[] buffer, long offset, long size) => SendPingAsync(buffer.AsSpan((int)offset, (int)size));
  157. public bool SendPingAsync(ReadOnlySpan<byte> buffer)
  158. {
  159. lock (WebSocket.WsSendLock)
  160. {
  161. WebSocket.PrepareSendFrame(WebSocket.WS_FIN | WebSocket.WS_PING, true, buffer);
  162. return base.SendAsync(WebSocket.WsSendBuffer.AsSpan());
  163. }
  164. }
  165. #endregion
  166. #region WebSocket send pong methods
  167. public long SendPong(string text) => SendPong(Encoding.UTF8.GetBytes(text));
  168. public long SendPong(ReadOnlySpan<char> text) => SendPong(Encoding.UTF8.GetBytes(text.ToArray()));
  169. public long SendPong(byte[] buffer) => SendPong(buffer.AsSpan());
  170. public long SendPong(byte[] buffer, long offset, long size) => SendPong(buffer.AsSpan((int)offset, (int)size));
  171. public long SendPong(ReadOnlySpan<byte> buffer)
  172. {
  173. lock (WebSocket.WsSendLock)
  174. {
  175. WebSocket.PrepareSendFrame(WebSocket.WS_FIN | WebSocket.WS_PONG, true, buffer);
  176. return base.Send(WebSocket.WsSendBuffer.AsSpan());
  177. }
  178. }
  179. public bool SendPongAsync(string text) => SendPongAsync(Encoding.UTF8.GetBytes(text));
  180. public bool SendPongAsync(ReadOnlySpan<char> text) => SendPongAsync(Encoding.UTF8.GetBytes(text.ToArray()));
  181. public bool SendPongAsync(byte[] buffer) => SendPongAsync(buffer.AsSpan());
  182. public bool SendPongAsync(byte[] buffer, long offset, long size) => SendPongAsync(buffer.AsSpan((int)offset, (int)size));
  183. public bool SendPongAsync(ReadOnlySpan<byte> buffer)
  184. {
  185. lock (WebSocket.WsSendLock)
  186. {
  187. WebSocket.PrepareSendFrame(WebSocket.WS_FIN | WebSocket.WS_PONG, true, buffer);
  188. return base.SendAsync(WebSocket.WsSendBuffer.AsSpan());
  189. }
  190. }
  191. #endregion
  192. #region WebSocket receive methods
  193. public string ReceiveText()
  194. {
  195. var result = new Buffer();
  196. if (!WebSocket.WsHandshaked)
  197. return result.ExtractString(0, result.Data.Length);
  198. var cache = new Buffer();
  199. // Receive WebSocket frame data
  200. while (!WebSocket.WsFinalReceived)
  201. {
  202. while (!WebSocket.WsFrameReceived)
  203. {
  204. long required = WebSocket.RequiredReceiveFrameSize();
  205. cache.Resize(required);
  206. long received = (int)base.Receive(cache.Data, 0, required);
  207. if (received != required)
  208. return result.ExtractString(0, result.Data.Length);
  209. WebSocket.PrepareReceiveFrame(cache.Data, 0, received);
  210. }
  211. if (!WebSocket.WsFinalReceived)
  212. WebSocket.PrepareReceiveFrame(null, 0, 0);
  213. }
  214. // Copy WebSocket frame data
  215. result.Append(WebSocket.WsReceiveFinalBuffer);
  216. WebSocket.PrepareReceiveFrame(null, 0, 0);
  217. return result.ExtractString(0, result.Data.Length);
  218. }
  219. public Buffer ReceiveBinary()
  220. {
  221. var result = new Buffer();
  222. if (!WebSocket.WsHandshaked)
  223. return result;
  224. var cache = new Buffer();
  225. // Receive WebSocket frame data
  226. while (!WebSocket.WsFinalReceived)
  227. {
  228. while (!WebSocket.WsFrameReceived)
  229. {
  230. long required = WebSocket.RequiredReceiveFrameSize();
  231. cache.Resize(required);
  232. long received = (int)base.Receive(cache.Data, 0, required);
  233. if (received != required)
  234. return result;
  235. WebSocket.PrepareReceiveFrame(cache.Data, 0, received);
  236. }
  237. if (!WebSocket.WsFinalReceived)
  238. WebSocket.PrepareReceiveFrame(null, 0, 0);
  239. }
  240. // Copy WebSocket frame data
  241. result.Append(WebSocket.WsReceiveFinalBuffer);
  242. WebSocket.PrepareReceiveFrame(null, 0, 0);
  243. return result;
  244. }
  245. #endregion
  246. #region Session handlers
  247. protected override void OnHandshaked()
  248. {
  249. // Clear WebSocket send/receive buffers
  250. WebSocket.ClearWsBuffers();
  251. // Fill the WebSocket upgrade HTTP request
  252. OnWsConnecting(Request);
  253. // Send the WebSocket upgrade HTTP request
  254. if (_syncConnect)
  255. SendRequest(Request);
  256. else
  257. SendRequestAsync(Request);
  258. }
  259. protected override void OnDisconnecting()
  260. {
  261. if (WebSocket.WsHandshaked)
  262. OnWsDisconnecting();
  263. }
  264. protected override void OnDisconnected()
  265. {
  266. // Disconnect WebSocket
  267. if (WebSocket.WsHandshaked)
  268. {
  269. WebSocket.WsHandshaked = false;
  270. OnWsDisconnected();
  271. }
  272. // Reset WebSocket upgrade HTTP request and response
  273. Request.Clear();
  274. Response.Clear();
  275. // Clear WebSocket send/receive buffers
  276. WebSocket.ClearWsBuffers();
  277. // Initialize new WebSocket random nonce
  278. WebSocket.InitWsNonce();
  279. }
  280. protected override void OnReceived(byte[] buffer, long offset, long size)
  281. {
  282. // Check for WebSocket handshaked status
  283. if (WebSocket.WsHandshaked)
  284. {
  285. // Prepare receive frame
  286. WebSocket.PrepareReceiveFrame(buffer, offset, size);
  287. return;
  288. }
  289. base.OnReceived(buffer, offset, size);
  290. }
  291. protected override void OnReceivedResponseHeader(HttpResponse response)
  292. {
  293. // Check for WebSocket handshaked status
  294. if (WebSocket.WsHandshaked)
  295. return;
  296. // Try to perform WebSocket upgrade
  297. if (!WebSocket.PerformClientUpgrade(response, Id))
  298. {
  299. base.OnReceivedResponseHeader(response);
  300. return;
  301. }
  302. }
  303. protected override void OnReceivedResponse(HttpResponse response)
  304. {
  305. // Check for WebSocket handshaked status
  306. if (WebSocket.WsHandshaked)
  307. {
  308. // Prepare receive frame from the remaining response body
  309. var body = Response.Body;
  310. var data = Encoding.UTF8.GetBytes(body);
  311. WebSocket.PrepareReceiveFrame(data, 0, data.Length);
  312. return;
  313. }
  314. base.OnReceivedResponse(response);
  315. }
  316. protected override void OnReceivedResponseError(HttpResponse response, string error)
  317. {
  318. // Check for WebSocket handshaked status
  319. if (WebSocket.WsHandshaked)
  320. {
  321. OnError(new SocketError());
  322. return;
  323. }
  324. base.OnReceivedResponseError(response, error);
  325. }
  326. #endregion
  327. #region Web socket handlers
  328. public virtual void OnWsConnecting(HttpRequest request) {}
  329. public virtual void OnWsConnected(HttpResponse response) {}
  330. public virtual bool OnWsConnecting(HttpRequest request, HttpResponse response) { return true; }
  331. public virtual void OnWsConnected(HttpRequest request) {}
  332. public virtual void OnWsDisconnecting() {}
  333. public virtual void OnWsDisconnected() {}
  334. public virtual void OnWsReceived(byte[] buffer, long offset, long size) {}
  335. public virtual void OnWsClose(byte[] buffer, long offset, long size, int status = 1000) { CloseAsync(); }
  336. public virtual void OnWsPing(byte[] buffer, long offset, long size) { SendPongAsync(buffer, offset, size); }
  337. public virtual void OnWsPong(byte[] buffer, long offset, long size) {}
  338. public virtual void OnWsError(string error) { OnError(SocketError.SocketError); }
  339. public virtual void OnWsError(SocketError error) { OnError(error); }
  340. #endregion
  341. // Sync connect flag
  342. private bool _syncConnect;
  343. }
  344. }