WebSocket - закрывающее рукопожатие Gorilla

Фрагмент из WebSocket RFC:

Чтобы запустить рукопожатие закрытия WebSocket с кодом состояния (раздел 7.4) / code / и необязательной причиной закрытия (раздел 7.1.6) / reason /, конечная точка ДОЛЖНА отправить кадр управления Close, как описано в Разделе 5.5.1, код состояния которого установлен на / code /, а причина закрытия установлена ​​на / reason /. Как только конечная точка отправила и получила кадр управления Close, этой конечной точке СЛЕДУЕТ закрыть соединение WebSocket, как определено в разделе 7.1.1.

Я пытаюсь выполнить Close Handshake, используя пакет Gorilla WebSocket со следующим кодом:

Сервер:

// Create upgrader function
conn, err := upgrader.Upgrade(w, r, nil)

// If there is an error stop everything.
if err != nil {
    fmt.Println(err)
    return
}

for {
    // Read Messages
    _, _, err := conn.ReadMessage()
    // Client is programmed to send a close frame immediately...
    // When reading close frame resend close frame with same
    // reason and code
    conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(1000, "woops"))
    fmt.Println(err)
    break
}

Клиент:

d := &websocket.Dialer{}

conn, _, err := d.Dial("ws://localhost:8080", nil)

if err != nil {
    fmt.Println(err)
    return
}

go func() {
    for {
        // Read Messages
        _, _, err := conn.ReadMessage()

        if c, k := err.(*websocket.CloseError); k {
            if(c.Code == 1000) {
                // Never entering since c.Code == 1005
                fmt.Println(err)
                break
            }
        }
    }
}()

conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(1000, "woops"))

for {}

Сервер читает Close Frame, как и ожидалось, выводя следующее:

websocket: закрыть 1000 (нормальный): woops

Однако клиент как бы останавливается, чтобы читать, как только он отправляет сообщение о закрытии. ReadMessage продолжает возвращать ошибку 1005. Что я делаю не так?


person Kim Byer    schedule 15.02.2016    source источник


Ответы (1)


Сервер отвечает на закрытый фрейм кодом :

    c.WriteControl(CloseMessage, []byte{}, time.Now().Add(writeWait))

Это преобразуется клиентом в код закрытия 1005 (статус не получен).

Фрейм закрытия 1000 oops, записанный сервером, не виден клиентскому приложению, потому что соединение с веб-сокетом перестает читать из сети после получения первого кадра закрытия.

Клиентское приложение должно выйти из цикла, когда сообщение об ошибке возвращается из ReadMessage. Нет необходимости проверять конкретные коды закрытия.

for {
    // Read Messages
    _, _, err := conn.ReadMessage()
    if err != nil {
        break
    }
}

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

Также, не связанного с проблемой в вопросе, используйте select {} вместо for {}, чтобы заблокировать основную горутину. Первый просто блокирует горутину. Последний вращается за счет процессорного времени.

person Cerise Limón    schedule 15.02.2016
comment
Я знаю, что это старый ответ, но это кажется неправильным: используйте select {} вместо for {}. Бывший блокирует навсегда. Последний крутится вечно. OP не читает чаны. Вот для чего нужен выбор. conn.ReadMessage() уже блокируется. - person Artur Sapek; 17.10.2017
comment
@ArturSapek Комментарий относится к использованию for {} в конце кода OP. Конструкция for {} сжигает процессорное время. select {} просто заблокируется. Вызов conn.ReadMessage() не блокирует основную горутину. - person Cerise Limón; 17.10.2017