当客户端断开连接而不取消连接时,Websocket.ReceiveAsync 导致崩溃
Posted
技术标签:
【中文标题】当客户端断开连接而不取消连接时,Websocket.ReceiveAsync 导致崩溃【英文标题】:Websocket.ReceiveAsync causing a crash when a client disconnects without cancelling connection 【发布时间】: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);
【问题讨论】:
您可以将此作为示例进行检查:github.com/georgekosmidis/AspNetCore.WebSockets 【参考方案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);
【讨论】:
以上是关于当客户端断开连接而不取消连接时,Websocket.ReceiveAsync 导致崩溃的主要内容,如果未能解决你的问题,请参考以下文章
当客户端更改 IP 地址或断开连接时,如何捕捉 websocket-close 事件?