Создайте службу WebSocket, используя язык Go

Go
Создайте службу WebSocket, используя язык Go

Как использовать сегодняGoсоздание языкаWebSocketуслуги, о которых кратко рассказано в первых двух частях статьиWebSocketсоглашение иGoКак создается стандартная библиотекаWebSocketСлужить. В третьей части практической части мы использовалиgorilla/websocketБиблиотеки помогают нам быстро строитьWebSocketСервисы, которые помогают инкапсулировать использованиеGoРеализация стандартной библиотекиWebSocketБазовая логика, связанная с услугами, позволяет нам освободиться от громоздкого базового кода и быстро строить в соответствии с потребностями бизнеса.WebSocketСлужить.

Исходный код для каждой статьи серии Web Programming сыграл соответствующую версию пакета для вашей справки. Отвечатьgohttp10Получить исходный код этой статьи

Введение в веб-сокеты

WebSocketпротокол связи через единыйTCPСоединение обеспечивает полнодуплексный канал связи. иHTTPпо сравнению с,WebSocketВам не нужно отправлять запрос, чтобы получить ответ. Это позволяет двунаправленный поток данных, так что вы просто ждете сообщения от сервера. когдаWebsocketОн отправит вам сообщение, когда он будет доступен. Для сервисов, требующих непрерывного обмена данными (таких как мессенджеры, онлайн-игры и торговые системы в реальном времени),WebSocketявляется хорошим решением.WebSocketСоединение запрашивается браузером, отвечает сервером, и соединение устанавливается. Этот процесс широко известен как рукопожатие. WebSocketСпециальные заголовки в : требуют только одного рукопожатия между браузером и сервером для установления соединения, которое будет оставаться активным в течение всего времени существования.WebSocketрешать многие в режиме реального времениWebразработанные головоломки, и с традиционнымиHTTPПо сравнению с, имеет много преимуществ:

  • Облегченные заголовки сокращают накладные расходы на передачу данных.
  • не замужемWebКлиенту нужен только одинTCPсоединять.
  • WebSocketСервер может отправлять данные наWebклиент.

Протокол WebSocket относительно прост в реализации. оно используетHTTPПротокол выполняет начальное рукопожатие. После успешного рукопожатия соединение устанавливается.WebSocketглавное использовать оригиналTCPЧтение/запись данных.

Запрос клиента выглядит так:

GET /chat HTTP/1.1
    Host: server.example.com
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
    Sec-WebSocket-Protocol: chat, superchat
    Sec-WebSocket-Version: 13
    Origin: http://example.com

Вот ответ сервера:

HTTP/1.1 101 Switching Protocols
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
    Sec-WebSocket-Protocol: chat

Как создать приложение WebSocket в Go

На основе встроенного языка Gonet/httpПитание библиотекиWebSocketсервер, вам нужно:

  • инициировать рукопожатие
  • Получение фрейма данных от клиента
  • Отправить фрейм данных клиенту
  • тесное рукопожатие

инициировать рукопожатие

Во-первых, давайте создадимWebSocketКонечная точкаHTTPОбработчик:

// HTTP server with WebSocket endpoint
func Server() {
        http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
            ws, err := NewHandler(w, r)
            if err != nil {
                 // handle error
            }
            if err = ws.Handshake(); err != nil {
                // handle error
            }
        …

затем инициализируйтеWebSocketструктура.

Первоначальный запрос на рукопожатие всегда исходит от клиента. сервер подтвержденWebSocketПосле запроса он должен ответить рукопожатием.

Помните, что вы не можете использоватьhttp.ResponseWriterНапишите ответ, потому что, как только он начинает отправлять ответ, он закрывает свой базовыйTCPподключение (этоHTTPЭто определяется механизмом работы протокола, и соединение закрывается после отправки ответа).

Поэтому вам нужно использоватьHTTPугнать(hijack). Угоняя, можно завладеть базовымTCPобработчик соединения иbufio.Writer. Это позволяет отключитьTCPЧтение и запись данных при подключении.

// NewHandler initializes a new handler
func NewHandler(w http.ResponseWriter, req *http.Request) (*WS, error) {
        hj, ok := w.(http.Hijacker)
        if !ok {
            // handle error
        }                  .....
}

Для завершения рукопожатия сервер должен ответить соответствующими заголовками.

// Handshake creates a handshake header
    func (ws *WS) Handshake() error {

        hash := func(key string) string {
            h := sha1.New()
            h.Write([]byte(key))
            h.Write([]byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))

        return base64.StdEncoding.EncodeToString(h.Sum(nil))
        }(ws.header.Get("Sec-WebSocket-Key"))
      .....
}

