一、WebSocket 核心概念

1.1 什么是 WebSocket?

WebSocket 是一种全双工实时通信协议,允许客户端与服务器在单个 TCP 连接上进行双向数据传输。与 HTTP 的“请求-响应”模式不同,WebSocket 连接建立后,双方可随时主动发送数据,无需频繁建立连接,适合实时聊天、数据监控等场景。

  • 协议标识:非加密连接用 ws://,加密连接用 wss://(类似 HTTPS)。
  • 与 HTTP 的关系:基于 HTTP 握手建立连接(通过 Upgrade 头升级协议),之后脱离 HTTP 独立通信。
  • 核心优势:低延迟、持久连接、双向通信、减少带宽消耗。

1.2 WebSocket 通信流程

  1. 握手阶段:客户端发送 HTTP 请求,携带 Upgrade: websocket 头,请求升级协议。
  2. 连接建立:服务器响应 101 Switching Protocols,确认协议升级,TCP 连接保持打开。
  3. 双向通信:客户端与服务器通过帧(Frame)格式交换数据,直到某一方主动关闭连接。
  4. 连接关闭:通过 close 帧终止连接,双方释放资源。

二、客户端 WebSocket 编程(JavaScript)

2.1 创建 WebSocket 连接

通过 WebSocket 构造函数初始化连接,参数为服务端 WebSocket 地址。

// 连接 WebSocket 服务器(ws 为非加密,wss 为加密)
const socket = new WebSocket('ws://localhost:8080/chat');

// 查看连接状态(readyState)
console.log('连接状态:', socket.readyState); 
// 0: CONNECTING(连接中)
// 1: OPEN(已连接)
// 2: CLOSING(关闭中)
// 3: CLOSED(已关闭)

2.2 监听连接事件

WebSocket 提供 4 个核心事件,覆盖连接生命周期的所有状态:

// 1. 连接成功建立时触发
socket.onopen = () => {
  console.log('连接已建立,可发送消息');
  // 连接成功后才能发送数据
  socket.send('客户端已上线');
};

// 2. 收到服务器消息时触发
socket.onmessage = (event) => {
  // event.data 为消息内容(可能是字符串、Blob 或 ArrayBuffer)
  console.log('收到消息:', event.data);

  // 若消息为 JSON 格式,需解析
  try {
    const data = JSON.parse(event.data);
    console.log('解析后的 JSON 消息:', data);
  } catch (e) {
    // 非 JSON 格式(如纯文本)
  }
};

// 3. 连接发生错误时触发
socket.onerror = (error) => {
  console.error('连接错误:', error);
};

// 4. 连接关闭时触发
socket.onclose = (event) => {
  console.log(`连接关闭,状态码:${event.code},原因:${event.reason}`);
  // 常见状态码:1000(正常关闭)、1001(端点离开)、1006(意外关闭)
};

2.3 发送与关闭连接

  • 发送数据:使用 send() 方法,支持字符串、JSON、二进制数据。
  • 关闭连接:使用 close() 方法,可指定状态码和原因。
// 发送字符串
socket.send('Hello, Server!');

// 发送 JSON 数据(需转为字符串)
const userMsg = { type: 'chat', content: '你好', sender: '张三' };
socket.send(JSON.stringify(userMsg));

// 发送二进制数据(如图片 Blob)
// const imageBlob = new Blob([imageData], { type: 'image/png' });
// socket.send(imageBlob);

// 主动关闭连接(状态码 1000 表示正常关闭)
socket.close(1000, '客户端主动结束会话');

三、服务端 WebSocket 编程(Node.js)

3.1 基于 ws 库的服务端实现

ws 是轻量、高性能的 WebSocket 库,严格遵循标准协议,适合对性能要求高的场景。

安装依赖

npm install ws

基础服务端代码

const WebSocket = require('ws');

// 创建 WebSocket 服务器,监听 8080 端口
const wss = new WebSocket.Server({ port: 8080 });

