WebSocket 基础:协议原理、与 HTTP 的区别、Node.js 搭建简易服务

好,咱们进入第二个章节。WebSocket 这东西,说实话,现在做多屏联动几乎绕不开它。我记得我刚接触实时通信那会儿,还在用轮询,那体验,啧啧……后来换了 WebSocket,才感觉打开了新世界的大门。

WebSocket 协议原理

WebSocket 是什么?说白了,它就是一个在单个 TCP 连接上实现全双工通信的协议。你想想看,传统的 HTTP 请求,永远是客户端先说话,服务器才能回。但 WebSocket 不一样,服务器可以随时主动给客户端发消息。

它的工作流程其实很简单:

  1. 握手阶段:客户端发一个特殊的 HTTP 请求,要求升级协议。
  2. 协议升级:服务器同意后,状态码返回 101,连接就从 HTTP 切换到了 WebSocket。
  3. 数据传输:之后双方就可以自由地互发数据了,不再有请求-响应的束缚。

我在项目中遇到过一个问题:有些老旧的代理服务器不认识 WebSocket 的 Upgrade 头,会直接拦截掉握手请求。嗯,这里要注意,生产环境一定要确认网络设备是否支持。

核心要点:WebSocket 的握手是 HTTP 的,但握手之后的数据传输是独立的帧协议。它用的是 ws:// 或 wss://(加密版)协议头。

与 HTTP 的区别

很多人问我,WebSocket 和 HTTP 到底有啥不一样?我习惯用一个比喻来解释:

  • HTTP 像打电话:你拨号(请求),对方接听(响应),说完就挂断。每次通话都要重新拨号。
  • WebSocket 像对讲机:按下开关就能说话,对方随时能回,而且一直在线。

具体区别,我整理了一张表:

对比项 HTTP WebSocket
通信模式 半双工(请求-响应) 全双工(双向实时)
连接状态 无状态,每次请求独立 有状态,保持长连接
开销 每次请求携带完整头部 握手后头部极小(2字节)
实时性 依赖轮询或长轮询 真正的服务器推送
适用场景 文档、API 请求 聊天、游戏、实时数据

你想想看,如果做多屏联动,用 HTTP 轮询,每秒钟发几十个请求,服务器压力大不说,延迟还高。WebSocket 一条连接搞定所有,这才是正道。

个人经验:我建议在需要实时性的场景优先考虑 WebSocket,但如果只是偶尔获取数据,HTTP 轮询反而更简单。别为了用新技术而用新技术。

Node.js 搭建简易 WebSocket 服务

理论说完了,咱们动手。我用 Node.js 搭一个最简单的 WebSocket 服务,用 ws 这个库,它是最轻量的选择。

先安装依赖:

npm init -y
npm install ws

然后写服务端代码,我习惯叫它 server.js

const WebSocket = require('ws');

const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', function connection(ws) {
  console.log('新客户端连接上了');

  // 收到消息就广播给所有人
  ws.on('message', function incoming(message) {
    console.log('收到: %s', message);
    // 广播给所有连接的客户端
    wss.clients.forEach(function each(client) {
      if (client.readyState === WebSocket.OPEN) {
        client.send('服务器说: ' + message);
      }
    });
  });

  // 发送欢迎消息
  ws.send('欢迎连接到 WebSocket 服务');
});

客户端代码更简单,浏览器原生就支持 WebSocket:

const ws = new WebSocket('ws://localhost:8080');

ws.onopen = function() {
  console.log('连接已建立');
  ws.send('你好,服务器!');
};

ws.onmessage = function(event) {
  console.log('收到消息:', event.data);
};

ws.onclose = function() {
  console.log('连接已关闭');
};

避坑指南:我曾经在生产环境遇到过一个问题——WebSocket 连接在长时间无数据交互后会被自动断开。这是因为中间的网络设备(如负载均衡器)有 idle timeout 设置。解决方案是加心跳机制,每隔一段时间发一个 ping/pong 帧保持连接活跃。

运行起来很简单:

node server.js

然后在浏览器控制台粘贴客户端代码,你就能看到双向通信了。嗯,这里要注意,如果用的是 HTTPS 页面,WebSocket 必须用 wss:// 而不是 ws://,否则浏览器会报安全错误。

心跳与重连机制

刚才提到了心跳,我顺便把这块补上。一个健壮的 WebSocket 应用,必须处理断线重连。我一般这么写:

// 服务端心跳
const interval = setInterval(function ping() {
  wss.clients.forEach(function each(ws) {
    if (ws.isAlive === false) return ws.terminate();
    ws.isAlive = false;
    ws.ping();
  });
}, 30000);

wss.on('connection', function(ws) {
  ws.isAlive = true;
  ws.on('pong', function() {
    ws.isAlive = true;
  });
});

// 客户端自动重连
function connect() {
  const ws = new WebSocket('ws://localhost:8080');
  ws.onclose = function() {
    console.log('连接断开,5秒后重连...');
    setTimeout(connect, 5000);
  };
}
connect();

这个模式我在多个项目里用过,稳定得很。说白了,WebSocket 本身不复杂,复杂的是怎么让它稳定运行。

总结一下:WebSocket 的核心就三点——握手升级、全双工通信、长连接。Node.js 搭服务用 ws 库,几分钟就能跑起来。但真正用到生产,心跳、重连、安全(wss)这些一个都不能少。

下一章咱们会深入 WebSocket 的帧协议和数据格式,到时候再聊更底层的东西。今天就先到这里,动手试试吧。