//------------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//------------------------------------------------------------------------------
namespace Microsoft.Samples.Kinect.Webserver
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Net.WebSockets;
using System.Threading.Tasks;
///
/// Web socket communication channel used for sending events to clients.
///
public sealed class WebSocketEventChannel : WebSocketChannelBase
{
///
/// If more than this number of send tasks are overlapping (i.e.: simultaneously
/// awaiting to finish sending), it is considered as an indication that more
/// events are happening at a faster pace than can be handled by the underlying
/// web socket.
///
private const int MaximumOverlappingTaskCount = 10;
///
/// Keeps track of task representing the last send request initiated.
///
private Task lastSendTask;
///
/// Number of overlapping send tasks.
///
private int overlappingTaskCount;
///
/// Initializes a new instance of the class.
///
///
/// Web socket context.
///
///
/// Action to perform when web socket becomes closed.
///
internal WebSocketEventChannel(WebSocketContext context, Action closedAction)
: base(context, closedAction)
{
// Always monitor for disconnections.
this.StartDisconnectionMonitor();
}
///
/// Attempt to open a new event channel from the specified HTTP listener context.
///
///
/// HTTP listener context.
///
///
/// Action to perform when web socket is opened.
/// Will never be called if web channel can't be opened.
///
///
/// Action to perform when web socket is closed.
/// Will never be called if web channel can't be opened.
///
///
/// If does not represent a web socket request, or if
/// web socket channel could not be established, an appropriate status code will be
/// returned via 's Response property.
///
public static async void TryOpenAsync(
HttpListenerContext listenerContext, Action openedAction, Action closedAction)
{
var socketContext = await HandleWebSocketRequestAsync(listenerContext);
if (socketContext != null)
{
var channel = new WebSocketEventChannel(socketContext, closedChannel => closedAction(closedChannel as WebSocketEventChannel));
openedAction(channel);
}
}
///
/// Asynchronously sends a batch of messages over the web socket channel.
///
///
/// Batch of messages to be sent as an atomic block through the web socket.
///
///
/// true if the messages were sent successfully. false otherwise.
///
public async Task SendMessagesAsync(params WebSocketMessage[] messages)
{
if (messages.Length == 0)
{
// No work to be done
return true;
}
++this.overlappingTaskCount;
Task> getSendTaskTask = null;
try
{
if (this.overlappingTaskCount > MaximumOverlappingTaskCount)
{
throw new InvalidOperationException(@"Events are being generated faster than web socket channel can handle");
}
// Create a function whose only purpose is to return a task representing
// the real work that needs to be done to send the messages, and a task
// corresponding to this function.
// We're basically creating a linked list of tasks, where each task waits
// for the previous task (if it exists) to finish processing before starting
// to do its own work.
// We do things in this way rather than adding message data to a queue that
// then processes each message in order, to avoid the additional data copy
// (and potential allocation) that would be required to allow for deferred
// processing. The current contract has message data be processed in the same
// function stack frame in which an awaiting client called us.
Func