1 | //------------------------------------------------------------------------------
|
---|
2 | // <copyright file="WebSocketChannelBase.cs" company="Microsoft">
|
---|
3 | // Copyright (c) Microsoft Corporation. All rights reserved.
|
---|
4 | // </copyright>
|
---|
5 | //------------------------------------------------------------------------------
|
---|
6 |
|
---|
7 | namespace Microsoft.Samples.Kinect.Webserver
|
---|
8 | {
|
---|
9 | using System;
|
---|
10 | using System.Diagnostics;
|
---|
11 | using System.Net;
|
---|
12 | using System.Net.WebSockets;
|
---|
13 | using System.Threading;
|
---|
14 | using System.Threading.Tasks;
|
---|
15 | using System.Windows.Threading;
|
---|
16 |
|
---|
17 | /// <summary>
|
---|
18 | /// Base class representing a web socket communication channel.
|
---|
19 | /// </summary>
|
---|
20 | /// <remarks>
|
---|
21 | /// This class has asynchronous functionality but it is NOT thread-safe, so it is expected
|
---|
22 | /// to be called by a single-threaded scheduler, e.g.: one running over a Dispatcher or
|
---|
23 | /// other SynchronizationContext implementation.
|
---|
24 | /// </remarks>
|
---|
25 | public class WebSocketChannelBase : IDisposable
|
---|
26 | {
|
---|
27 | /// <summary>
|
---|
28 | /// Maximum time allowed to pass between successive checks for web socket disconnection.
|
---|
29 | /// </summary>
|
---|
30 | private static readonly TimeSpan DisconnectionCheckTimeout = TimeSpan.FromSeconds(2.0);
|
---|
31 |
|
---|
32 | /// <summary>
|
---|
33 | /// Timer used to ensure we periodically check for disconnection even if no messages are
|
---|
34 | /// being sent between server and client.
|
---|
35 | /// </summary>
|
---|
36 | /// <remarks>
|
---|
37 | /// The disconnection timer helps us notice that there is a web socket resource ready to
|
---|
38 | /// be disposed.
|
---|
39 | /// </remarks>
|
---|
40 | private readonly DispatcherTimer disconnectionCheckTimer = new DispatcherTimer();
|
---|
41 |
|
---|
42 | /// <summary>
|
---|
43 | /// Action to perform when web socket becomes closed.
|
---|
44 | /// </summary>
|
---|
45 | private readonly Action<WebSocketChannelBase> closedAction;
|
---|
46 |
|
---|
47 | /// <summary>
|
---|
48 | /// Non-null if someone has requested that this object be closed and disposed. Null otherwise.
|
---|
49 | /// </summary>
|
---|
50 | private Task disposingTask;
|
---|
51 |
|
---|
52 | /// <summary>
|
---|
53 | /// Non-null if channel is currently sending a message. Null otherwise.
|
---|
54 | /// </summary>
|
---|
55 | private Task sendingTask;
|
---|
56 |
|
---|
57 | /// <summary>
|
---|
58 | /// True if we're performing the protocol close handshake and notifying clients
|
---|
59 | /// that socket has been closed.
|
---|
60 | /// </summary>
|
---|
61 | private bool isClosing;
|
---|
62 |
|
---|
63 | /// <summary>
|
---|
64 | /// True if this object has already been disposed. False otherwise.
|
---|
65 | /// </summary>
|
---|
66 | private bool isDisposed;
|
---|
67 |
|
---|
68 | /// <summary>
|
---|
69 | /// True if Closed event has been sent already. False otherwise.
|
---|
70 | /// </summary>
|
---|
71 | private bool closedSent;
|
---|
72 |
|
---|
73 | /// <summary>
|
---|
74 | /// True if disconnection monitoring task has been started, false otherwise.
|
---|
75 | /// </summary>
|
---|
76 | private bool isDisconnectionMonitorStarted;
|
---|
77 |
|
---|
78 | /// <summary>
|
---|
79 | /// Initializes a new instance of the <see cref="WebSocketChannelBase"/> class.
|
---|
80 | /// </summary>
|
---|
81 | /// <param name="context">
|
---|
82 | /// Web socket context.
|
---|
83 | /// </param>
|
---|
84 | /// <param name="closedAction">
|
---|
85 | /// Action to perform when web socket becomes closed.
|
---|
86 | /// </param>
|
---|
87 | protected WebSocketChannelBase(WebSocketContext context, Action<WebSocketChannelBase> closedAction)
|
---|
88 | {
|
---|
89 | if ((context == null) || (context.WebSocket == null))
|
---|
90 | {
|
---|
91 | throw new ArgumentNullException("context", @"Context and associated web socket must not be null");
|
---|
92 | }
|
---|
93 |
|
---|
94 | this.Socket = context.WebSocket;
|
---|
95 | this.CancellationTokenSource = new CancellationTokenSource();
|
---|
96 |
|
---|
97 | this.disconnectionCheckTimer.Interval = DisconnectionCheckTimeout;
|
---|
98 | this.disconnectionCheckTimer.Tick += this.OnDisconnectionCheckTimerTick;
|
---|
99 | this.disconnectionCheckTimer.Start();
|
---|
100 |
|
---|
101 | this.closedAction = closedAction;
|
---|
102 | }
|
---|
103 |
|
---|
104 | /// <summary>
|
---|
105 | /// True if this web socket channel is open for sending/receiving messages.
|
---|
106 | /// </summary>
|
---|
107 | public bool IsOpen
|
---|
108 | {
|
---|
109 | get
|
---|
110 | {
|
---|
111 | return !this.isDisposed && ((this.Socket.State == WebSocketState.Open) || (this.Socket.State == WebSocketState.Connecting));
|
---|
112 | }
|
---|
113 | }
|
---|
114 |
|
---|
115 | /// <summary>
|
---|
116 | /// Web socket used for communications.
|
---|
117 | /// </summary>
|
---|
118 | protected WebSocket Socket { get; private set; }
|
---|
119 |
|
---|
120 | /// <summary>
|
---|
121 | /// Token source used to cancel pending socket operations.
|
---|
122 | /// </summary>
|
---|
123 | protected CancellationTokenSource CancellationTokenSource { get; private set; }
|
---|
124 |
|
---|
125 | /// <summary>
|
---|
126 | /// Cancel all pending socket operations.
|
---|
127 | /// </summary>
|
---|
128 | public void Cancel()
|
---|
129 | {
|
---|
130 | this.CancellationTokenSource.Cancel();
|
---|
131 | }
|
---|
132 |
|
---|
133 | /// <summary>
|
---|
134 | /// Implement IDisposable.
|
---|
135 | /// </summary>
|
---|
136 | /// <remarks>
|
---|
137 | /// Releases resources right away if underlying socket is already closed.
|
---|
138 | /// Otherwise asynchronously awaits to complete web socket close handshake
|
---|
139 | /// and then disposes resources.
|
---|
140 | /// Call <see cref="CloseAsync"/> instead to be able to await for socket to
|
---|
141 | /// finish disposing resources.
|
---|
142 | /// </remarks>
|
---|
143 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification = "Asynchronous disposal. Does call Dispose(true) and GC.SuppressFinalize.")]
|
---|
144 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1816:CallGCSuppressFinalizeCorrectly", Justification = "Asynchronous disposal. Does call Dispose(true) and GC.SuppressFinalize.")]
|
---|
145 | public async void Dispose()
|
---|
146 | {
|
---|
147 | await this.CloseAsync();
|
---|
148 | }
|
---|
149 |
|
---|
150 | /// <summary>
|
---|
151 | /// Cancel all pending operations, initiate web socket close handshake and send Closed
|
---|
152 | /// event to clients if necessary.
|
---|
153 | /// </summary>
|
---|
154 | /// <returns>
|
---|
155 | /// Await-able task.
|
---|
156 | /// </returns>
|
---|
157 | /// <remarks>
|
---|
158 | /// Disposes of socket resources. There is no need to call <see cref="Dispose"/> after
|
---|
159 | /// calling this method.
|
---|
160 | /// </remarks>
|
---|
161 | public async Task CloseAsync()
|
---|
162 | {
|
---|
163 | if (this.disposingTask != null)
|
---|
164 | {
|
---|
165 | await this.disposingTask;
|
---|
166 | return;
|
---|
167 | }
|
---|
168 |
|
---|
169 | this.disposingTask = this.CloseAndDisposeAsync();
|
---|
170 | await this.disposingTask;
|
---|
171 | }
|
---|
172 |
|
---|
173 | /// <summary>
|
---|
174 | /// Determine if this web socket channel is open for sending/receiving messages
|
---|
175 | /// or if it has been closed
|
---|
176 | /// </summary>
|
---|
177 | /// <returns>
|
---|
178 | /// True if this web socket channel is still open, false otherwise.
|
---|
179 | /// </returns>
|
---|
180 | /// <remarks>
|
---|
181 | /// This call is expected to perform more comprehensive connection state checks
|
---|
182 | /// than IsOpen property, which might include sending remote messages, if the
|
---|
183 | /// specific <see cref="WebSocketChannelBase"/> subclass warrants it, so callers
|
---|
184 | /// should be careful not to call this method too often.
|
---|
185 | /// </remarks>
|
---|
186 | public virtual bool CheckConnectionStatus()
|
---|
187 | {
|
---|
188 | return this.IsOpen;
|
---|
189 | }
|
---|
190 |
|
---|
191 | /// <summary>
|
---|
192 | /// Try to establish a web socket context from the specified HTTP request context.
|
---|
193 | /// </summary>
|
---|
194 | /// <param name="listenerContext">
|
---|
195 | /// HTTP listener context.
|
---|
196 | /// </param>
|
---|
197 | /// <returns>
|
---|
198 | /// A web socket context if communications channel was successfully established.
|
---|
199 | /// Null if web socket channel could not be established.
|
---|
200 | /// </returns>
|
---|
201 | /// <remarks>
|
---|
202 | /// If <paramref name="listenerContext"/> does not represent a web socket request, or if
|
---|
203 | /// web socket channel could not be established, an appropriate status code will be
|
---|
204 | /// returned via <paramref name="listenerContext"/>'s Response property, and the return
|
---|
205 | /// value will be null.
|
---|
206 | /// </remarks>
|
---|
207 | protected static async Task<WebSocketContext> HandleWebSocketRequestAsync(HttpListenerContext listenerContext)
|
---|
208 | {
|
---|
209 | if (!listenerContext.Request.IsWebSocketRequest)
|
---|
210 | {
|
---|
211 | listenerContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
|
---|
212 | listenerContext.Response.Close();
|
---|
213 | return null;
|
---|
214 | }
|
---|
215 |
|
---|
216 | try
|
---|
217 | {
|
---|
218 | return await listenerContext.AcceptWebSocketAsync(null);
|
---|
219 | }
|
---|
220 | catch (WebSocketException)
|
---|
221 | {
|
---|
222 | listenerContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
|
---|
223 | listenerContext.Response.Close();
|
---|
224 | return null;
|
---|
225 | }
|
---|
226 | }
|
---|
227 |
|
---|
228 | /// <summary>
|
---|
229 | /// Sends data over the WebSocket connection asynchronously.
|
---|
230 | /// </summary>
|
---|
231 | /// <param name="buffer">
|
---|
232 | /// The buffer to be sent over the connection.
|
---|
233 | /// </param>
|
---|
234 | /// <param name="messageType">
|
---|
235 | /// Indicates whether the application is sending a binary or text message.
|
---|
236 | /// </param>
|
---|
237 | /// <returns>
|
---|
238 | /// true if the message was sent successfully. false otherwise.
|
---|
239 | /// </returns>
|
---|
240 | protected async Task<bool> SendAsync(ArraySegment<byte> buffer, WebSocketMessageType messageType)
|
---|
241 | {
|
---|
242 | if (await this.CheckForDisconnectionAsync())
|
---|
243 | {
|
---|
244 | return false;
|
---|
245 | }
|
---|
246 |
|
---|
247 | if (this.sendingTask != null)
|
---|
248 | {
|
---|
249 | Trace.TraceError("Channel is unable to start sending a new websocket message while it is already sending a previous message.");
|
---|
250 | return false;
|
---|
251 | }
|
---|
252 |
|
---|
253 | bool result = true;
|
---|
254 |
|
---|
255 | // We create a separate task from the one corresponding to the WebSocket.SendAsync
|
---|
256 | // method call, because we need to provide a guarantee to other parts of the code
|
---|
257 | // waiting on this task that SendAsync method call has returned by the time the
|
---|
258 | // sendingTask has completed.
|
---|
259 | // If we don't do this, it would be possible for other code awaiting on task to
|
---|
260 | // start executing before SendAsync stack frame gets a chance to clear the "sending"
|
---|
261 | // state to get ready to receive other calls that initiate data sending.
|
---|
262 | this.sendingTask = SharedConstants.CreateNonstartedTask();
|
---|
263 |
|
---|
264 | try
|
---|
265 | {
|
---|
266 | await this.Socket.SendAsync(buffer, messageType, true, this.CancellationTokenSource.Token);
|
---|
267 | }
|
---|
268 | catch (Exception e)
|
---|
269 | {
|
---|
270 | if (!IsSendReceiveException(e))
|
---|
271 | {
|
---|
272 | throw;
|
---|
273 | }
|
---|
274 |
|
---|
275 | result = false;
|
---|
276 | }
|
---|
277 | finally
|
---|
278 | {
|
---|
279 | this.sendingTask.Start();
|
---|
280 | this.sendingTask = null;
|
---|
281 | }
|
---|
282 |
|
---|
283 | if (!result)
|
---|
284 | {
|
---|
285 | // Client might have disconnected
|
---|
286 | await this.CheckForDisconnectionAsync();
|
---|
287 | }
|
---|
288 |
|
---|
289 | return result;
|
---|
290 | }
|
---|
291 |
|
---|
292 | /// <summary>
|
---|
293 | /// Receives data from the WebSocket connection asynchronously.
|
---|
294 | /// </summary>
|
---|
295 | /// <param name="buffer">
|
---|
296 | /// References the application buffer that is the storage location for the received data.
|
---|
297 | /// </param>
|
---|
298 | /// <returns>
|
---|
299 | /// A receive result representing a full message if we could receive one successfully
|
---|
300 | /// within the specified buffer. null otherwise.
|
---|
301 | /// </returns>
|
---|
302 | /// <remarks>
|
---|
303 | /// This method receives data from socket until either we get to the end of message sent
|
---|
304 | /// by socket client or we fill the specified buffer. If we don't get to end of message
|
---|
305 | /// within the allocated buffer space, we return a result value indicating that message
|
---|
306 | /// is still incomplete.
|
---|
307 | /// </remarks>
|
---|
308 | protected async Task<WebSocketReceiveResult> ReceiveAsync(ArraySegment<byte> buffer)
|
---|
309 | {
|
---|
310 | if (await this.CheckForDisconnectionAsync())
|
---|
311 | {
|
---|
312 | return null;
|
---|
313 | }
|
---|
314 |
|
---|
315 | int receiveOffset = 0;
|
---|
316 | int receiveCount = 0;
|
---|
317 | bool isResponseComplete = false;
|
---|
318 | WebSocketReceiveResult receiveResult = null;
|
---|
319 |
|
---|
320 | try
|
---|
321 | {
|
---|
322 | while (!isResponseComplete)
|
---|
323 | {
|
---|
324 | if (receiveCount >= buffer.Count)
|
---|
325 | {
|
---|
326 | // If we've filled up the buffer and response message is still not
|
---|
327 | // complete, we won't have space for response at all, so just return
|
---|
328 | // incomplete message.
|
---|
329 | return new WebSocketReceiveResult(
|
---|
330 | receiveCount, receiveResult != null ? receiveResult.MessageType : WebSocketMessageType.Text, false);
|
---|
331 | }
|
---|
332 |
|
---|
333 | receiveResult = await this.Socket.ReceiveAsync(new ArraySegment<byte>(buffer.Array, receiveOffset + buffer.Offset, buffer.Count - receiveOffset), this.CancellationTokenSource.Token);
|
---|
334 |
|
---|
335 | if (receiveResult.MessageType == WebSocketMessageType.Close)
|
---|
336 | {
|
---|
337 | await this.SendCloseMessagesAsync(Properties.Resources.SocketClosedByClient, true);
|
---|
338 | return null;
|
---|
339 | }
|
---|
340 |
|
---|
341 | receiveCount += receiveResult.Count;
|
---|
342 | receiveOffset += receiveResult.Count;
|
---|
343 | isResponseComplete = receiveResult.EndOfMessage;
|
---|
344 | }
|
---|
345 |
|
---|
346 | receiveResult = new WebSocketReceiveResult(receiveCount, receiveResult.MessageType, true);
|
---|
347 | }
|
---|
348 | catch (Exception e)
|
---|
349 | {
|
---|
350 | if (!IsSendReceiveException(e))
|
---|
351 | {
|
---|
352 | throw;
|
---|
353 | }
|
---|
354 |
|
---|
355 | return null;
|
---|
356 | }
|
---|
357 |
|
---|
358 | return receiveResult;
|
---|
359 | }
|
---|
360 |
|
---|
361 | /// <summary>
|
---|
362 | /// Dispose resources owned by this object.
|
---|
363 | /// </summary>
|
---|
364 | /// <param name="disposing">
|
---|
365 | /// True if called from IDisposable.Dispose. False if called by runtime during
|
---|
366 | /// finalization.
|
---|
367 | /// </param>
|
---|
368 | protected virtual void Dispose(bool disposing)
|
---|
369 | {
|
---|
370 | if (!this.isDisposed)
|
---|
371 | {
|
---|
372 | if (disposing)
|
---|
373 | {
|
---|
374 | this.disconnectionCheckTimer.Stop();
|
---|
375 |
|
---|
376 | this.Socket.Dispose();
|
---|
377 | this.Socket = null;
|
---|
378 |
|
---|
379 | this.CancellationTokenSource.Dispose();
|
---|
380 | this.CancellationTokenSource = null;
|
---|
381 | }
|
---|
382 |
|
---|
383 | this.isDisposed = true;
|
---|
384 | }
|
---|
385 | }
|
---|
386 |
|
---|
387 | /// <summary>
|
---|
388 | /// Start monitoring for client disconnection requests.
|
---|
389 | /// </summary>
|
---|
390 | /// <remarks>
|
---|
391 | /// Should not be called if legitimate (non-disconnection request) messages are
|
---|
392 | /// expected from client.
|
---|
393 | /// </remarks>
|
---|
394 | protected async void StartDisconnectionMonitor()
|
---|
395 | {
|
---|
396 | if (this.isDisposed)
|
---|
397 | {
|
---|
398 | return;
|
---|
399 | }
|
---|
400 |
|
---|
401 | if (this.isDisconnectionMonitorStarted)
|
---|
402 | {
|
---|
403 | // Monitor is already started
|
---|
404 | return;
|
---|
405 | }
|
---|
406 |
|
---|
407 | this.isDisconnectionMonitorStarted = true;
|
---|
408 | var dummyBuffer = new byte[1];
|
---|
409 |
|
---|
410 | try
|
---|
411 | {
|
---|
412 | WebSocketReceiveResult result;
|
---|
413 |
|
---|
414 | do
|
---|
415 | {
|
---|
416 | // We don't use a real cancellation token because explicitly cancelling a
|
---|
417 | // send or receive operation will put the socket in "Aborted" state, and
|
---|
418 | // it will appear to client as if we have forcefully closed the connection,
|
---|
419 | // when in reality our goal is just to passively monitor for client closing
|
---|
420 | // requests until socket connection is closed by either party.
|
---|
421 | var receiveTask = this.Socket.ReceiveAsync(new ArraySegment<byte>(dummyBuffer), CancellationToken.None);
|
---|
422 | await receiveTask;
|
---|
423 |
|
---|
424 | if (!receiveTask.IsCompleted)
|
---|
425 | {
|
---|
426 | return;
|
---|
427 | }
|
---|
428 |
|
---|
429 | result = receiveTask.Result;
|
---|
430 | }
|
---|
431 | while (result.MessageType != WebSocketMessageType.Close); // If message received was not a close message, keep looping.
|
---|
432 |
|
---|
433 | // We have received a close message, so initiate closing actions.
|
---|
434 | await this.SendCloseMessagesAsync(Properties.Resources.SocketClosedByClient, true);
|
---|
435 | }
|
---|
436 | catch (WebSocketException)
|
---|
437 | {
|
---|
438 | // If connection closing is server-driven, our call to receive data will throw
|
---|
439 | // a WebSocketException and we won't receive any closing message from client.
|
---|
440 | }
|
---|
441 | }
|
---|
442 |
|
---|
443 | /// <summary>
|
---|
444 | /// Determine if the specified exception is one of the standard web socket send/receive
|
---|
445 | /// exceptions.
|
---|
446 | /// </summary>
|
---|
447 | /// <param name="ex">
|
---|
448 | /// Caught exception.
|
---|
449 | /// </param>
|
---|
450 | /// <returns>
|
---|
451 | /// True if exception is of a type recognized as a standard web socket send/receive
|
---|
452 | /// exception.
|
---|
453 | /// </returns>
|
---|
454 | private static bool IsSendReceiveException(Exception ex)
|
---|
455 | {
|
---|
456 | return (ex is WebSocketException) || (ex is HttpListenerException) || (ex is OperationCanceledException);
|
---|
457 | }
|
---|
458 |
|
---|
459 | /// <summary>
|
---|
460 | /// Checks if socket has been disconnected
|
---|
461 | /// </summary>
|
---|
462 | /// <returns>
|
---|
463 | /// true if web socket has been disconnected already. false otherwise.
|
---|
464 | /// </returns>
|
---|
465 | private async Task<bool> CheckForDisconnectionAsync()
|
---|
466 | {
|
---|
467 | if (this.isDisposed || this.isClosing)
|
---|
468 | {
|
---|
469 | return true;
|
---|
470 | }
|
---|
471 |
|
---|
472 | // Every time we check for disconnection, stop the disconnection check timer
|
---|
473 | this.disconnectionCheckTimer.Stop();
|
---|
474 |
|
---|
475 | bool isDisconnected = !this.IsOpen;
|
---|
476 |
|
---|
477 | if (!isDisconnected)
|
---|
478 | {
|
---|
479 | // re-start timer if we're still connected
|
---|
480 | this.disconnectionCheckTimer.Start();
|
---|
481 | }
|
---|
482 | else
|
---|
483 | {
|
---|
484 | this.CancellationTokenSource.Cancel();
|
---|
485 | await this.SendCloseMessagesAsync(Properties.Resources.SocketClientDisconnectionDetected, false);
|
---|
486 | }
|
---|
487 |
|
---|
488 | return isDisconnected;
|
---|
489 | }
|
---|
490 |
|
---|
491 | /// <summary>
|
---|
492 | /// Send Closed event if it hasn't been sent already.
|
---|
493 | /// </summary>
|
---|
494 | private void SendClosed()
|
---|
495 | {
|
---|
496 | if (!this.closedSent)
|
---|
497 | {
|
---|
498 | if (this.closedAction != null)
|
---|
499 | {
|
---|
500 | this.closedAction(this);
|
---|
501 | }
|
---|
502 |
|
---|
503 | this.closedSent = true;
|
---|
504 | }
|
---|
505 | }
|
---|
506 |
|
---|
507 | /// <summary>
|
---|
508 | /// Cancel all pending operations, initiate web socket close handshake, send Closed
|
---|
509 | /// event to clients if necessary, and dispose of socket resources.
|
---|
510 | /// </summary>
|
---|
511 | /// <returns>
|
---|
512 | /// Await-able task.
|
---|
513 | /// </returns>
|
---|
514 | private async Task CloseAndDisposeAsync()
|
---|
515 | {
|
---|
516 | if (!await this.CheckForDisconnectionAsync())
|
---|
517 | {
|
---|
518 | this.CancellationTokenSource.Cancel();
|
---|
519 |
|
---|
520 | await this.SendCloseMessagesAsync(Properties.Resources.SocketClosedByServer, false);
|
---|
521 | }
|
---|
522 |
|
---|
523 | this.Dispose(true);
|
---|
524 |
|
---|
525 | GC.SuppressFinalize(this);
|
---|
526 | }
|
---|
527 |
|
---|
528 | /// <summary>
|
---|
529 | /// Gracefully close socket by performing the protocol close handshake and notify clients
|
---|
530 | /// that socket has been closed.
|
---|
531 | /// </summary>
|
---|
532 | /// <param name="closeMessage">
|
---|
533 | /// Human readable explanation as to why the connection is being closed.
|
---|
534 | /// </param>
|
---|
535 | /// <param name="awaitAcknowledgement">
|
---|
536 | /// True if we should wait for client acknowledgement of socket close request.
|
---|
537 | /// False if we shouldn't wait.
|
---|
538 | /// </param>
|
---|
539 | /// <returns>
|
---|
540 | /// Await-able task.
|
---|
541 | /// </returns>
|
---|
542 | private async Task SendCloseMessagesAsync(string closeMessage, bool awaitAcknowledgement)
|
---|
543 | {
|
---|
544 | // If we're already closing, there's no need to start closing again
|
---|
545 | if (this.isClosing)
|
---|
546 | {
|
---|
547 | return;
|
---|
548 | }
|
---|
549 |
|
---|
550 | this.isClosing = true;
|
---|
551 |
|
---|
552 | try
|
---|
553 | {
|
---|
554 | // Store task reference because instance variable could be set to null while
|
---|
555 | // we await, and await operator will reference task again once we are done
|
---|
556 | // awaiting.
|
---|
557 | var task = this.sendingTask;
|
---|
558 | if (task != null)
|
---|
559 | {
|
---|
560 | // Wait for the sending task to complete, if it is pending.
|
---|
561 | await task;
|
---|
562 | }
|
---|
563 | }
|
---|
564 | catch (Exception e)
|
---|
565 | {
|
---|
566 | if (!IsSendReceiveException(e))
|
---|
567 | {
|
---|
568 | throw;
|
---|
569 | }
|
---|
570 | }
|
---|
571 |
|
---|
572 | try
|
---|
573 | {
|
---|
574 | if (awaitAcknowledgement)
|
---|
575 | {
|
---|
576 | await this.Socket.CloseAsync(WebSocketCloseStatus.NormalClosure, closeMessage, CancellationToken.None);
|
---|
577 | }
|
---|
578 | else
|
---|
579 | {
|
---|
580 | await this.Socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, closeMessage, CancellationToken.None);
|
---|
581 | }
|
---|
582 | }
|
---|
583 | catch (Exception e)
|
---|
584 | {
|
---|
585 | if (!IsSendReceiveException(e))
|
---|
586 | {
|
---|
587 | throw;
|
---|
588 | }
|
---|
589 |
|
---|
590 | Trace.TraceWarning(
|
---|
591 | "Problem encountered while closing socket. Client might have gone away abruptly without initiating socket close handshake.\n{0}",
|
---|
592 | e);
|
---|
593 | }
|
---|
594 |
|
---|
595 | this.SendClosed();
|
---|
596 | }
|
---|
597 |
|
---|
598 | /// <summary>
|
---|
599 | /// Handler for Tick event of disconnection check timer.
|
---|
600 | /// </summary>
|
---|
601 | /// <param name="sender">
|
---|
602 | /// Object that sent this event.
|
---|
603 | /// </param>
|
---|
604 | /// <param name="args">
|
---|
605 | /// Event arguments.
|
---|
606 | /// </param>
|
---|
607 | private async void OnDisconnectionCheckTimerTick(object sender, EventArgs args)
|
---|
608 | {
|
---|
609 | await this.CheckForDisconnectionAsync();
|
---|
610 | }
|
---|
611 | }
|
---|
612 | }
|
---|