2
2
{
3
3
using AngleSharp . Attributes ;
4
4
using AngleSharp . Dom ;
5
+ using AngleSharp . Dom . Events ;
6
+ using AngleSharp . Io . Extensions ;
5
7
using System ;
6
8
using System . IO ;
7
9
using System . Linq ;
10
+ using System . Net . WebSockets ;
11
+ using System . Text ;
12
+ using System . Threading ;
13
+ using System . Threading . Tasks ;
8
14
9
15
/// <summary>
10
16
/// Represents the WebSocket interface. For more information see:
@@ -15,8 +21,14 @@ public class WebSocket : EventTarget, IDisposable
15
21
{
16
22
#region Fields
17
23
24
+ const Int32 ReceiveChunkSize = 2048 ;
25
+ const Int32 SendChunkSize = 1024 ;
26
+
18
27
readonly Url _url ;
19
28
readonly MemoryStream _buffered ;
29
+ readonly CancellationTokenSource _cts ;
30
+ readonly ClientWebSocket _ws ;
31
+
20
32
String _protocol ;
21
33
WebSocketReadyState _state ;
22
34
@@ -77,6 +89,7 @@ public WebSocket(String url, params String[] protocols)
77
89
_protocol = String . Empty ;
78
90
_state = WebSocketReadyState . Connecting ;
79
91
_buffered = new MemoryStream ( ) ;
92
+ _cts = new CancellationTokenSource ( ) ;
80
93
81
94
if ( _url . IsInvalid || _url . IsRelative )
82
95
throw new DomException ( DomError . Syntax ) ;
@@ -85,6 +98,17 @@ public WebSocket(String url, params String[] protocols)
85
98
86
99
if ( invalid > 0 )
87
100
throw new DomException ( DomError . Syntax ) ;
101
+
102
+ _ws = new ClientWebSocket ( ) ;
103
+ _ws . Options . KeepAliveInterval = TimeSpan . FromSeconds ( 20 ) ;
104
+ ConnectAsync ( url ) . Forget ( ) ;
105
+ }
106
+
107
+ async Task ConnectAsync ( String url )
108
+ {
109
+ await _ws . ConnectAsync ( new Uri ( url ) , _cts . Token ) . ConfigureAwait ( false ) ;
110
+ OnConnected ( ) ;
111
+ StartListen ( ) . Forget ( ) ;
88
112
}
89
113
90
114
#endregion
@@ -140,7 +164,7 @@ public String Protocol
140
164
[ DomName ( "send" ) ]
141
165
public void Send ( String data )
142
166
{
143
- //TODO
167
+ SendAsync ( data ) . Wait ( ) ;
144
168
}
145
169
146
170
/// <summary>
@@ -149,12 +173,14 @@ public void Send(String data)
149
173
[ DomName ( "close" ) ]
150
174
public void Close ( )
151
175
{
152
- //TODO
176
+ _state = WebSocketReadyState . Closing ;
177
+ StopListen ( ) ;
178
+ OnDisconnected ( ) ;
153
179
}
154
180
155
181
void IDisposable . Dispose ( )
156
182
{
157
- Close ( ) ;
183
+ StopListen ( ) ;
158
184
}
159
185
160
186
#endregion
@@ -172,6 +198,92 @@ static Boolean IsValid(String protocol)
172
198
return true ;
173
199
}
174
200
201
+ async Task SendAsync ( String message )
202
+ {
203
+ if ( _ws . State != WebSocketState . Open )
204
+ throw new Exception ( "WebSocket is already in CLOSING or CLOSED state." ) ;
205
+
206
+ var messageBuffer = Encoding . UTF8 . GetBytes ( message ) ;
207
+ var remainder = 0 ;
208
+ var messagesCount = Math . DivRem ( messageBuffer . Length , SendChunkSize , out remainder ) ;
209
+
210
+ if ( remainder > 0 )
211
+ messagesCount ++ ;
212
+ else
213
+ remainder = SendChunkSize ;
214
+
215
+ for ( var i = 0 ; i < messagesCount ; i ++ )
216
+ {
217
+ var offset = SendChunkSize * i ;
218
+ var lastMessage = ( i + 1 ) == messagesCount ;
219
+ var count = lastMessage ? remainder : SendChunkSize ;
220
+ var segment = new ArraySegment < Byte > ( messageBuffer , offset , count ) ;
221
+ await _ws . SendAsync ( segment , WebSocketMessageType . Text , lastMessage , _cts . Token ) . ConfigureAwait ( false ) ;
222
+ }
223
+ }
224
+
225
+ async Task StartListen ( )
226
+ {
227
+ var buffer = new Byte [ ReceiveChunkSize ] ;
228
+ var stringResult = new StringBuilder ( ) ;
229
+
230
+ try
231
+ {
232
+ while ( _ws . State == WebSocketState . Open )
233
+ {
234
+ var segment = new ArraySegment < Byte > ( buffer ) ;
235
+ var result = await _ws . ReceiveAsync ( segment , _cts . Token ) . ConfigureAwait ( false ) ;
236
+
237
+ if ( result . MessageType == WebSocketMessageType . Close )
238
+ {
239
+ await _ws . CloseAsync ( WebSocketCloseStatus . NormalClosure , String . Empty , _cts . Token ) . ConfigureAwait ( false ) ;
240
+ OnDisconnected ( ) ;
241
+ return ;
242
+ }
243
+
244
+ stringResult . Append ( Encoding . UTF8 . GetString ( buffer , 0 , result . Count ) ) ;
245
+
246
+ if ( result . EndOfMessage )
247
+ {
248
+ OnMessage ( stringResult . ToString ( ) ) ;
249
+ stringResult . Clear ( ) ;
250
+ }
251
+ }
252
+ }
253
+ catch
254
+ {
255
+ OnDisconnected ( ) ;
256
+ }
257
+ finally
258
+ {
259
+ StopListen ( ) ;
260
+ }
261
+ }
262
+
263
+ void StopListen ( )
264
+ {
265
+ _cts . Cancel ( ) ;
266
+ _ws . Abort ( ) ;
267
+ _ws . Dispose ( ) ;
268
+ }
269
+
270
+ void OnMessage ( String message )
271
+ {
272
+ this . Dispatch ( new MessageEvent ( MessageEvent , data : message , origin : _url . Href ) ) ;
273
+ }
274
+
275
+ void OnDisconnected ( )
276
+ {
277
+ _state = WebSocketReadyState . Closed ;
278
+ this . Dispatch ( new Event ( CloseEvent ) ) ;
279
+ }
280
+
281
+ void OnConnected ( )
282
+ {
283
+ _state = WebSocketReadyState . Open ;
284
+ this . Dispatch ( new Event ( OpenEvent ) ) ;
285
+ }
286
+
175
287
#endregion
176
288
}
177
289
}
0 commit comments