WsClient.cs 17 KB

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