// 监听客户端连接事件(每个客户端连接会触发一次)
wss.on('connection', (ws) => {
  console.log('新客户端已连接');

  // 监听当前客户端发送的消息
  ws.on('message', (data) => {
    // data 默认为 Buffer 类型,需转为字符串
    const message = data.toString();
    console.log(`收到客户端消息:${message}`);

    // 向当前客户端回复消息
    ws.send(`服务端已收到:${message}`);
  });

  // 监听当前客户端断开连接
  ws.on('close', () => {
    console.log('客户端已断开连接');
  });

  // 监听错误
  ws.on('error', (err) => {
    console.error('连接错误:', err);
  });
});

console.log('WebSocket 服务启动,地址:ws://localhost:8080');

3.2 基于 Socket.IO 库的服务端实现

Socket.IO 是功能更丰富的库,支持自动重连、房间管理、事件自定义等,适合复杂实时应用(需客户端也使用 Socket.IO)。

安装依赖

npm install socket.io

基础服务端代码

const http = require('http');
const { Server } = require('socket.io');

// 创建 HTTP 服务器(Socket.IO 依赖 HTTP 服务器)
const httpServer = http.createServer((req, res) => {
  res.end('Socket.IO Server Running');
});

// 初始化 Socket.IO,允许跨域(开发环境)
const io = new Server(httpServer, {
  cors: {
    origin: "http://localhost:5500" // 客户端地址
  }
});

// 监听客户端连接
io.on('connection', (socket) => {
  console.log(`客户端 ${socket.id} 已连接`); // socket.id 为客户端唯一标识

  // 监听客户端自定义事件(如 "chat")
  socket.on('chat', (msg) => {
    console.log(`收到消息:${msg}`);
    // 向当前客户端回复
    socket.emit('reply', `服务端已收到:${msg}`);
  });

  // 监听客户端断开
  socket.on('disconnect', () => {
    console.log(`客户端 ${socket.id} 已断开`);
  });
});

// 启动服务器(端口 3000)
httpServer.listen(3000, () => {
  console.log('Socket.IO 服务启动,地址:http://localhost:3000');
});

四、核心功能实现(服务端)

4.1 广播消息(发送给所有客户端)

向所有已连接的客户端推送消息(如群聊、公告)。

ws 库实现

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

wss.on('connection', (ws) => {
  ws.on('message', (data) => {
    const message = data.toString();

    // 遍历所有客户端,发送消息(排除自身)
    wss.clients.forEach((client) => {
      // 仅向状态为 "已连接" 的客户端发送
      if (client !== ws && client.readyState === WebSocket.OPEN) {
        client.send(`其他用户说:${message}`);
      }
    });
  });
});

Socket.IO 实现

// 接上一节 Socket.IO 服务端代码
io.on('connection', (socket) => {
  socket.on('group_chat', (msg) => {
    // 广播给所有客户端(包括发送者)
    io.emit('group_message', `用户${socket.id}说:${msg}`);
  });
});

4.2 单发消息(发送给指定客户端)

向特定客户端发送消息(如私聊、定向通知)。

ws 库实现(需维护客户端映射)

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

// 存储客户端 ID 与连接的映射(key: 自定义 ID,value: ws 实例)
const clients = new Map();
let clientId = 1;

wss.on('connection', (ws) => {
  // 为新客户端分配 ID
  const id = clientId++;
  clients.set(id, ws);
  console.log(`客户端 ${id} 连接`);

  // 接收消息时,解析目标 ID 并单发
  ws.on('message', (data) => {
    const { targetId, content } = JSON.parse(data.toString());
    const targetClient = clients.get(targetId);

    if (targetClient && targetClient.readyState === WebSocket.OPEN) {
      targetClient.send(`来自客户端 ${id} 的私聊:${content}`);
    }
  });

  // 断开连接时移除映射
  ws.on('close', () => {
    clients.delete(id);
    console.log(`客户端 ${id} 断开`);
  });
});

