WebSockets в Go: Пишем real-time приложение с нуля

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

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

Начать курс

Почему Go и WebSockets — идеальный тандем для real-time?

Представьте себе ситуацию: вы запускаете онлайн-чат, биржевой терминал или многопользовательскую игру. Пользователи ждут мгновенной реакции. Никто не хочет обновлять страницу вручную или ждать 5 секунд, пока придет ответ от сервера. Здесь на сцену выходят WebSockets.

Go (Golang) отлично подходит для этой задачи. Его легковесные горутины (goroutines) позволяют держать десятки тысяч одновременных соединений без просадки производительности. А стандартная библиотека net/http в паре с пакетом gorilla/websocket превращает написание real-time приложений в чистое удовольствие.

В этой статье мы разберемся, как работает WebSocket в Go, напишем простой чат и обсудим best practices. Поехали!

Что такое WebSocket и чем он лучше HTTP?

Если кратко — WebSocket это протокол, который устанавливает постоянное двустороннее соединение между клиентом (браузером) и сервером. В отличие от классического HTTP, где клиент всегда инициирует запрос, WebSocket позволяет серверу самому отправлять данные клиенту в любой момент.

Плюсы очевидны:

  • Мгновенная доставка — нет задержек на установку нового соединения.
  • Меньше трафика — заголовки отправляются только один раз при handshake.
  • Эффективность — одно соединение вместо тысячи HTTP-запросов.

Go отлично справляется с управлением этими соединениями. Вместо того чтобы плодить потоки ОС (что дорого), Go использует горутины. Одна горутина — одно соединение. Тысячи горутин работают на нескольких потоках ОС, потребляя минимум памяти.

Установка и подготовка проекта на Go

Предположим, Go у вас уже установлен. Если нет — brew install go или скачайте с golang.org.

Создадим новый модуль:

mkdir go-websocket-chat
cd go-websocket-chat
go mod init go-websocket-chat

Теперь установим самую популярную библиотеку gorilla/websocket:

go get github.com/gorilla/websocket

Готово. Теперь у нас есть всё для создания real-time приложения на Go.

Пишем WebSocket сервер на Go

Начнем с самого простого — эхо-сервера. Клиент отправляет сообщение, сервер его возвращает. Это поможет понять базовую механику.

Шаг 1: Структура и апгрейд соединения

WebSocket соединение начинается с HTTP-запроса (handshake). Сервер должен «апгрейднуть» его до WebSocket. В Go это делается через http.Hijacker или через удобный метод Upgrader из пакета gorilla/websocket.

Создайте файл main.go:

package main

import (
    "fmt"
    "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("Ошибка апгрейда:", err)
        return
    }
    defer conn.Close()

    // Бесконечный цикл чтения и записи
    for {
        messageType, message, err := conn.ReadMessage()
        if err != nil {
            log.Println("Ошибка чтения:", err)
            break
        }
        log.Printf("Получено: %s", message)

        // Отправляем то же самое обратно
        err = conn.WriteMessage(messageType, message)
        if err != nil {
            log.Println("Ошибка записи:", err)
            break
        }
    }
}

func main() {
    http.HandleFunc("/ws", handleWebSocket)
    fmt.Println("Сервер запущен на :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

Запустите: go run main.go.

Теперь откройте консоль браузера (F12) и выполните JavaScript код для подключения:

const socket = new WebSocket("ws://localhost:8080/ws");
socket.onopen = () => {
    socket.send("Привет, Go!");
};
socket.onmessage = (event) => {
    console.log("Ответ:", event.data);
};

Если в консоли появилось «Привет, Go!» — всё работает. Это основа любого real-time приложения на Go.

Создаем многопользовательский чат на Go

Эхо-сервер — это скучно. Давайте сделаем полноценный чат, где сообщения одного клиента видят все остальные. Для этого нам понадобится управлять списком подключений.

Шаг 2: Управление подключениями

Создадим структуру Hub, которая хранит всех активных клиентов и рассылает сообщения.

type Client struct {
    hub  *Hub
    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),
    }
}

Теперь реализуем метод Run, который будет слушать каналы и управлять клиентами:

func (h *Hub) Run() {
    for {
        select {
        case client := <-h.register:
            h.clients[client] = true
            log.Println("Новый клиент подключен")
        case client := <-h.unregister:
            if _, ok := h.clients[client]; ok {
                delete(h.clients, client)
                close(client.send)
                log.Println("Клиент отключен")
            }
        case message := <-h.broadcast:
            for client := range h.clients {
                select {
                case client.send <- message:
                default:
                    close(client.send)
                    delete(h.clients, client)
                }
            }
        }
    }
}

Шаг 3: Чтение и запись для каждого клиента

Каждый клиент будет работать в двух горутинах: одна читает из WebSocket и отправляет в broadcast, другая пишет из канала send обратно в WebSocket.

func (c *Client) readPump() {
    defer func() {
        c.hub.unregister <- c
        c.conn.Close()
    }()
    for {
        _, message, err := c.conn.ReadMessage()
        if err != nil {
            break
        }
        // Рассылаем всем
        c.hub.broadcast <- message
    }
}

func (c *Client) writePump() {
    defer c.conn.Close()
    for message := range c.send {
        err := c.conn.WriteMessage(websocket.TextMessage, message)
        if err != nil {
            break
        }
    }
}

Шаг 4: Собираем всё вместе

Теперь обновим handleWebSocket:

Блоги

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