短轮询在低频小用户场景下足够且更稳;长轮询需用channel配合context.WithTimeout防goroutine泄漏。

短轮询够不够用?别急着写长连接
大多数刚上手的推送需求,其实根本不需要 WebSocket 或 SSE——短轮询(pollHandler)在低频、小用户量场景下更稳、更易调试。比如后台运营系统给 200 个内部员工发通知,每 30 秒拉一次,后端连 Redis 都可以省掉,直接用 map[string][]string 存消息就行。
容易踩的坑:
• 客户端没做防抖,用户切页又切回导致瞬间并发多个请求;
• 后端没清空已读消息(如示例里直接赋空切片 messages[userID] = []string{}),下次 poll 还是返回旧内容;
• 忘记设 Content-Type: application/json,前端 fetch().json() 报错 Unexpected token。
长轮询怎么避免 goroutine 泄漏?看 channel 和 context 怎么配
长轮询本质是“挂起等待”,Go 里最自然的做法就是用 chan string + select + context.WithTimeout。关键不是“等消息”,而是“等消息或超时”——否则客户端断网、页面关掉,goroutine 就永远卡在 上。
实操建议:
• 每个 userID 对应一个带缓冲的 channel(如 make(chan string, 10)),防止发消息时阻塞;
• 用 sync.Mutex 保护全局 userChannels map,别在 handler 里直接读写 map;
• 超时时间设 25–30 秒,比 Nginx 默认 proxy_read_timeout(30s)略短,避免被中间代理提前断连。
SSE 适合什么场景?别拿它传二进制或大 JSON
SSE 的优势是浏览器原生支持、自动重连、服务端逻辑轻量,但它只支持文本流。如果你要推的是图片 URL、日志片段、或带格式的富文本通知,SSE 很合适;但要是想推一个 50KB 的结构化事件对象,或者需要客户端主动发指令(比如“标记已读”),那它就不如 WebSocket。
注意点:
• 响应头必须包含 Content-Type: text/event-stream 和 Cache-Control: no-cache;
• 每条消息以 data: {...}\n\n 结尾,少两个换行,浏览器就收不到;
• Chrome 对单个 SSE 连接有内存限制,持续推 10 分钟以上小消息,可能触发 net::ERR_INSUFFICIENT_RESOURCES。
WebSocket 真正难的不是握手,是连接生命周期管理
用 gorilla/websocket 写通 /ws 接口只要 20 行,但真实项目里 80% 的工作量都在处理:谁连上了、谁断了、断了要不要重连、消息发到一半连接没了怎么办、怎么踢掉重复登录的老连接。
立即学习“go语言免费学习笔记(深入)”;
推荐做法:
• 给每个连接分配唯一 key(如 "user:123:session_abc"),存进 sync.Map 而非普通 map;
• 心跳用 conn.SetPingHandler + conn.SetPongHandler,别自己 time.Ticker 发 ping;
• 读消息的 goroutine 退出时,务必 close 对应的 messageChan,否则写协程可能 forever blocked。
最常被忽略的一点:没有做连接数软限。单机跑满 65535 个 fd 不现实,但放任客户端疯狂重连,几秒就能把服务器拖垮。上线前至少加个 golang.org/x/net/netutil.LimitListener。










