You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

WebSocketSharpClient.cs 6.3 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Security.Authentication;
  5. using System.Text;
  6. using System.Threading;
  7. using System.Threading.Tasks;
  8. using Discord.Net;
  9. using Discord.Net.WebSockets;
  10. using WebSocketSharp;
  11. namespace Discord.Providers.WebSocketSharp
  12. {
  13. /// <summary>
  14. /// WebSocket provider using websocket-sharp.
  15. /// </summary>
  16. internal class WebSocketSharpClient : IWebSocketClient, IDisposable
  17. {
  18. /// <inheritdoc />
  19. public event Func<byte[], int, int, Task> BinaryMessage;
  20. /// <inheritdoc />
  21. public event Func<string, Task> TextMessage;
  22. /// <inheritdoc />
  23. public event Func<Exception, Task> Closed;
  24. private readonly SemaphoreSlim _lock;
  25. private readonly Dictionary<string, string> _headers;
  26. private readonly ManualResetEventSlim _waitUntilConnect;
  27. private WebSocket _client;
  28. private CancellationTokenSource _cancelTokenSource;
  29. private CancellationToken _cancelToken;
  30. private CancellationToken _parentToken;
  31. private bool _isDisposed;
  32. /// <summary>
  33. /// Initializes a new instance of the <see cref="WebSocketSharpProvider"/> class.
  34. /// </summary>
  35. public WebSocketSharpClient()
  36. {
  37. _headers = new Dictionary<string, string>();
  38. _lock = new SemaphoreSlim(1, 1);
  39. _cancelTokenSource = new CancellationTokenSource();
  40. _cancelToken = CancellationToken.None;
  41. _parentToken = CancellationToken.None;
  42. _waitUntilConnect = new ManualResetEventSlim();
  43. }
  44. /// <inheritdoc />
  45. public void SetHeader(string key, string value)
  46. {
  47. _headers[key] = value;
  48. }
  49. /// <inheritdoc />
  50. public void SetCancelToken(CancellationToken cancelToken)
  51. {
  52. _parentToken = cancelToken;
  53. _cancelToken = CancellationTokenSource.CreateLinkedTokenSource
  54. (
  55. _parentToken,
  56. _cancelTokenSource.Token
  57. )
  58. .Token;
  59. }
  60. /// <inheritdoc />
  61. public async Task ConnectAsync(string host)
  62. {
  63. await _lock.WaitAsync().ConfigureAwait(false);
  64. try
  65. {
  66. await ConnectInternalAsync(host).ConfigureAwait(false);
  67. }
  68. finally
  69. {
  70. _lock.Release();
  71. }
  72. }
  73. private async Task ConnectInternalAsync(string host)
  74. {
  75. await DisconnectInternalAsync().ConfigureAwait(false);
  76. _cancelTokenSource = new CancellationTokenSource();
  77. _cancelToken = CancellationTokenSource.CreateLinkedTokenSource
  78. (
  79. _parentToken,
  80. _cancelTokenSource.Token
  81. )
  82. .Token;
  83. _client = new WebSocket(host)
  84. {
  85. CustomHeaders = _headers.ToList()
  86. };
  87. _client.SslConfiguration.EnabledSslProtocols = SslProtocols.Tls12;
  88. _client.OnMessage += OnMessage;
  89. _client.OnOpen += OnConnected;
  90. _client.OnClose += OnClosed;
  91. _client.Connect();
  92. _waitUntilConnect.Wait(_cancelToken);
  93. }
  94. /// <inheritdoc />
  95. public async Task DisconnectAsync()
  96. {
  97. await _lock.WaitAsync().ConfigureAwait(false);
  98. try
  99. {
  100. await DisconnectInternalAsync().ConfigureAwait(false);
  101. }
  102. finally
  103. {
  104. _lock.Release();
  105. }
  106. }
  107. private Task DisconnectInternalAsync()
  108. {
  109. _cancelTokenSource.Cancel();
  110. if (_client is null)
  111. {
  112. return Task.CompletedTask;
  113. }
  114. if (_client.ReadyState == WebSocketState.Open)
  115. {
  116. _client.Close();
  117. }
  118. _client.OnMessage -= OnMessage;
  119. _client.OnOpen -= OnConnected;
  120. _client.OnClose -= OnClosed;
  121. _client = null;
  122. _waitUntilConnect.Reset();
  123. return Task.CompletedTask;
  124. }
  125. private void OnMessage(object sender, MessageEventArgs messageEventArgs)
  126. {
  127. if (messageEventArgs.IsBinary)
  128. {
  129. OnBinaryMessage(messageEventArgs);
  130. }
  131. else if (messageEventArgs.IsText)
  132. {
  133. OnTextMessage(messageEventArgs);
  134. }
  135. }
  136. /// <inheritdoc />
  137. public async Task SendAsync(byte[] data, int index, int count, bool isText)
  138. {
  139. await _lock.WaitAsync(_cancelToken).ConfigureAwait(false);
  140. try
  141. {
  142. if (isText)
  143. {
  144. _client.Send(Encoding.UTF8.GetString(data, index, count));
  145. }
  146. else
  147. {
  148. _client.Send(data.Skip(index).Take(count).ToArray());
  149. }
  150. }
  151. finally
  152. {
  153. _lock.Release();
  154. }
  155. }
  156. private void OnTextMessage(MessageEventArgs e)
  157. {
  158. TextMessage?.Invoke(e.Data).GetAwaiter().GetResult();
  159. }
  160. private void OnBinaryMessage(MessageEventArgs e)
  161. {
  162. BinaryMessage?.Invoke(e.RawData, 0, e.RawData.Length).GetAwaiter().GetResult();
  163. }
  164. private void OnConnected(object sender, EventArgs e)
  165. {
  166. _waitUntilConnect.Set();
  167. }
  168. private void OnClosed(object sender, CloseEventArgs e)
  169. {
  170. if (e.WasClean)
  171. {
  172. Closed?.Invoke(null).GetAwaiter().GetResult();
  173. return;
  174. }
  175. var ex = new WebSocketClosedException(e.Code, e.Reason);
  176. Closed?.Invoke(ex).GetAwaiter().GetResult();
  177. }
  178. /// <inheritdoc />
  179. public void Dispose()
  180. {
  181. if (_isDisposed)
  182. {
  183. return;
  184. }
  185. DisconnectInternalAsync().GetAwaiter().GetResult();
  186. ((IDisposable)_client)?.Dispose();
  187. _client = null;
  188. _isDisposed = true;
  189. }
  190. }
  191. }