房间与命名空间的关系
在 Socket.io 里有房间与命名空间这么两个概念与结构。房间与命名空间都能实现 websocket 的多路复用,但是它们有一定的区别。
当 websocket 连接后,socket 会属于某个房间,还会属于某个命名空间。socket 与 room,namespace 的关系就像是个人,房子,地区的关系。
它们的关系如下图,每个 Namespace 里会有很多 Room,Room 里又会有很多 socket。
命名空间(Namespace)
连接的时候,使用路径名来指定命名空间。在没有指定命名空间下,默认会使用 /
作为命名空间。
如果要想指定命名空间,则需要在客户端指定:/news
,这样就指明进入的是 /news
命名空间。
客户端指定命名空间:
1 2 | const socket = io('/news'); |
在服务端里对应的处理,则需要使用 of
:
1 2 3 4 5 6 7 8 9 10 11 | const io = require('socket.io')(); // news 命令空间 const news = io.of('/news'); news.on('connection', function(socket) { console.log('someone connected'); }); // 只在本命名空间发送消息 news.emit('hi', 'everyone!'); |
io 在创建是,它就会被指派到默认的命名空间 /
,那么它的广播只限于在 /
里的 socket 才收到,其他空间里是收不到消息的。
1 2 3 4 | const io = require('socket.io')(); io.send('!!!'); io.broadcast.emit('!!!'); |
也可以从默认空间里去到其他空间里发消息:
1 2 3 4 | // 去到 /news 里发消息 io.of('/news').send('!!!'); // 去到 /news 里的 room1 房间里发消息 io.of('/news').to('room1').send('!!!'); |
只有命名空间才有权力去到另一个命名空间发消息:
1 2 3 4 | // 下面代码是错误的 io.on('connection', function(socket) { socket.of('/news'); }); |
socket 只能换房间,不能去到另一个命名空间。
在命名空间里广播有些差异性:
1 2 | io.sockets.emit('!!!'); // 默认命名空间,广播方式 news.emit('!!!'); // 非默认命名空间,广播方式 |
在连接时,如果指定了命名空间,也会进入到默认空间里:
1 2 3 4 5 6 7 | io.on('connection', (socket) => { console.log(`[/ 欢迎] ${socket.id}`); }); news.on('connection', (socket) => { console.log(`[/news 欢迎] ${socket.id}`); }); |
上面代码,当一个客户端连接时,出现输出两次欢迎,并且拥有相同的 id。
1 2 | [/ 欢迎] qTrAe97j9cNI4VYMAAAA [/news 欢迎] /video#qTrAe97j9cNI4VYMAAAA |
此外连接事件可以多次监听:
1 2 3 4 5 6 7 | io.on('connection', (socket) => { console.log(`[/ 欢迎1] ${socket.id}`); }); io.on('connection', (socket) => { console.log(`[/ 欢迎2] ${socket.id}`); }); |
上面代码输出两次欢迎,并且有先后顺序(1 -> 2)
房间(Room)
对于房间的进入与离开,可以使用 join
与 leave
。
1 2 3 4 5 6 | io.on('connection', function(socket) { // 把 socket 扔进 room1 房间里 socket.join('room1'); // 再把 socket 赶出 room1 房间 socket.leave('room1'); }); |
每个房间只属于某个命名空间,因此可以收听同一个命令空间的消息。而不同的房间之间是隔离的,它们不能接收不同房间的消息。
使用 to/in
(它们是一样的)来对某个房间进行广播消息。
1 2 | io.to('room1').emit('some event'); socket.to('room1').to('room2').emit('hello'); |
当连接时,默认会指派到一个唯一的房间,也就是用 socket.id
来命名的房间。这样的做法是让每个 socket 待在自己的房间里不受到其他人影响。
这样可以轻松地向其他 socket 广播消息:
1 2 3 4 5 6 | io.on('connection', function(socket) { // id 是某个 socket.id,相当于去到他的房间里叫他 socket.on('say to someone', function(id, msg) { socket.broadcast.to(id).emit('my message', msg); } }); |
socket 可以进入多个房间接收信息,相当于你可以在 QQ 上加入多个群一样。
可以使用 rooms 来查看,当前 socket 所在的房间。
1 2 3 4 5 6 | io.on('connection', (socket) => { socket.join('room 237', () => { let rooms = Objects.keys(socket.rooms); console.log(rooms); // [ <socket.id>, 'room 237' ] }); }); |
如何从 url 判断房间与命名空间?
假设 url = /news/room1
,其中 news 是命名空间,room1 是房间。
命名空间自动分配不用管,而房间需要我们自己去分配。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | const io = require('socket.io')(); // news 命令空间 const news = io.of('/news'); news.on('connection', function (socket) { // http://127.0.0.1:3000/news/room1 const url = socket.request.url; // 获取房间 id,也就是 room1 const room = url.match(//w+$/).toString(); // 进入房间 socket.join(room, () => { let rooms = Objects.keys(socket.rooms); console.log(rooms); // [ <socket.id>, 'room1' ] }); }); |
也可以使用 query,这是最简单的方式。
1 2 | // http://127.0.0.1:3000/news?r=room1 const room = socket.handshake.query['r']; |
轻易实现群聊与私聊
利用命名空间与房间的特性,可以轻易的实现群聊与私聊。
对于群聊,你只需要把每个 socket 扔进同一个房间即可。
对于私聊,你只需要把两个 socket 扔进同一个房间即可。
小结
socket.io 的 API 是对称的,所以适用于服务端的操作也适用于客户端。