Iris WebSocket 默认不自动重连,需前端实现带指数退避的重连逻辑;心跳须由客户端定时发送业务格式ping消息,服务端响应pong以保活连接。

WS连接断开后 Iris 默认不自动重连
Iris 的 websocket.Server 本身不提供重连逻辑,它只负责单次连接的生命周期管理。一旦网络抖动、服务重启或客户端切后台,conn.Close() 被触发,连接就彻底断了,不会自己拉起新连接。你看到的“掉线没反应”,不是 bug,是设计如此——重连策略必须由业务层控制。
常见错误现象:onClose 回调被触发,但页面没重试动作;手动刷新才恢复通信;移动端切后台再回来 WebSocket 状态为 CLOSED 且无后续动作。
- 重连不能写在服务端,得由前端主动发起(Iris 只响应握手请求)
- 不要依赖
conn.IsClosed()做服务端轮询判断——它只反映当前连接状态,无法感知客户端是否已断连 - 服务端无需“保持连接活跃”,那是客户端和网络层的事
前端用原生 WebSocket 实现带退避的重连
核心是监听 onclose 和 onerror,并在回调里调用重建逻辑。关键点在于避免密集重试压垮服务端,需加入指数退避 + 最大重试次数限制。
let ws = null;
let reconnectTimeout = null;
const MAX_RECONNECT_ATTEMPTS = 5;
let attempt = 0;
<p>function connect() {
ws = new WebSocket("ws://localhost:8080/ws");</p><p>ws.onopen = () => {
console.log("connected");
attempt = 0; // 成功则重置计数
};</p><p>ws.onclose = () => {
if (attempt < MAX_RECONNECT_ATTEMPTS) {
const delay = Math.min(1000 * Math.pow(2, attempt), 30000); // 1s → 2s → 4s → 8s → 16s,上限30s
console.log(<code>reconnect in ${delay}ms (attempt ${attempt + 1})</code>);
reconnectTimeout = setTimeout(connect, delay);
attempt++;
}
};</p><p>ws.onerror = (err) => {
console.error("ws error:", err);
// 不在此处重连,交给 onclose 统一处理(部分浏览器 onerror 后不一定触发 onclose)
};
}</p><p>connect();
- 别在
onerror里直接重连:某些情况(如 DNS 失败)会频繁触发,导致雪崩 - 用
setTimeout而非setInterval:确保前一次重连失败后再启动下一次,避免并发连接 - Iris 后端无需特殊配置,只要路由注册了
iris.Websocket.Handler(...),每次新连接都会被正常接入
心跳包必须由客户端发,服务端只响应
Iris 的 WebSocket 没有内置心跳机制,也**不主动 ping 客户端**。如果你发现连接被 Nginx/ALB/运营商中间件静默断开,根源是空闲超时(比如 60 秒无数据),解决方案是让客户端定时发 ping 帧,服务端收到后立刻回 pong —— 这能维持 TCP 连接活跃,且不干扰业务消息流。
注意:WebSocket.prototype.ping() 是无效的(浏览器不支持该方法),必须用业务消息模拟心跳,比如发送 {"type":"ping"},服务端解析后回复 {"type":"pong"}。
- 服务端不用定时器去 ping 客户端:Iris 不暴露底层
net.Conn的SetDeadline控制权,强行轮询易出错 - 心跳间隔建议 ≤ 中间件超时时间的 2/3(例如 Nginx
proxy_read_timeout 60,则客户端每 40 秒发一次 ping) - 别用
conn.WriteJSON()发心跳:JSON 序列化有开销;直接conn.WriteMessage(websocket.TextMessage, []byte{'{','"','t','y','p','e','"',';',...})更轻量(但可读性差,权衡取舍)
服务端如何安全处理心跳与业务消息混杂
客户端发来的 ping/pong 消息和其他业务数据走同一条通道,服务端必须在 conn.ReadMessage() 循环里做类型分发。重点是:别让心跳逻辑阻塞主消息循环,也别在处理心跳时意外关闭连接。
ws.OnConnection(func(c iris.WebsocketConnection) {
go func() {
for {
_, msg, err := c.ReadMessage()
if err != nil {
if !websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
return // 正常断开,退出
}
break
}
<pre class="brush:php;toolbar:false;"> var pkt map[string]string
if err := json.Unmarshal(msg, &pkt); err != nil {
continue // 非法 JSON,跳过
}
switch pkt["type"] {
case "ping":
c.Emit("pong", nil) // 或直接 WriteMessage 回复
case "chat":
// 业务逻辑
default:
// 忽略未知 type
}
}}() })
- 心跳响应必须用
Emit或WriteMessage,不能用WriteJSON后忘记return,否则可能把 pong 当作业务消息二次处理 - 不要在
case "ping"里调c.Close():有些旧版文档示例会误写,这会导致连接立即中断 - 如果业务消息量极低,仅靠心跳保活即可;若消息频繁,则心跳可省略——TCP Keepalive + 消息流本身已足够防中间件断连
Iris 的 WebSocket 是轻量级封装,它的“简单”意味着你要亲手控制重连节奏、心跳时机和错误边界。最容易被忽略的是:把重连责任错当成服务端该做的事,以及用浏览器不支持的 ping() 方法白忙一场。


















