WebSocket单工流 - 客户端如何关闭连接?

Posted

技术标签:

【中文标题】WebSocket单工流 - 客户端如何关闭连接?【英文标题】:WebSocket simplex stream - how can the client close the connection? 【发布时间】:2021-11-16 15:30:05 【问题描述】:

我正在使用 WebSockets 将值从服务器传输到客户端。当值流完成(服务器端终止)或客户端停止订阅(客户端终止)时,应关闭连接。

客户端如何优雅地关闭连接?

在 AspNetCore 中演示问题的粗略示例;服务器推送(可能)无限的值流;客户端订阅前 5 个值,然后应该关闭连接。

app.Use(async (context, next) =>

    if (context.Request.Path == "/read")
    
        var client = new ClientWebSocket();
        await client.ConnectAsync(new Uri(@"wss://localhost:7125/write"), CancellationToken.None);

        for (int i = 0; i < 5; i++)
            await client.ReceiveAsync(new ArraySegment<byte>(new byte[sizeof(int)]), CancellationToken.None);
        
        // This does not seem to have any particular effect on writer
        // await client.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
        
        // This freezes in AspNetcore on .NET6 core (because the implementation waits for the connection to close, which never happens)
        // In AspNet (non-core, .Net Framework 4.8), this seems to throw an exception that data cannot be read after the connection has been closed (i.e. the socket seems to only be closeable if no data is pending to be read)
        await client.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
    

    if (context.Request.Path == "/write")
    
        var ws = await context.WebSockets.AcceptWebSocketAsync();
            
        await foreach (var number in GetNumbers())
        
            var bytes = BitConverter.GetBytes(number);
                
            if (ws.State != WebSocketState.Open)
                throw new Exception("I wish we'd hit this branch, but we never do!");
                
            await ws.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Binary, true, CancellationToken.None);
        
    
);

static async IAsyncEnumerable<int> GetNumbers()

    for (int i = 0; i <= int.MaxValue; i++)
    
        yield return i;
        await Task.Delay(25);
    

一般问题似乎是 /write 方法没有接收到关闭消息,即 ws.State 仍然是 WebSocketState.Open。我假设只有接收操作会更新连接状态?

有没有什么好的方法来处理这种情况/让服务器接收客户端关闭连接的请求?

我很想避免客户端必须向服务器发送任何显式消息/服务器必须显式读取流。不过,我越来越想知道这是否可能。

【问题讨论】:

【参考方案1】:

WebSocket 协议的工作方式类似于 TCP - 通过建立连接,唯一的区别 - 通过 http[s] 完成初始化。 来自一侧的一个发送操作与来自另一侧的一个接收操作相匹配,反之亦然。 你可以在documentation的评论中注意到这个细节(如果我没记错的话):

每个 WebSocket 对象并行支持一发一收。

因此,您应该至少收到一个数据段并识别来自客户端的 CloseIntention 消息。在客户端也是如此。

如何接收消息、识别亲密意图并对其做出适当反应 - 请参阅here。 如何发送关闭意图消息 - 请参阅here。

怀疑您应该在服务器的后台至少调用一次webSocket.ReceiveAsync。 然后,在ContinueWith 任务中调用 CancellationTokenSource.Cancel() 获取当前服务器套接字会话。 除了 docker-compose 之外,该 repo 正在运行。 - 我在复杂的 DevOps 方面有点新手。 )

更新

文档的备注部分不是关于在对话的不同方面匹配发送-接收操作。只是想让你注意这个 TCP 概念是如何工作的,即你应该至少接收一次数据。

【讨论】:

谢谢 - 确实在启动读取时有效,即使客户端没有明确提交数据(关闭消息本身似乎触发读取完成,读取字节为 0,但适当的关闭状态)。在后台启动阅读并不会感觉非常优雅,但这似乎是最好的(唯一的?)方式。谢谢! 不客气!

以上是关于WebSocket单工流 - 客户端如何关闭连接?的主要内容,如果未能解决你的问题,请参考以下文章

websocket连接正在自动关闭

如何在WebSocket的服务器侧检测客户端的断开连接

Springboot集成SSE实现消息推送之单工通信

websocket

怎么在服务器端关闭websocket连接

如何正确关闭 java-ee websocket 连接