长轮询不能直接阻塞等待,因http处理函数超时会被代理断开;须用responsewriter手动控制写入,配合context.withtimeout和select监听事件channel或超时,一次性响应。

长轮询为什么不能直接用 http.ServeHTTP 阻塞等待
因为 Go 的 HTTP 处理函数必须在返回前结束,否则连接会超时、被中间代理(如 Nginx)断开,或触发 context.DeadlineExceeded。真正的长轮询不是“等数据”,而是“挂起响应直到有数据或超时”。
- 必须用
responseWriter手动控制写入时机,且不能提前调用WriteHeader或写入 body 后再等 - 推荐搭配
context.WithTimeout控制单次请求最长等待时间(通常 20–60 秒) - 别在 handler 里起 goroutine 后直接 return —— 这会导致响应体没写就关闭连接,客户端收不到任何内容
如何安全地在 handler 中等待事件并响应
核心是:用 channel 接收推送事件,用 select 等待 channel 或 context 超时,只在收到事件或超时后一次性写出响应。
- 事件 channel 类型建议为
chan []byte或chan struct{ data []byte; id string },避免共享内存竞争 - 不要用全局 map 存 connection → channel 映射来实现广播 —— 并发写 map panic 风险高;改用
sync.Map或加锁 - 每次请求应生成独立的
donechannel,注册到事件分发器中,并在 handler 返回前注销(防止内存泄漏) - 示例关键片段:
select { case data :=
客户端发起长轮询时必须设置哪些关键参数
服务端做得再稳,客户端配错也会导致重连风暴或假死。
- 务必禁用默认超时:浏览器 fetch 默认 timeout 是 0(无限),但某些 SDK 或移动端 WebView 会设为 30s,需显式设为
timeout: 0或足够大值(如 65000) - 不要复用
XMLHttpRequest实例 —— 每次请求都 new 一个,否则旧请求 abort 可能干扰新连接 - HTTP/1.1 下必须设置
Connection: keep-alive,服务端也要确保ResponseWriter不主动关闭连接(Go 默认保持) - 首次请求失败后,建议指数退避重试(如 1s → 2s → 4s),而不是立即重连
为什么不用 net/http.Server.IdleTimeout 做长轮询保活
这个字段控制的是“空闲连接最大存活时间”,不是单个请求的等待上限。它对长轮询几乎无效,反而可能让活跃的长连接被误杀。
-
IdleTimeout在整个连接没有读写时才计时,而长轮询的请求始终处于“处理中”状态,不计入 idle - 真正要调的是
ReadTimeout和WriteTimeout—— 但它们也得设得比业务超时长(比如请求最长等 30s,那ReadTimeout至少设 35s),否则连接会被 server 主动关掉 - 更稳妥的做法是:不依赖 Server 级超时,完全由 handler 内部
context.WithTimeout控制单次等待,并在超时后主动 write + return
proxy_read_timeout 是 60 秒,如果服务端等待 55 秒,Nginx 可能在第 61 秒切断连接,而服务端还浑然不觉 —— 这类跨层超时对齐,比代码逻辑更难排查。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











