Вступление: Дилемма разработчика
Представьте: вы пишете чат, биржевой терминал или онлайн-игру на 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 — это не «устаревшая технология», а правильный инструмент для определенных задач:
- Вам нужно быстро сделать прототип или MVP.
- Ваши клиенты работают из-за строгих корпоративных firewall.
- У вас редкие обновления (раз в 10-30 секунд) — например, уведомления о новых письмах.
-
Блоги
Рекомендаций по книгам
Читайте также