Socket.IO 实现(通过 socket.id 定位)

// 接上一节 Socket.IO 服务端代码
io.on('connection', (socket) => {
  // 客户端发送私聊请求:{ targetId: 'xxx', content: '...' }
  socket.on('private_chat', ({ targetId, content }) => {
    // 向目标客户端发送消息
    io.to(targetId).emit('private_message', {
      from: socket.id,
      content: content
    });
  });
});

4.3 房间管理(分组通信)

将客户端划分到“房间”,实现分组消息推送(如群聊房间、游戏房间)。

Socket.IO 实现(自带房间功能)

// 接上一节 Socket.IO 服务端代码
io.on('connection', (socket) => {
  // 客户端加入房间
  socket.on('join_room', (roomId) => {
    socket.join(roomId); // 加入指定房间
    console.log(`客户端 ${socket.id} 加入房间 ${roomId}`);
    // 通知房间内所有成员
    io.to(roomId).emit('room_notice', `用户 ${socket.id} 加入房间`);
  });

  // 向房间内发送消息
  socket.on('room_chat', ({ roomId, content }) => {
    // 仅向房间内成员发送(排除发送者可用 socket.to(roomId).emit())
    io.to(roomId).emit('room_message', {
      from: socket.id,
      content: content
    });
  });

  // 离开房间
  socket.on('leave_room', (roomId) => {
    socket.leave(roomId);
    io.to(roomId).emit('room_notice', `用户 ${socket.id} 离开房间`);
  });
});

五、实际案例:实时聊天系统(完整实现)

5.1 服务端(Socket.IO)

const http = require('http');
const { Server } = require('socket.io');

const httpServer = http.createServer();
const io = new Server(httpServer, {
  cors: { origin: "http://localhost:5500" } // 允许客户端跨域
});

// 存储在线用户:{ socketId: 用户名 }
const onlineUsers = new Map();

io.on('connection', (socket) => {
  console.log(`用户 ${socket.id} 连接`);

  // 1. 用户登录(设置用户名)
  socket.on('login', (username) => {
    onlineUsers.set(socket.id, username);
    // 广播系统消息:用户上线
    io.emit('system_msg', `${username} 进入聊天室`);
    // 同步在线用户列表
    io.emit('online_users', Array.from(onlineUsers.values()));
  });

  // 2. 群聊消息
  socket.on('send_msg', (content) => {
    const username = onlineUsers.get(socket.id) || '匿名';
    const msg = {
      user: username,
      content: content,
      time: new Date().toLocaleTimeString()
    };
    // 广播给所有用户
    io.emit('new_msg', msg);
  });

  // 3. 断开连接
  socket.on('disconnect', () => {
    const username = onlineUsers.get(socket.id);
    if (username) {
      onlineUsers.delete(socket.id);
      io.emit('system_msg', `${username} 离开聊天室`);
      io.emit('online_users', Array.from(onlineUsers.values()));
    }
    console.log(`用户 ${socket.id} 断开`);
  });
});

httpServer.listen(3000, () => {
  console.log('聊天服务启动:http://localhost:3000');
});

5.2 客户端(HTML + JavaScript)

