WebSocket на Go: создание real-time приложений на Golang

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

WebSocket на Go: создание real-time приложений на Golang

Современные веб-приложения всё чаще требуют мгновенной передачи данных между клиентом и сервером. Традиционный HTTP-запрос-ответ не подходит для чатов, онлайн-игр, торговых платформ и мониторинга в реальном времени. На помощь приходит протокол WebSocket, который устанавливает постоянное двустороннее соединение. В этой статье на pythonlib.ru мы разберём, как создать real-time приложение на Go (Golang) с использованием WebSocket.

Go идеально подходит для этой задачи благодаря встроенной поддержке конкурентности (горутины и каналы) и высокой производительности. Вместе мы напишем простой WebSocket-сервер, реализуем чат и рассмотрим типичные проблемы.

Что такое WebSocket и зачем он нужен в Go?

WebSocket — это протокол, работающий поверх TCP. Он инициируется как HTTP-запрос (handshake), после чего соединение "апгрейдится" до постоянного двустороннего канала. В отличие от long polling или Server-Sent Events, WebSocket позволяет серверу и клиенту отправлять данные в любой момент без повторного создания соединения.

Преимущества использования WebSocket на Go:

  • Конкурентность: горутины легко обрабатывают тысячи одновременных подключений.
  • Производительность: минимальное потребление памяти на каждое соединение.
  • Простота: стандартная библиотека net/http и пакет gorilla/websocket делают код лаконичным.

WebSocket часто применяется в:

  • Чат-системах и мессенджерах
  • Онлайн-редакторах (Google Docs)
  • Торговых терминалах (биржевые котировки)
  • Играх и симуляторах

Установка и настройка WebSocket в Go

В Go нет встроенной поддержки WebSocket в стандартной библиотеке, но есть отличный пакет gorilla/websocket. Установим его:

go get github.com/gorilla/websocket

Создадим минимальный HTTP-сервер, который будет обслуживать WebSocket-соединения. Для начала напишем функцию-обработчик, которая "апгрейдит" HTTP-запрос до WebSocket.

package main

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

var upgrader = websocket.Upgrader{
    CheckOrigin: func(r *http.Request) bool {
        return true // разрешаем все источники (для разработки)
    },
}

func handleWebSocket(w http.ResponseWriter, r *http.Request) {
    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Print("Upgrade error:", err)
        return
    }
    defer conn.Close()

    // Бесконечный цикл чтения сообщений
    for {
        messageType, message, err := conn.ReadMessage()
        if err != nil {
            log.Println("Read error:", err)
            break
        }
        log.Printf("Received: %s", message)

        // Эхо-ответ: отправляем то же сообщение обратно
        if err := conn.WriteMessage(messageType, message); err != nil {
            log.Println("Write error:", err)
            break
        }
    }
}

func main() {
    http.HandleFunc("/ws", handleWebSocket)
    log.Println("Server started on :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

Этот код создаёт простой эхо-сервер. Клиент подключается к ws://localhost:8080/ws и получает обратно все отправленные сообщения.

Создание real-time чата на Go с WebSocket

Теперь усложним задачу: сделаем чат, где все подключённые клиенты видят сообщения друг друга. Для этого нам понадобится управлять списком соединений и рассылать сообщения.

Основные компоненты:

  • Hub — центральный менеджер, который регистрирует/удаляет клиентов и рассылает сообщения.
  • Client — обёртка над WebSocket-соединением, работающая в своей горутине.

Реализуем Hub с использованием каналов:

type Client struct {
    conn *websocket.Conn
    send chan []byte
}

type Hub struct {
    clients    map[*Client]bool
    broadcast  chan []byte
    register   chan *Client
    unregister chan *Client
}

func NewHub() *Hub {
    return &Hub{
        clients:    make(map[*Client]bool),
        broadcast:  make(chan []byte),
        register:   make(chan *Client),
        unregister: make(chan *Client),
    }
}

func (h *Hub) Run() {
    for {
        select {
        case client := <-h.register:
            h.clients[client] = true
        case client := <-h.unregister:
            if _, ok := h.clients[client]; ok {
                delete(h.clients, client)
                close(client.send)
            }
        case message := <-h.broadcast:
            for client := range h.clients {
                select {
                case client.send <- message:
                default:
                    close(client.send)
                    delete(h.clients, client)
                }
            }
        }
    }
}

Теперь модифицируем обработчик WebSocket, чтобы он регистрировал клиента в Hub и запускал две горутины: на чтение и на запись.

func handleChatWebSocket(hub *Hub, w http.ResponseWriter, r *http.Request) {
    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Println(err)
        return
    }

    client := &Client{conn: conn, send: make(chan []byte, 256)}
    hub.register <- client

    // Горутина записи: берёт сообщения из канала и отправляет в сокет
    go func() {
        defer func() {
            conn.Close()
        }()
        for message := range client.send {
            if err := conn.WriteMessage(websocket.TextMessage, message); err != nil {
                return
            }
        }
    }()

    // Горутина чтения: читает из сокета и отправляет в broadcast
    go func() {
        defer func() {
            hub.unregister <- client
            conn.Close()
        }()
        for {
            _, message, err := conn.ReadMessage()
            if err != nil {
                break
            }
            hub.broadcast <- message
        }
    }()
}

В main() запускаем Hub и регистрируем новый обработчик:

func main() {
    hub := NewHub()
    go hub.Run()

    http.HandleFunc("/chat", func(w http.ResponseWriter, r *http.Request) {
        handleChatWebSocket(hub, w, r)
    })

    log.Println("Chat server started on :8080")
    log.F                

Блоги

Рекомендаций по книгам