Socket.IO聊天室实战:从0搭建WebSocket实时通信、压测延迟到断线重连
Chapter 1:OK so,先把实时通信跑起来
兄弟们,eccfy 开机!今天不讲玄学,直接屏幕录制式开干:我们用 Node.js + Socket.IO 做一个能广播、能断线重连、能看延迟的实时聊天室。这个 Socket.IO教程 适合你做客服系统、弹幕、协同白板、订单状态推送。先说结论:原生 WebSocket 更轻,Socket.IO 胜在自动降级、房间、重连、心跳机制,项目上线更省心。
接下来打开终端,跟我敲:
mkdir socket-demo && cd socket-demo
npm init -y
npm i express socket.io
npm i -D nodemon
创建 server.js,注意看屏幕右边,我把心跳、CORS、消息广播都写上:
const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const app = express();
const server = http.createServer(app);
const io = new Server(server, {
cors: { origin: '*' },
pingInterval: 10000,
pingTimeout: 5000
});
io.on('connection', socket => {
console.log('connected:', socket.id);
socket.on('join', room => {
socket.join(room);
socket.emit('system', `joined ${room}`);
});
socket.on('chat', data => {
io.to(data.room).emit('chat', {
user: data.user,
text: data.text,
ts: Date.now()
});
});
socket.on('disconnect', reason => {
console.log('disconnect:', reason);
});
});
server.listen(3000, () => console.log('ws on 3000'));
再创建 package.json 脚本:
"scripts": {
"dev": "nodemon server.js"
}
运行:
npm run dev
Chapter 2:Now watch this,客户端连接、房间广播、延迟测试
接下来做前端。新建 index.html,这就是最小可复现版本,也是很多人搜索 WebSocket实时通信怎么用 真正需要的东西:
<input id="msg" placeholder="type message" />
<button onclick="send()">send</button>
<ul id="list"></ul>
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io('http://localhost:3000', {
reconnectionAttempts: 5,
reconnectionDelay: 1000
});
socket.emit('join', 'room-1');
function send() {
const start = Date.now();
socket.emit('chat', {
room: 'room-1',
user: 'eccfy',
text: document.getElementById('msg').value,
start
});
}
socket.on('chat', data => {
const li = document.createElement('li');
li.innerText = `${data.user}: ${data.text} | delay=${Date.now() - data.ts}ms`;
document.getElementById('list').appendChild(li);
});
socket.on('connect', () => console.log('connected', socket.id));
socket.on('disconnect', r => console.log('lost', r));
</script>
OK so,这里有个坑:如果你直接双击 HTML 文件,可能会遇到跨域或静态资源路径问题。最稳的方式是让 Express 托管它。在 server.js 顶部加:
app.use(express.static(__dirname));
浏览器打开 http://localhost:3000,开两个窗口,一个发“hello”,另一个立刻收到。我本机测试,Chrome 两个标签页之间延迟通常是 1-4ms;局域网两台机器测试大约 8-18ms。别急,接下来上压测。
Chapter 3:压测、排错与上线前验证
先装压测工具:
npm i -g artillery
创建 load.yml:
config:
target: "http://localhost:3000"
phases:
- duration: 30
arrivalRate: 20
scenarios:
- engine: socketio
flow:
- emit:
channel: "join"
data: "room-1"
- loop:
- emit:
channel: "chat"
data:
room: "room-1"
user: "bot"
text: "ping"
count: 10
跑起来:
artillery run load.yml
我在 4 核 8G 本地环境测到:30 秒内约 600 个虚拟连接、6000 条消息,Node 单进程 CPU 峰值约 38%。如果你看到延迟突然从 20ms 飙到 800ms,优先查三件事:
- Nginx 是否开启 WebSocket 升级:必须有
proxy_set_header Upgrade $http_upgrade;和proxy_set_header Connection "upgrade";。 - 进程是否单点:多实例部署 Socket.IO 要接 Redis Adapter,否则房间广播会丢。
- 网络路径是否被代理影响:有些同学边查 油管怎么看 边开 翻墙软件 或 免费VPN,结果本地 WebSocket 测试走了奇怪代理,延迟数据完全失真。
Nginx 关键配置长这样:
location /socket.io/ {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
如何验证它真的修好了:第一,浏览器控制台看到 connected socket.id;第二,两个窗口互发消息不丢;第三,断开服务端 5 秒再启动,客户端能自动重连;第四,跑 artillery 后错误率低于 1%,P95 延迟低于 100ms。需要排查跨境网络时,优先用官方文档、本地直连和免费工具;如果确实需要备用网络方案,Roxi 也只是可选项之一:https://wizzegroup.com。觉得这期有用,收藏、评论你的报错日志,eccfy 下期直接现场拆!