Long Polling vs WebSocket в Go: Битва за реальное время

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

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

Начать курс

Вступление: Дилемма разработчика

Представьте: вы пишете чат, биржевой терминал или онлайн-игру на Go. Клиенту нужно получать обновления мгновенно. Старый добрый AJAX с периодическим опросом (polling) отпадает — слишком много бесполезных запросов, убивающих и сервер, и батарею пользователя. Остаются два кандидата: Long Polling (длинный опрос) и WebSocket.

Оба решают задачу «реального времени», но делают это принципиально разными способами. Какой выбрать для вашего Go-приложения? Давайте разберемся без воды, с живыми примерами кода и честным разбором плюсов и минусов. Эта статья — ваш навигатор в мире real-time коммуникаций на Go.

Что такое Long Polling в Go?

Long Polling — это эволюция обычного polling. Клиент отправляет HTTP-запрос на сервер, но сервер не отвечает сразу, если данных нет. Он «зависает» (держит соединение открытым) и ждет, пока появится новое событие. Как только событие происходит — сервер отправляет ответ и клиент немедленно создает новый Long Polling запрос. Цикл повторяется.

В Go это реализуется очень элегантно благодаря горутинам и каналам. Сервер может «усыпить» обработчик запроса до появления данных.

Пример сервера Long Polling на Go

package main

import (
    "fmt"
    "net/http"
    "time"
)

// Канал, в который приходят новые сообщения
var messages = make(chan string, 10)

func longPollHandler(w http.ResponseWriter, r *http.Request) {
    select {
    case msg := <-messages:
        fmt.Fprintf(w, "Новое сообщение: %s", msg)
    case <-time.After(30 * time.Second):
        // Таймаут — если за 30 секунд сообщений не было
        http.Error(w, "Timeout", http.StatusGatewayTimeout)
    }
}

func main() {
    // Горутина, эмулирующая появление новых сообщений
    go func() {
        for {
            time.Sleep(5 * time.Second)
            messages <- fmt.Sprintf("Time: %s", time.Now().String())
        }
    }()

    http.HandleFunc("/poll", longPollHandler)
    http.ListenAndServe(":8080", nil)
}

Клиент на JavaScript будет выглядеть так: после получения ответа (или таймаута) он сразу вызывает /poll снова. Просто, надежно, работает через любые прокси и firewall.

WebSocket в Go: Полный дуплекс

WebSocket — это протокол, который после рукопожатия (HTTP Upgrade) переводит соединение в постоянный двунаправленный канал. Сервер может отправлять данные клиенту в любой момент, и клиент — серверу. Никаких повторных запросов, никаких таймаутов.

В Go стандартная библиотека не имеет встроенной поддержки WebSocket, но есть золотой стандарт — пакет gorilla/websocket. Он стабилен, хорошо документирован и используется в продакшене тысячами проектов.

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

package main

import (
    "log"
    "net/http"
    "time"

    "github.com/gorilla/websocket"
)

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

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

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

        // Отправляем ответ (или пуш-уведомление)
        err = conn.WriteMessage(websocket.TextMessage, []byte("Сервер: "+time.Now().String()))
        if err != nil {
            log.Println("Write error:", err)
            break
        }
    }
}

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

Клиентская часть (JavaScript) создает объект WebSocket и слушает события onmessage. Никаких таймеров, никаких повторов — данные текут рекой.

Сравнение: Long Polling vs WebSocket в Go

Давайте разложим по полочкам, когда что использовать.

1. Производительность и нагрузка

  • Long Polling: Каждое сообщение — это новый HTTP-запрос и ответ. На каждый запрос Go создает горутину (если используете стандартный http.Handler), что при 10 000 одновременных пользователей может создать 10 000 «висящих» горутин. Это не смертельно, но накладные расходы на парсинг HTTP-заголовков, куки и т.д. есть.
  • WebSocket: Одно соединение на всю сессию. После Upgrade тратится только трафик на полезные данные. Go справляется с десятками тысяч WebSocket соединений на одном сервере без проблем — каждая горутина легковесна.

2. Задержка (Latency)

  • Long Polling: Задержка равна времени между отправкой ответа и созданием нового запроса + время путешествия пакета. Обычно 100-500 мс, даже если данные появились мгновенно.
  • WebSocket: Задержка минимальна — данные отправляются сразу, как только сервер решит. Идеально для игр, торговли, совместного редактирования.

3. Инфраструктура и совместимость

  • Long Polling: Работает везде, где есть HTTP. Не требует специальных прокси, балансировщиков или настройки firewall. Старые корпоративные прокси (например, в банках) часто блокируют WebSocket, но Long Polling проходит без проблем.
  • WebSocket: Требует поддержки протокола на всех уровнях: браузер, прокси, балансировщик, CDN. Если у вас сложная инфраструктура с Nginx или HAProxy — придется настраивать proxy_pass с правильными таймаутами.

4. Сложность реализации

  • Long Polling: Проще некуда. Стандартный HTTP, никаких дополнительных библиотек. Go из коробки отлично справляется.
  • WebSocket: Нужна библиотека (gorilla/websocket или nhooyr.io/websocket). Нужно управлять состоянием соединения, обрабатывать разрывы, пинги/понги. Кода больше, но и возможностей — тоже.

Когда выбирать Long Polling в Go?

Long Polling — это не «устаревшая технология», а правильный инструмент для определенных задач: