引言:开发者的两难选择
\n\n设想一下:您正在用Go编写一个聊天应用、股票交易终端或在线游戏。客户端需要即时获取更新。传统的AJAX轮询方式已经过时——会产生大量无用的请求,既拖垮服务器,又消耗用户电量。现在有两个候选方案:Long Polling(长轮询)和WebSocket。
\n\n两者都能解决“实时”问题,但实现方式截然不同。对于您的Go应用程序,该如何选择?让我们通过真实的代码示例和诚实的优缺点分析来一探究竟。本文将是您在Go实时通信世界中的导航指南。
\n\nGo中的Long Polling是什么?
\n\nLong Polling是普通轮询的进化版。客户端向服务器发送HTTP请求,但如果服务器没有数据,它不会立即响应。它会“挂起”(保持连接打开),等待新事件发生。一旦事件发生,服务器发送响应,客户端立即发起一个新的Long Polling请求。如此循环往复。
\n\n在Go中,由于goroutine和channel的存在,实现起来非常优雅。服务器可以在数据到达前“休眠”请求处理程序。
\n\nGo Long Polling服务器示例
\n\npackage main\n\nimport (\n "fmt"\n "net/http"\n "time"\n)\n\n// 接收新消息的通道\nvar messages = make(chan string, 10)\n\nfunc longPollHandler(w http.ResponseWriter, r *http.Request) {\n select {\n case msg := <-messages:\n fmt.Fprintf(w, "新消息: %s", msg)\n case <-time.After(30 * time.Second):\n // 超时——如果30秒内没有消息\n http.Error(w, "超时", http.StatusGatewayTimeout)\n }\n}\n\nfunc main() {\n // 模拟新消息产生的goroutine\n go func() {\n for {\n time.Sleep(5 * time.Second)\n messages <- fmt.Sprintf("时间: %s", time.Now().String())\n }\n }()\n\n http.HandleFunc("/poll", longPollHandler)\n http.ListenAndServe(":8080", nil)\n}\n\n\nJavaScript客户端看起来是这样的:收到响应(或超时)后,立即再次调用/poll。简单、可靠,能通过任何代理和防火墙工作。
Go中的WebSocket:全双工通信
\n\nWebSocket是一种协议,在握手(HTTP升级)后,将连接转换为永久的双向通道。服务器可以随时向客户端发送数据,客户端也可以随时向服务器发送数据。无需重复请求,没有超时。
\n\nGo标准库没有内置的WebSocket支持,但有一个黄金标准——gorilla/websocket包。它稳定、文档完善,并被数千个项目用于生产环境。
Go WebSocket服务器示例
\n\npackage main\n\nimport (\n "log"\n "net/http"\n "time"\n\n "github.com/gorilla/websocket"\n)\n\nvar upgrader = websocket.Upgrader{\n CheckOrigin: func(r *http.Request) bool {\n return true // 用于开发环境。生产环境中请检查!\n },\n}\n\nfunc wsHandler(w http.ResponseWriter, r *http.Request) {\n conn, err := upgrader.Upgrade(w, r, nil)\n if err != nil {\n log.Print("升级失败:", err)\n return\n }\n defer conn.Close()\n\n // 读写循环\n for {\n // 从客户端读取消息\n _, message, err := conn.ReadMessage()\n if err != nil {\n log.Println("读取错误:", err)\n break\n }\n log.Printf("收到: %s", message)\n\n // 发送响应(或推送通知)\n err = conn.WriteMessage(websocket.TextMessage, []byte("服务器: "+time.Now().String()))\n if err != nil {\n log.Println("写入错误:", err)\n break\n }\n }\n}\n\nfunc main() {\n http.HandleFunc("/ws", wsHandler)\n log.Pr