为了账号安全,请及时绑定邮箱和手机立即绑定

关闭在服务器端发送但客户端仍然连接

关闭在服务器端发送但客户端仍然连接

Go
繁星coding 2023-05-08 18:13:21
我有一个通过 API 和 websocket 进行通信的应用程序。websocket 用于将更新的用户数据发布到客户端,如果它在数据库中发生更改 - 这非常有效,除了在某些情况下 websocket 不接收任何数据的情况。几秒钟后,websocket 再次开始工作。服务器日志(首先,websocket 不工作并重新开始工作)msg="无法将数据写入 Websocket: websocket: close sent"msg="正在向客户端发送 Ping 消息"msg="无法将 ping 消息写入 Websocket:websocket:关闭发送"msg="正在向客户端发送 Ping 消息"msg="正在向客户端发送 Ping 消息"msg="正在向客户端发送 Ping 消息"msg="正在向客户端发送 Ping 消息"客户端代码:<html><body><p id="data"></p></body><script>var ws = new WebSocket("wss://example.com/ws");function unloadPage() {    toggleLoader();    ws.onclose = function () {};    ws.close();}ws.onopen = function () {    ws.send('Ping');};ws.onerror = function (error) {    console.log('WebSocket Error ' + error);    var d = document.getElementById("data");    d.innerHTML += "<tr><td>Failed to connect to Server.</td></tr>"};ws.onmessage = function (e) {    console.log(e);    var data = e.data;    var d = document.getElementById("data");    var parsedjson = JSON.parse(data);    d.innerHTML = "";    for (var i = 0; i < parsedjson.length; i++) {        d.innerHTML += parsedjson;    }};ws.onclose = function () {    console.log("Websocket has been closed");};window.addEventListener("beforeunload", unloadPage);</script></html>基本上,我们将当前数据发布到新的 Websocket 连接,当它更新时——这总是有效的。之后,如果数据库发生变化,它会将更新后的用户列表发布到频道中——然后 websocket 应将其发布到更新列表的客户端。我们还发送 ping 消息 - 失败(如上面的日志所示)。客户端本身不会记录任何错误或关闭 websocket。
查看完整描述

1 回答

?
尚方宝剑之说

TA贡献1788条经验 获得超4个赞

该websocket: close sent错误表明服务器向客户端发送了关闭消息。因为应用程序服务器代码不发送消息,所以连接必须发送消息以响应来自客户端的关闭消息。


关闭消息作为错误从 websocket 读取方法返回。因为没有记录任何消息,所以客户端一定发送了“离开”关闭消息(唯一没有记录的错误)。


当websocket连接返回错误时,读写goroutine关闭连接返回。连接未保持打开状态。


读取和写入 goroutine 不会检测到另一个已关闭连接,直到从连接上的方法调用返回错误。读取 goroutine 会快速检测到关闭的连接,因为它始终在读取,但写入 goroutine 可能会有延迟。这可能是应用程序的问题


要让写入 goroutine 快速退出,请使用通道向写入 goroutine 发出信号。有可能dataChan可用于此目的,但我不确定,因为该问题不包含有关如何管理频道的信息。假设通道可以使用,读取 goroutine 应该关闭dataChan。writer 应该检测到关闭的通道并退出 goroutine:


...

for {

    select {

    case data, ok := <-datachan:

        if !ok {

           // Done writing, return

           return

        }

        ws.SetWriteDeadline(time.Now().Add(writeWait))

        err := ws.WriteJSON(&data)

        if err != nil {

            conf.Log.Debugf("Failed to write data to Websocket: %v", err)

            return

        }

        ...

这是Gorilla Chat Example使用的方法。


如果dataChan不能使用,为此引入一个新的通道。在处理程序中创建通道并将通道传递给读写 goroutines:


 done := make(chan struct{})

 go allUserWebsocketWriter(ws, stop, datachan)

 go PingResponse(ws, stop)

从读取 goroutine 返回时关闭通道:


func PingResponse(ws *websocket.Conn, done chan struct{}) {

    defer close(done)

    conf := storage.GetConfig()

    ...

在写gorountine的通道上选择:


...

for {

    select {

    case <-done:

        return

    case data := <-datachan:

        ws.SetWriteDeadline(time.Now().Add(writeWait))

        err := ws.WriteJSON(&data)

        ...

这导致写 goroutine 在读 goroutine 退出后快速退出。


这两种方法都降低了在连接上写入返回错误的可能性websocket: close sent,但它们并没有消除这种可能性。该错误是预期的,因为读取 goroutine 可以在写入 goroutine 写入消息之前关闭连接。


无论如何,证据是客户端正在关闭连接。未关闭的连接不是问题所在。


查看完整回答
反对 回复 2023-05-08
  • 1 回答
  • 0 关注
  • 232 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信