当客户端断开连接而不取消连接时 Websocket.ReceiveAsync 导致崩溃 [英] Websocket.ReceiveAsync causing a crash when a client disconnects without cancelling connection

查看:71
本文介绍了当客户端断开连接而不取消连接时 Websocket.ReceiveAsync 导致崩溃的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

DotNet Core 3.1(运行 Kestral 或 IIS)

DotNet Core 3.1 (running either Kestral or IIS)

我有以下循环,为每个连接的客户端运行一个任务

I have the following loop, running in a Task for each connected client

 using (var ms = new MemoryStream())
                {
                        do

                            webSocketReceiveResult = await socket.ReceiveAsync(buffer, CancellationToken.None);
                            ms.Write(buffer.Array, buffer.Offset, webSocketReceiveResult.Count);

                        }
                        while (!webSocketReceiveResult.EndOfMessage);

当客户端突然退出、网络故障、崩溃或其他任何事情并且没有机会关闭其连接时,它会使整个类崩溃,并且所有任务都在运行.网络服务器仍在运行并将接受所有新连接,但所有现有连接都将终止.

When a client abruptly exits, network failure, crash or whatever and doesnt have a chance to close its connection, it crashes the whole class, and all tasks running. The webserver is still up and will accept all new connections, but all existing connections are terminated.

错误是:'远程方在没有完成关闭握手的情况下关闭了 WebSocket 连接.'这是预期的,但我无法尝试捕获它以保留其余连接?

The error is : 'The remote party closed the WebSocket connection without completing the close handshake.' which is expected, but I cannot try-catch it to preserve the rest of the connections?

完整方法在这里:

    private static async Task SocketProcessingLoopAsync(ConnectedClient client)
    {
        _ = Task.Run(() => client.BroadcastLoopAsync().ConfigureAwait(false));

        var socket = client.Socket;
        var loopToken = SocketLoopTokenSource.Token;
        var broadcastTokenSource = client.BroadcastLoopTokenSource; // store a copy for use in finally block
        string sessionName = "";
        string command = "";
        int commandCounter = 0;
        WebSocketReceiveResult webSocketReceiveResult = null;
        try
        {



            var buffer = WebSocket.CreateServerBuffer(4096);

            while (socket.State != WebSocketState.Closed && socket.State != WebSocketState.Aborted && !loopToken.IsCancellationRequested)
            {

                // collect all the bytes incoming

                using (var ms = new MemoryStream())
                {
                        do
                        {
                            webSocketReceiveResult = await socket.ReceiveAsync(buffer, CancellationToken.None);
                            ms.Write(buffer.Array, buffer.Offset, webSocketReceiveResult.Count);

                        }
                        while (!webSocketReceiveResult.EndOfMessage);

                    //var receiveResult = await client.Socket.ReceiveAsync(buffer, loopToken);

                    // if the token is cancelled while ReceiveAsync is blocking, the socket state changes to aborted and it can't be used
                    if (!loopToken.IsCancellationRequested)
                    {
                        // the client is notifying us that the connection will close; send acknowledgement
                        if (client.Socket.State == WebSocketState.CloseReceived && webSocketReceiveResult.MessageType == WebSocketMessageType.Close)
                        {
                            Console.WriteLine($"Socket {client.SocketId}: Acknowledging Close frame received from client");
                            broadcastTokenSource.Cancel();
                            await socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "Acknowledge Close frame", CancellationToken.None);
                            // the socket state changes to closed at this point
                        }


                        if (client.Socket.State == WebSocketState.Open)
                        {
                            if (webSocketReceiveResult.MessageType == WebSocketMessageType.Text)
                            {
                                // here we receive text instructioons from the clients

                                using (StreamReader reader = new StreamReader(ms, Encoding.UTF8))
                                {
                                    ms.Seek(0, SeekOrigin.Begin);
                                    command = reader.ReadToEnd().ToLower();
                                }

                                //   var command = Encoding.UTF8.GetString(buffer.Array, 0, webSocketReceiveResult.Count).ToLower();
                                if (command.Contains("session:"))
                                {
                                    // assign the client to a session, and inform them of their position in it
                                    sessionName = command.Replace("session:", "");
                                    if (!ClientControl.Sessions.ContainsKey(sessionName))
                                    {
                                        ClientControl.Sessions.TryAdd(sessionName, new BlockingCollection<ConnectedClient>());
                                    }
                                    ClientControl.Sessions[sessionName].Add(client);
                                    // broadcast the collection count
                                    Broadcast(ClientControl.Sessions[sessionName].Count.ToString(), client, true, sessionName);
                                    Broadcast("Number of clients " + ClientControl.Sessions[sessionName].Count.ToString(), client, false, sessionName);
                                    Broadcast("Number of clients " + ClientControl.Sessions[sessionName].Count.ToString(), client, true, sessionName);
                                }
                                else if (command.Contains("status:"))
                                {
                                    string output = "<br/><h1>Sessions:</h1><br/><br/>";
                                    foreach (var session in ClientControl.Sessions)
                                    {
                                        output += session.Key + " Connected Clients: " + session.Value.Count.ToString() + "<br/>";

                                    }

                                    Broadcast(output, client, true, "");
                                }
                                else if (command.Contains("ping"))
                                {
                                    Console.WriteLine(command + " " + DateTime.Now.ToString() + " " + commandCounter);
                                }

                            }
                            else
                            {
                                // we just mirror what is sent out to the connected clients, depending on the session

                                Broadcast(ms.ToArray(), client, sessionName);
                                commandCounter++;

                            }

                        }

                    }

                }// end memory stream 

            }
        }
        catch (OperationCanceledException)
        {
            // normal upon task/token cancellation, disregard
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Socket {client.SocketId}:");
            Program.ReportException(ex);
        }
        finally
        {
            broadcastTokenSource.Cancel();

            Console.WriteLine($"Socket {client.SocketId}: Ended processing loop in state {socket.State}");

            // don't leave the socket in any potentially connected state
            if (client.Socket.State != WebSocketState.Closed)
                client.Socket.Abort();

            // by this point the socket is closed or aborted, the ConnectedClient object is useless
            if (ClientControl.Sessions[sessionName].TryTake(out client))
                socket.Dispose();

            // signal to the middleware pipeline that this task has completed
            client.TaskCompletion.SetResult(true);
        }
    }

推荐答案

好的,无法让它工作,我重新编写了循环看起来像这样,当客户端挂起时它干净地退出

Ok, couldn't get this to work, I have re-written the loop to look like this, it exits cleanly when a client hangs

  do
                    {
                        cancellationTokenSource = new CancellationTokenSource(10000);

                        task = socket.ReceiveAsync(buffer, loopToken);
                        while (!task.IsCompleted && !SocketLoopTokenSource.IsCancellationRequested)
                        {
                            await Task.Delay(2).ConfigureAwait(false);
                        }
                        if (socket.State != WebSocketState.Open || task.Status != TaskStatus.RanToCompletion)
                        {
                            if (socket.State == WebSocketState.CloseReceived)
                            {
                                Console.WriteLine($"Socket {client.SocketId}: Acknowledging Close frame received from client");
                                broadcastTokenSource.Cancel();
                                await socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "Acknowledge Close frame", CancellationToken.None);
                                ClientControl.Sessions[sessionName].TryTake(out client);
                            }

                            Console.WriteLine("Client Left " + client.SocketId);
                            break;
                        }
                        ms.Write(buffer.Array, buffer.Offset, task.Result.Count);

                    }
                    while (!task.Result.EndOfMessage);

这篇关于当客户端断开连接而不取消连接时 Websocket.ReceiveAsync 导致崩溃的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