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.

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

报告相同问题?

悬赏问题

  • ¥50 comsol稳态求解器 找不到解,奇异矩阵有1个空方程返回的解不收敛。没有返回所有参数步长;pid控制
  • ¥15 怎么让wx群机器人发送音乐
  • ¥15 fesafe材料库问题
  • ¥35 beats蓝牙耳机怎么查看日志
  • ¥15 Fluent齿轮搅油
  • ¥15 八爪鱼爬数据为什么自己停了
  • ¥15 交替优化波束形成和ris反射角使保密速率最大化
  • ¥15 树莓派与pix飞控通信
  • ¥15 自动转发微信群信息到另外一个微信群
  • ¥15 outlook无法配置成功