解决WebSocket通信一分钟后通信中断问题:添加心跳机制

4 阅读2分钟

为什么 1 分钟就会断开?

在客户端和服务器之间,往往存在 Nginx、云厂商的负载均衡器(如 AWS ALB)等反向代理。它们都有一个“空闲超时时间(Idle Timeout)”的默认设置。如果在这段时间内没有数据交互,代理就会强制断开 TCP 连接。

  • 常见默认值:Nginx、AWS ALB、Azure 等通常默认就是 60秒。这就是你遇到“刚好 1 分钟断开”的根本原因。

解决方案:添加心跳机制

心跳机制的原理非常简单:客户端和服务器每隔一小段时间(比如 30 秒),互相发送一个极小的数据包(通常叫 ping 和 pong)。这就像两个人打电话时偶尔“喂”一声,告诉对方“我还在线,别挂电话”。

1. 前端客户端实现(JavaScript)

由于浏览器的 WebSocket API 无法直接操作底层的协议级 ping/pong,我们需要在应用层自己实现。通常建议每 30 秒发送一次心跳(小于 60 秒的超时阈值)。

class WebSocketManager {
  constructor(url) {
    this.url = url;
    this.ws = null;
    this.heartbeatTimer = null;
    this.heartbeatInterval = 30000; // 30秒发送一次心跳
  }

  connect() {
    this.ws = new WebSocket(this.url);
    
    this.ws.onopen = () => {
      console.log('WebSocket 连接成功');
      this.startHeartbeat(); // 连接成功后开启心跳
    };

    this.ws.onmessage = (event) => {
      const data = JSON.parse(event.data);
      // 收到服务器的 pong 响应,说明连接健康
      if (data.type === 'pong') {
        console.log('收到心跳响应,连接正常');
        return;
      }
      // 处理其他业务消息...
    };

    this.ws.onclose = () => {
      console.log('连接已断开');
      this.stopHeartbeat(); // 断开后停止心跳,防止内存泄漏
    };
  }

  // 开启心跳
  startHeartbeat() {
    this.stopHeartbeat(); // 先清除可能存在的旧定时器
    this.heartbeatTimer = setInterval(() => {
      if (this.ws && this.ws.readyState === WebSocket.OPEN) {
        // 发送心跳包
        this.ws.send(JSON.stringify({ type: 'ping' }));
      }
    }, this.heartbeatInterval);
  }

  // 停止心跳
  stopHeartbeat() {
    if (this.heartbeatTimer) {
      clearInterval(this.heartbeatTimer);
      this.heartbeatTimer = null;
    }
  }
}

2. 后端服务器响应

服务器端在接收到 type: 'ping' 的消息后,需要立即回复一个 type: 'pong' 的消息。以 Node.js 为例:

ws.on('message', (message) => {
  const data = JSON.parse(message);
  if (data.type === 'ping') {
    // 收到心跳,立即回复 pong
    ws.send(JSON.stringify({ type: 'pong' }));
  }
  // 处理其他业务逻辑...
});

(注:如果你使用的是 Spring Boot 等框架,也可以通过配置 STOMP 协议的 setHeartbeatValue 来实现标准的心跳机制。)

⚙️ 配套优化:检查 Nginx 配置

除了前端加心跳,如果你能控制服务器,强烈建议检查并修改 Nginx 的超时配置。将 proxy_read_timeout 和 proxy_send_timeout 设置得比心跳间隔更长(例如 75 秒):

location /ws-endpoint {
    proxy_pass http://backend;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    
    # 将超时时间延长至 75 秒,给心跳留出足够的缓冲时间
    proxy_read_timeout 75s;
    proxy_send_timeout 75s;
}

总结建议:保留 WebSocket 长连接,在前端加入 30 秒间隔的应用层心跳机制,并确保后端能响应 pong。这套组合拳能完美解决 1 分钟断连的问题,同时保持实时通信的高性能。