3、Socket.IO入门:Socket.IO的安装与配置、事件驱动模型、房间与命名空间

聊到多屏联动,WebSocket 是绕不开的基础设施。但说实话,原生 WebSocket 用起来有点「裸」。你想想看,要处理重连、要搞广播、要区分不同客户端...这些琐事够你喝一壶的。

这时候,Socket.IO 就站出来了。它把 WebSocket 包装得特别顺手,还加了一堆「后悔药」——比如自动降级到轮询。我个人习惯,只要涉及实时通信,第一反应就是拿 Socket.IO 来搭架子。

3.1 安装与配置:5 分钟跑起来

安装没什么花头,一行命令搞定:

npm install socket.io

服务端配置,我一般这么写:

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

const server = http.createServer();
const io = new Server(server, {
  cors: {
    origin: '*', // 开发环境先放通,生产环境记得收紧
    methods: ['GET', 'POST']
  }
});

server.listen(3000, () => {
  console.log('Socket.IO 服务跑在 3000 端口');
});

客户端那边更简单:

<script src="/socket.io/socket.io.js"></script>
<script>
  const socket = io('http://localhost:3000');
</script>
小提示:客户端引入的 socket.io.js 是服务端自动提供的,不需要额外下载。我第一次用的时候还傻乎乎去 CDN 找文件,后来才发现多此一举。

配置里有个坑,我提一下。跨域问题在本地开发时经常冒出来,cors 选项一定要配。我曾经有个项目,联调了一下午才发现是跨域没放开,气得我差点砸键盘。

3.2 事件驱动模型:一切皆事件

Socket.IO 的核心思想,说白了就是「你发一个事件,我收一个事件」。跟 DOM 事件很像,但它是跨网络的。

服务端监听事件:

io.on('connection', (socket) => {
  console.log('有客户端连进来了');

  socket.on('chat message', (msg) => {
    console.log('收到消息:', msg);
    // 广播给所有人
    io.emit('chat message', msg);
  });

  socket.on('disconnect', () => {
    console.log('客户端走了');
  });
});

客户端发送事件:

socket.emit('chat message', '大家好,我是新来的');

这里有个细节要注意:socket.emit 只发给当前连接,io.emit 是群发。我刚开始做的时候,经常搞混这两个,结果消息要么发不出去,要么炸了锅。

核心要点:
  • socket.emit() → 发给当前客户端
  • io.emit() → 发给所有客户端
  • socket.broadcast.emit() → 发给除自己外的所有人

事件名可以自定义,但别用 connectdisconnect 这些保留字。嗯,这个坑我踩过,自定义事件叫了个 connect,结果服务端死活不触发,查了半天文档才发现。

3.3 房间:把用户分组管理

多屏联动的场景里,你不可能让所有屏幕都收到同样的消息。比如,A 组屏幕只显示 PPT,B 组屏幕只显示提词器。这时候就需要「房间」了。

加入房间:

socket.join('room-ppt');
socket.join('room-teleprompter');

离开房间:

socket.leave('room-ppt');

给特定房间发消息:

io.to('room-ppt').emit('slide-change', { page: 3 });

我做过一个教育项目,老师端、学生端、投屏端各占一个房间。老师翻页时,只通知投屏端;提问时,才广播给学生端。这种设计,代码清晰,逻辑也不容易乱。

注意:房间是 Socket.IO 服务端的概念,客户端不需要知道自己在哪个房间。你只管在服务端管理好 socket.join()io.to() 就行。

3.4 命名空间:隔离不同业务

房间解决的是「同一类业务内的分组」,命名空间解决的是「不同业务之间的隔离」。说白了,就是给你的 Socket.IO 开几个「独立通道」。

定义命名空间:

const chatNsp = io.of('/chat');
const monitorNsp = io.of('/monitor');

chatNsp.on('connection', (socket) => {
  console.log('有人进了聊天室');
});

monitorNsp.on('connection', (socket) => {
  console.log('有人连了监控');
});

客户端连接不同命名空间:

const chatSocket = io('/chat');
const monitorSocket = io('/monitor');

命名空间之间完全隔离,事件不会串。我在一个物联网项目里用过这个特性:/device 命名空间处理设备数据,/admin 命名空间处理管理指令。两边互不干扰,维护起来特别省心。

特性 房间 命名空间
作用范围 同一命名空间内 全局隔离
适用场景 分组广播 业务模块分离
资源开销 稍高
典型用途 多屏联动、聊天室 聊天 + 监控 + 日志
我的建议:小项目先用房间就够了。等业务复杂到一定程度,比如一个服务要同时支撑聊天、推送、监控,再考虑拆命名空间。别一开始就搞得太重。

最后说一句,Socket.IO 的调试挺方便的。加上 ?EIO=4&transport=polling 参数,就能看到详细的握手日志。我曾经靠这个参数,定位到一个诡异的断连问题——原来是 Nginx 的超时配置太短了。

好了,这一章的内容就这些。Socket.IO 入门不难,关键是理解事件驱动、房间、命名空间这三个概念。下一章,我们会拿这些知识来做一个真正的多屏联动 Demo,到时候你就知道这些东西有多好用了。