doumei1772 2018-11-21 16:22
浏览 703
已采纳

关闭已在服务器端发送,但客户端仍保持连接

I have an application that communicates over an API and a websocket. The websocket is used to publish updated userdata to the client if it is changed in the database - this works perfectly, except for the case that the websocket does not receive any data in some cases. After a few seconds, the websocket starts working again.

The server log (first, the websocket does not work and starts working again)

msg="Failed to write data to Websocket: websocket: close sent"

msg="Sending Ping Message to Client"

msg="Failed to write ping message to Websocket: websocket: close sent"

msg="Sending Ping Message to Client"

msg="Sending Ping Message to Client"

msg="Sending Ping Message to Client"

msg="Sending Ping Message to Client"

The client-side code:

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

The Go Code (routed through gorilla mux):

var (
    upgrader = websocket.Upgrader{
        ReadBufferSize:  1024,
        WriteBufferSize: 1024,
        CheckOrigin:     func(r *http.Request) bool { return true },
    }
    pingPeriod = (pongPeriod * 9) / 10
    pongPeriod = 60 * time.Second
    writeWait  = 10 * time.Second
)


func PingResponse(ws *websocket.Conn) {
    conf := storage.GetConfig()
    defer ws.Close()
    ws.SetReadLimit(512)
    ws.SetReadDeadline(time.Now().Add(pongPeriod))
    ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add(pongPeriod)); return nil })
    for {
        _, _, err := ws.ReadMessage()
        if err != nil {
            if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
                conf.Log.Debugf("Websocket Ping Read Failed: %v", err)
            }

            return
        } else {
            conf.Log.Debugf("Received message from Websocket client")
        }
    }
}

func ServeAllUsersWebsocket(datachan chan *[]storage.UserResponse) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        conf := storage.GetConfig()
        ws, err := upgrader.Upgrade(w, r, nil)
        if err != nil {
            conf.Log.Debugf("Failed to upgrade data to Websocket: %v", err)
            return
        }

        go allUserWebsocketWriter(ws, datachan)
        go PingResponse(ws)
    })
}


func allUserWebsocketWriter(ws *websocket.Conn, datachan chan *[]storage.UserResponse) {
    conf := storage.GetConfig()
    pingticker := time.NewTicker(pingPeriod)
    defer func() {
        pingticker.Stop()
        ws.Close()
    }()

    userresponse, err := conf.Database.GetAllUsers()
    if err != nil {
        conf.Log.Errorf("Failed to query users from database: %v", err)
        return
    }

    ws.SetWriteDeadline(time.Now().Add(writeWait))
    err = ws.WriteJSON(&userresponse)
    if err != nil {
        conf.Log.Debugf("Failed to write initial user response: %v", err)
        return
    }

    for {
        select {
        case data := <-datachan:
            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
            }
        case <-pingticker.C:
            ws.SetWriteDeadline(time.Now().Add(writeWait))
            conf.Log.Debugf("Sending Ping Message to Client")
            if err := ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
                conf.Log.Debugf("Failed to write ping message to Websocket: %v", err)
                return
            }
        }
    }
}

Basically, we are posting the current data to the new Websocket Connection, when it is updated - this does always work. Afterwards, if the database changes, it posts the updated userlist into the channel - the websocket should then post it to the client which updates the list. We are also sending ping messages - which fail (as seen in the log above). The client itself does not log any errors or closes the websocket.

  • 写回答

1条回答 默认 最新

  • doubi12138 2018-11-21 20:01
    关注

    The websocket: close sent error indicates that the server sent a close message to the client. Because the application server code does not send the message, the message must have been sent by the connection in response to a close message from the client.

    The close message is returned as an error from the websocket read methods. Because there are no messages logged, the client must have sent the 'going away' close message (the only error not logged).

    When the websocket connection returns an error, the reading and writing goroutines close the connection and return. The connection is not left open.

    The reading and writing goroutines do not detect that the other has closed the connection until an error is returned from a method call on the connection. The reading goroutine quickly detects the closed connection because it's always reading, but there can be a delay for the writing goroutine. This may be a problem for the application

    To get the writing goroutine to exit quickly, signal writing goroutine using a channel. It's possible that dataChan can be used for this purpose, but I don't know for sure because the question does not include information about how the channel is managed. Assuming that the channel can be used, the reading goroutine should close dataChan. The writer should detect the closed channel and exit the 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
            }
            ...
    

    This is the approach used by the Gorilla Chat Example.

    If dataChan cannot be used, introduce a new channel just for this purpose. Create the channel in the handler and pass the channel to the reading and writing goroutines:

     done := make(chan struct{})
     go allUserWebsocketWriter(ws, stop, datachan)
     go PingResponse(ws, stop)
    

    Close the channel on return from the reading goroutine:

    func PingResponse(ws *websocket.Conn, done chan struct{}) {
        defer close(done)
        conf := storage.GetConfig()
        ...
    

    Select on the channel in the writing gorountine:

    ...
    for {
        select {
        case <-done:
            return
        case data := <-datachan:
            ws.SetWriteDeadline(time.Now().Add(writeWait))
            err := ws.WriteJSON(&data)
            ...
    

    This causes the writing goroutine to quickly exit after the reading goroutine exits.

    This is the approach used by the Gorilla Command Example.

    Both of these approaches reduce the likelihood that write on the connection returns the websocket: close sent error, but they do not eliminate the possibility. The error is expected because the reading goroutine can close the connection just before the writing goroutine writes a message.

    In any case, the evidence is that the client is closing the connection. Unclosed connections are not whatever the issue is.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 这个电路是如何实现路灯控制器的,原理是什么,怎么求解灯亮起后熄灭的时间如图?
  • ¥15 matlab数字图像处理频率域滤波
  • ¥15 在abaqus做了二维正交切削模型,给刀具添加了超声振动条件后输出切削力为什么比普通切削增大这么多
  • ¥15 ELGamal和paillier计算效率谁快?
  • ¥15 file converter 转换格式失败 报错 Error marking filters as finished,如何解决?
  • ¥15 ubuntu系统下挂载磁盘上执行./提示权限不够
  • ¥15 Arcgis相交分析无法绘制一个或多个图形
  • ¥15 关于#r语言#的问题:差异分析前数据准备,报错Error in data[, sampleName1] : subscript out of bounds请问怎么解决呀以下是全部代码:
  • ¥15 seatunnel-web使用SQL组件时候后台报错,无法找到表格
  • ¥15 fpga自动售货机数码管(相关搜索:数字时钟)