C# WebSockets Multicast notifications asynchronously

时间:2019-05-31 11:45:23

标签: c# asp.net websocket

I have a Asp.NET core application. Startup.Configure() mainly contains this code

app.UseWebSockets();
app.Use(async (httpContext, next) =>
{
    // If the request is a WebServerRequest, handle it as such ...
    if (httpContext.WebSockets.IsWebSocketRequest)
    {
        ClientHandler h = new ClientHandler(httpContext);

        if (h.IsWebsockOpen)
        {
            await h.Handle();
        }
        else
        {
            httpContext.Response.StatusCode = 400;
        }
    }

    // ... otherwise just hand the request to the next element in chain
    else
    {
        await next();
    }
});

Inside h.Handle() the client is supposed to register with a ClientManager which in turn multicasts that a new client has connected like this

public async Task Multicast<T>(List<ClientHandler> l, Msg<T> m)
{
    foreach (ClientHandler h in l)
    {
        if (h.IsWebsockOpen)
        {
            await h.webSocket.SendAsync(
                System.Text.Encoding.UTF8.GetBytes(m.ToString()),
                System.Net.WebSockets.WebSocketMessageType.Text,
                true,
                System.Threading.CancellationToken.None);
        }
    }
}

I am now wondering if that is safe to do. I can imagine a scenario where two clients connect at the same time resulting in h.webSocket.SendAsync being called twice simultaneously, which is not allowed as said in

https://docs.microsoft.com/en-us/dotnet/api/system.net.websockets.websocket.sendasync?view=netframework-4.8

Remarks This operation will not block. The returned Task object will complete after the data has been sent on the WebSocket.

Exactly one send and one receive is supported on each WebSocket object in parallel.

Wraping the h.webSocket.SendAsync in a lock-statement seems to be impossible due to the await keyword.

How can I make my code safe? Related questions are either not using WebSockets or they use different frameworks for which mechanisms apply.

1 个答案:

答案 0 :(得分:1)

您可以在此处使用信号量,特别是SemaphoreSlim。我建议在您的gifski类上创建一个SendAsync方法,并通过该方法传递所有请求-即从您的ClientHandler方法进行调用。

您的Multicast的内容将类似于:

ClientHandler

class ClientHandler() { private readonly SemaphoreSlim _sendLock; public ClientHandler(HttpContext context) { _sendLock = new SemaphoreSlim(1, 1); //.... } public async Task SendAsync(string msg) { await _sendLock.WaitAsync(); try { await webSocket.SendAsync( System.Text.Encoding.UTF8.GetBytes(msg.ToString()), System.Net.WebSockets.WebSocketMessageType.Text, true, System.Threading.CancellationToken.None); } finally { _sendLock.Release(); } } } SemaphoreSlim,因此您需要注意这一点,它的IDisposable方法有一些重载的取消令牌和/或超时,可能适合您使用。