【问题标题】:Websocket.ReceiveAsync causing a crash when a client disconnects without cancelling connection当客户端断开连接而不取消连接时,Websocket.ReceiveAsync 导致崩溃
【发布时间】:2020-04-07 09:37:34
【问题描述】:

DotNet Core 3.1(运行 Kestral 或 IIS)

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

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

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

                        }
                        while (!webSocketReceiveResult.EndOfMessage);

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

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

这里的完整方法:

    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);
        }
    }

【问题讨论】:

标签: c# asp.net-core websocket


【解决方案1】:

好的,无法让它工作,我已经将循环重写为如下所示,当客户端挂起时它干净地退出

  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);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-28
    • 2019-03-27
    • 2017-07-17
    • 2020-07-21
    • 2010-11-01
    相关资源
    最近更新 更多