长轮询是“有数据才返回,没数据就挂起”,而非固定等待10秒;很多人误将PollResponse实现为简单延时响应。

长轮询不是“等10秒再返回”,而是“有数据才回,没数据就挂起”
很多人一写 PollResponse 就直接 ,这其实是伪长轮询:不管有没有新消息,都硬等10秒再返回。真正在用的长轮询必须阻塞在数据源上,直到有更新才响应——否则客户端收不到实时变化,服务器还白占 <a style="color:#f60; text-decoration:underline;" title="go" href="https://www.php.cn/zt/15863.html" target="_blank">go</a>routine。
- 正确做法是用带缓冲的
chan string(比如容量100),让推送方往里塞值,轮询方从 channel 读——没数据就卡住,有数据立刻走 - 别用
time.Sleep或time.After做超时兜底,除非你明确要加最大等待时间;否则应由 channel 的发送行为驱动响应时机 - HTTP 连接超时(如 Nginx 默认60秒)会强制断开,所以实际部署时,建议在 channel 阻塞逻辑外加一个 context 超时控制,避免连接被中间件掐断后 goroutine 泄漏
计数器递增后发字符串,string(counter) 是个经典坑
把整数直接转成 string(counter),结果可能是乱码(比如 counter=65 变成 "A"),因为 Go 里 string(65) 是 ASCII 字符转换,不是数字字符串化。
- 必须用
strconv.Itoa(counter)或fmt.Sprintf("%d", counter) - 如果后续要传 JSON,更推荐
json.Marshal,避免手动拼接引号和转义 - 注意
messageschannel 容量设太小(如 1)会导致推送方阻塞在messages ,尤其高并发时可能卡死整个推送流程
客户端不能“发完就忘”,要自动重连 + 清理旧 body
Go 客户端用 http.Get 轮询,常见错误是每次循环都漏掉 res.Body.Close(),导致文件描述符耗尽;或者没处理网络中断,一出错就退出循环,消息流彻底断掉。
- 每次
http.Get后必须defer res.Body.Close(),但注意:defer 在循环里会累积,得改成显式res.Body.Close() - 错误处理不能只
continue,要加退避(如指数退避),否则服务重启瞬间涌进几百个并发请求 - 别用
ioutil.ReadAll(已废弃),改用io.ReadAll;如果响应体很大,考虑流式读取或加 size 限制
生产环境绕不开:连接数、超时、反向代理兼容性
本地跑通不等于能上线。Nginx、ALB、CDN 这些中间件默认对长连接很“不友好”,动不动就 30–60 秒切断空闲连接。
立即学习“go语言免费学习笔记(深入)”;
- 服务端响应头务必加
Connection: keep-alive和Cache-Control: no-cache,否则某些代理会缓存响应 - Nginx 配置要调大:
proxy_read_timeout 300、proxy_buffering off、proxy_http_version 1.1 - Golang HTTP server 的
ReadTimeout和WriteTimeout建议设为 0(禁用),靠 context 控制单次轮询生命周期,而不是全局连接超时
真正难的不是写通一个 /poll 接口,而是让成百上千个连接稳定挂住、不泄漏、不堆积、不被中间件误杀——这些细节不出现在 demo 里,但一上线全冒出来。