Инициировано клиентомWebSocketиспользуется для запросов на подключениеSec-WebSocket-keyОн генерируется случайным образом и кодируется Base64. После принятия запроса серверу необходимо привязать к этому ключу фиксированную строку. Предположим, что секретный ключx3JJHMbDL1EzLkh9GBhXDw==. В этом примере вы можете использоватьSHA-1Вычислите двоичное значение и используйтеBase64закодировать его. получитьHSmrc0sMlYUkAGmm5OPpG2HaGWk=. затем используйте его какSec-WebSocket-AcceptЗначение заголовка ответа.

передать кадр данных

После успешного завершения рукопожатия ваше приложение может считывать данные с клиента или записывать данные на него.Спецификация веб-сокетаОпределяет определенный формат кадра, используемый между клиентом и сервером. Вот битовая структура кадра:

img{512x368}
Рисунок: Битовая комбинация передаваемого кадра данных

Используйте следующий код для декодирования полезной нагрузки клиента:

// Recv receives data and returns a Frame
    func (ws *WS) Recv() (frame Frame, _ error) {
        frame = Frame{}
        head, err := ws.read(2)
        if err != nil {
            // handle error
        }

Эти строки кода, в свою очередь, позволяют кодировать данные:

// Send sends a Frame
    func (ws *WS) Send(fr Frame) error {
        // make a slice of bytes of length 2
        data := make([]byte, 2)

        // Save fragmentation & opcode information in the first byte
        data[0] = 0x80 | fr.Opcode
        if fr.IsFragment {
            data[0] &= 0x7F
        }
        .....

тесное рукопожатие

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

// Close sends a close frame and closes the TCP connection
func (ws *Ws) Close() error {
    f := Frame{}
    f.Opcode = 8
    f.Length = 2
    f.Payload = make([]byte, 2)
    binary.BigEndian.PutUint16(f.Payload, ws.status)
    if err := ws.Send(f); err != nil {
        return err
    }
    return ws.conn.Close()
}

Быстро создавайте с помощью сторонних библиотекWebSocketСлужить

Из предыдущих глав видно, чтоGoавтономныйnet/httpреализация библиотекиWebSocketСлужба все еще слишком сложна. К счастью, пар много.WebSocketХорошо поддерживаемые сторонние библиотеки могут значительно сократить нашу низкоуровневую работу по написанию кода. Здесь мы используемgorilla web toolkitЕще одна библиотека для семьиgorilla/websocketреализовать нашиWebSocketобслуживание, построение простогоEchoСлужить(echoЭто означает эхо, которое отправляет клиент, а сервер отправляет сообщение обратно клиенту).

мы вhttp_demoПроектhandlerСоздайте новый в каталогеwsподкаталоги для храненияWebSocketОбработчик запроса, соответствующий маршруту, связанному со службой.

Добавьте два маршрута:

  • /ws/echo echoМаршрут для службы WebSocket приложения.
  • /ws/echo_display echoМаршрут к клиентской странице приложения.

Создайте сервер WebSocket

// handler/ws/echo.go
package ws

import (
	"fmt"
	"github.com/gorilla/websocket"
	"net/http"
)

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
}

func EchoMessage(w http.ResponseWriter, r *http.Request) {
	conn, _ := upgrader.Upgrade(w, r, nil) // 实际应用时记得做错误处理

	for {
		// 读取客户端的消息
		msgType, msg, err := conn.ReadMessage()
		if err != nil {
			return
		}

		// 把消息打印到标准输出
		fmt.Printf("%s sent: %s\n", conn.RemoteAddr(), string(msg))

		// 把消息写回客户端,完成回音
		if err = conn.WriteMessage(msgType, msg); err != nil {
			return
		}
	}
}
  • connТип переменной*websocket.Conn, websocket.Connтип, используемый для представленияWebSocketсоединять. серверное приложение отHTTPвызов обработчика запросаUpgrader.Upgradeспособ получить*websocket.Conn

  • вызов подключенWriteMessageиReadMessageспособы отправки и получения сообщений. вышеmsgПосле его получения он отправляется обратно клиенту ниже.msgТип[]byte.

Создайте клиент WebSocket

Обработчик запроса, соответствующий маршрутизации страницы внешнего интерфейса, выглядит следующим образом и возвращает напрямуюviews/websockets.htmlПросто отобразите страницу в браузере.

// handler/ws/echo_display.go
package ws

import "net/http"

func DisplayEcho(w http.ResponseWriter, r *http.Request) {
	http.ServeFile(w, r, "views/websockets.html")
}

websocket.htmlгде нам нужно использоватьJavaScriptсоединятьWebScoketСервис отправляет и принимает сообщения, выложу только из соображений экономии места.JSКод готов, полный код можно скачать с официального аккаунта через пароль в этом разделе.

<form>
    <input id="input" type="text" />
    <button onclick="send()">Send</button>
    <pre id="output"></pre>
</form>
...
<script>
    var input = document.getElementById("input");
    var output = document.getElementById("output");
    var socket = new WebSocket("ws://localhost:8000/ws/echo");

    socket.onopen = function () {
        output.innerHTML += "Status: Connected\n";
    };

    socket.onmessage = function (e) {
        output.innerHTML += "Server: " + e.data + "\n";
    };

    function send() {
        socket.send(input.value);
        input.value = "";
    }
</script>
...

зарегистрировать маршрут

После того, как серверная и клиентская программы готовы, регистрируем маршруты и соответствующие обработчики запросов для них по заранее согласованному пути:

// router/router.go
func RegisterRoutes(r *mux.Router) {
    ...
    wsRouter := r.PathPrefix("/ws").Subrouter()
    wsRouter.HandleFunc("/echo", ws.EchoMessage)
    wsRouter.HandleFunc("/echo_display", ws.DisplayEcho)
}

Тестовая проверка

Доступ после перезапуска службыhttp://localhost:8000/ws/echo_display, любое сообщение, введенное в поле ввода, может быть снова отображено в браузере.

图片

Сервер распечатывает полученное сообщение на терминале, а затем вызываетwriteMessageСообщение отправляется обратно клиенту, и запись можно просмотреть в терминале.

image-20200316142506287

Суммировать

WebSocketОн очень широко используется в приложениях, которые сейчас часто обновляются.WebSocketПрограммирование также является важным навыком, которым нам необходимо овладеть. Практические упражнения из статьи немного проще, а проверок на ошибки и безопасности нет. В основном, чтобы прояснить общий процесс. оgorilla/websocketДля получения более подробной информации вам необходимо проверить официальную документацию при его использовании.

Ссылка на ссылку:

Arran Ascension.com/blog/how-to…

woohoo.gorilla toolkit.org/pkg/Web sock…  

Предыдущий отзыв

Глубокое погружение в написание HTTP-серверов в Go

Подробное руководство по применению библиотеки шаблонов Go

Создайте статический файловый сервер в Go

Внедрение управления сеансами на стороне клиента с помощью SecureCookie

img