Закрыть отправлено на стороне сервера, но на стороне клиента все еще есть соединение - PullRequest
0 голосов
/ 21 ноября 2018

У меня есть приложение, которое взаимодействует через API и веб-сокет.Веб-сокет используется для публикации обновленных пользовательских данных на клиенте, если они изменяются в базе данных - это работает отлично, за исключением случая, когда веб-сокет не получает никаких данных в некоторых случаях.Через несколько секунд веб-розетка снова начинает работать.

Журнал сервера (сначала веб-сокет не работает и снова начинает работать)

msg = "Не удалось записать данные в Websocket: websocket: закрыть отправлено"

msg = "Отправка сообщения Ping клиенту"

msg = "Не удалось записать сообщение Ping в Websocket: websocket: close sent"

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>

Код Go (проложенный через 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
            }
        }
    }
}

По сути, мы публикуем текущие данные в новом Websocket Connection,когда он обновляется - это всегда работает.После этого, если база данных изменяется, она публикует обновленный список пользователей в канале - веб-сокет должен затем отправить его клиенту, который обновляет список.Мы также отправляем сообщения ping, которые терпят неудачу (как видно из журнала выше).Сам клиент не регистрирует никаких ошибок или закрывает веб-сокет.

1 Ответ

0 голосов
/ 21 ноября 2018

Ошибка websocket: close sent указывает, что сервер отправил клиенту сообщение о закрытии.Поскольку код сервера приложений не отправляет сообщение, сообщение должно быть отправлено соединением в ответ на закрытое сообщение от клиента.

Сообщение о закрытии возвращается как ошибка из методов чтения websocket.Поскольку нет зарегистрированных сообщений, клиент должен был отправить закрывающее сообщение «уходящий» (единственная ошибка не зарегистрирована).

Когда соединение веб-сокета возвращает ошибку, программы чтения и записи закрывают соединение ивернуть.Соединение не оставлено открытым.

Функции чтения и записи не обнаруживают, что другой закрыл соединение, пока не будет возвращена ошибка из вызова метода соединения.Программа чтения быстро обнаруживает закрытое соединение, потому что она всегда читает, но для программы записи может быть задержка.Это может быть проблемой для приложения

Чтобы быстро завершить запись программы, сигнализируйте программу записи с помощью канала.Возможно, что dataChan может использоваться для этой цели, но я точно не знаю, потому что вопрос не включает информацию о том, как управляется канал.Предполагая, что канал можно использовать, программа чтения должна закрыться dataChan.Автор должен обнаружить закрытый канал и выйти из программы:

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

Если dataChan невозможноиспользовать, ввести новый канал только для этой цели.Создайте канал в обработчике и передайте его в программу чтения и записи:

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

Закройте канал при возврате из процедуры чтения:

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

Выберите канал взаписывающая программа:

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

Это заставляет записывающую программу быстро завершать работу после завершения считывающей программы.

Этот подход используется в примере команды Gorilla .

Оба эти подхода снижают вероятность того, что запись в соединение возвращает ошибку websocket: close sent, но онине исключайте возможность.Ошибка ожидается, потому что программа чтения может закрыть соединение непосредственно перед тем, как программа записи пишет сообщение.

В любом случае, доказательство состоит в том, что клиент закрывает соединение.Незащищенные соединения - это не проблема.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...