<!DOCTYPE html>
<html>
<body>
  <h3>实时聊天室</h3>
  <div>
    <input type="text" id="username" placeholder="请输入用户名">
    <button onclick="login()">登录</button>
  </div>
  <div id="onlineUsers" style="margin: 10px 0; color: #666;">在线用户:无</div>
  <div id="chatLog" style="border: 1px solid #ccc; height: 300px; overflow: auto; padding: 10px;"></div>
  <div>
    <input type="text" id="msgInput" placeholder="输入消息">
    <button onclick="sendMsg()">发送</button>
  </div>

  <!-- 引入 Socket.IO 客户端库 -->
  <script src="https://cdnjs.cloudflare.com/ajaxajax/libs/socket.io/4.5.1/socket.io.min.js"></script>
  <script>
    // 连接服务端
    const socket = io('http://localhost:3000');
    let isLogin = false;

    // 登录
    function login() {
      const username = document.getElementById('username').value.trim();
      if (username) {
        socket.emit('login', username);
        isLogin = true;
        document.getElementById('username').disabled = true; // 禁用输入
      }
    }

    // 发送消息
    function sendMsg() {
      if (!isLogin) {
        alert('请先登录');
        return;
      }
      const input = document.getElementById('msgInput');
      const content = input.value.trim();
      if (content) {
        socket.emit('send_msg', content);
        input.value = ''; // 清空输入
      }
    }

    // 接收新消息
    socket.on('new_msg', (msg) => {
      const log = document.getElementById('chatLog');
      log.innerHTML += `<div><strong>${msg.user}</strong> [${msg.time}]:${msg.content}</div>`;
      log.scrollTop = log.scrollHeight; // 滚动到底部
    });

    // 接收系统消息(上下线通知)
    socket.on('system_msg', (msg) => {
      const log = document.getElementById('chatLog');
      log.innerHTML += `<div style="color: #999;">${msg}</div>`;
      log.scrollTop = log.scrollHeight;
    });

    // 更新在线用户列表
    socket.on('online_users', (users) => {
      document.getElementById('onlineUsers').textContent = `在线用户:${users.join(', ')}`;
    });
  </script>
</body>
</html>

六、WebSocket 重点难点与解决方案

6.1 断线重连

网络波动可能导致连接中断,需客户端实现自动重连机制。

// 客户端断线重连示例(原生 WebSocket)
function connectWebSocket() {
  const socket = new WebSocket('ws://localhost:8080');
  let reconnectTimer = null;

  // 连接关闭时触发重连
  socket.onclose = () => {
    console.log('连接关闭,尝试重连...');
    // 5 秒后重连
    reconnectTimer = setTimeout(() => {
      connectWebSocket(); // 递归重连
    }, 5000);
  };

  // 连接成功时清除重连计时器
  socket.onopen = () => {
    console.log('重连成功');
    clearTimeout(reconnectTimer);
  };

  return socket;
}

// 初始化连接
const socket = connectWebSocket();

6.2 跨域问题

服务端需配置跨域允许,否则客户端连接会被浏览器拦截。

// ws 库跨域配置(绑定到 HTTP 服务器并设置 CORS)
const http = require('http');
const WebSocket = require('ws');

const server = http.createServer((req, res) => {
  // 设置 CORS 头
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.end();
});

// 绑定到 HTTP 服务器,而非直接监听端口
const wss = new WebSocket.Server({ server });

server.listen(8080, () => {
  console.log('支持跨域的 WebSocket 服务启动');
});

6.3 高并发处理

大量客户端连接时,需优化性能(如使用集群、二进制传输)。

// 使用 Node.js 集群(cluster)利用多核 CPU(ws 库)
const cluster = require('cluster');
const numCPUs = require('os').cpus().length;
const WebSocket = require('ws');

if (cluster.isPrimary) {
  // 主进程:创建工作进程
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }
} else {
  // 工作进程:启动 WebSocket 服务
  const wss = new WebSocket.Server({ port: 8080 });
  console.log(`工作进程 ${process.pid} 启动`);
}

七、总结

WebSocket 是实时双向通信的核心技术,通过客户端 WebSocket API 和服务端库(ws/Socket.IO)可快速实现实时聊天、数据监控、多人游戏等场景。初学者需掌握:

  • 连接建立、事件监听、消息发送的基础用法;
  • 服务端广播、单发、房间管理等核心功能;
  • 断线重连、跨域、高并发等难点的解决方案。

通过实际案例练习,可快速理解 WebSocket 的工作原理并应用到项目中。