技术讨论群【522121825】
1. 上次的博客已经讲述了如何建立服务器,如何建立客户端,并且与服务器进行连接,那么本文接着上次,讲述一下客户端与服务器端的通讯是如何实现的。
PS:事件的发布与监听、广播事件、私聊事件、其他常用事件。
2. 事件的发布与监听:
2.1 emit()、on():
在vue中,组件可以发布自定义事件,使用emit,其他地方直接使用 on 监听这个事件即可。而socket.io 也是类似的,发布事件用 emit ,监听事件用 on;
// 监听客户端连接
io.on("connection", function (socket) {
/* 每一个连接上来的用户,都会分配一个socket */
console.log("客户端有连接");
/* 监听登录事件 */
socket.on('login', data => {
console.log('login', data);
});
// 给客户端发送消息(发布welcome事件)
socket.emit("welcome", "欢迎连接socket");
/* socket实例对象会监听一个特殊函数,关闭连接的函数disconnect */
socket.on('disconnect', function () {
console.log('用户关闭连接');
});
});
例如上的代码,服务器端监听了‘connection’事件,(connection事件是socket的默认事件,指的是用户连接),还监听了login事件,发布了一个 welcome事件,监听一个disconnection事件,(disconnection也是默认事件,指的是用户断开连接)
2.2 发布与监听:
发布事件,就是我想给你发消息。监听事件,就是我想收到你的消息。就是一个 ‘发-收’ 的关系。(我自己的理解哈)
3. 客户端监听与发布:
/* socket是监听服务器发布的自定义事件 */
sockets: {
/* 监听welcome事件 */
welcome:function(data){
console.log("welcome data 数据返回 = >", data);
},
},
这个是监听服务器发布的事件,用sockets监听;
methods: {
/* 登录 */
login(){
console.log("login",this.username);
/* 发送数据到服务器 */
this.$socket.emit('login',this.username);
this.isLogin=true;
this.msgList.push({type:'',msg:this.username+'登录成功'});
},
}
这个是发布事件,定义在methods中,使用 this.$socket.emit();
4. 启动服务器与Vue:
连接了两个客户端, 服务器输出连接消息;
5. 群聊事件
到此,已经简单讲述了事件的发布于监听,而socket.io的通讯就是建立在这个基础上。
我们使用vue-socket.io做聊天应用,无非就是私聊和群聊,下面慢慢来探究里面的技术;
5.1 普通群聊:
socket.io 在群聊的处理上,是非常棒的!Introduction | Socket.IO 这个是socket.io 的官方文档,里面有更详细的描述。
我们还是先看一下服务器端的代码:
var io = require("socket.io")(http, {
allowEIO3: true,
cors: {
origin: "http://localhost:8080",
methods: ["GET", "POST"],
credentials: true
}
});
// 监听客户端连接
io.on("connection", function (socket) {
/* 每一个连接上来的用户,都会分配一个socket */
console.log("客户端有连接");
/* 监听登录事件 */
socket.on('login', data => {
console.log('login', data);
});
// 给客户端发送消息(发布welcome事件)
socket.emit("welcome", "欢迎连接socket");
/* socket实例对象会监听一个特殊函数,关闭连接的函数disconnect */
socket.on('disconnect', function () {
console.log('用户关闭连接');
});
});
我先解释一下,这里面有两个非常重要的变量:io、socket;
io是socket服务器对象(servers);
socket是每一个连接上来的客户端实例;
所以群聊,就是给连接上来的所有人发送消息!而所有人存在io 服务器对象中,因此,广播事件:
io.emit("hello", "world");
5.2 socket.io 官方文档中,还有一点需要我们去注意的。
Rooms:A room is an arbitrary channel that sockets can
join
andleave
. It can be used to broadcast events to a subset of clients:
我们可以加入房间或者离开房间,在广播事件的时候,就可以直接广播给该房间的客户端。
5.1我们说的是给连接上来的所有人,而现在这个是指定房间的客户端,还是有很大的区别的。
那么,我们如何定义房间,并给指定房间的客户端发送消息呢?
// 在客户端连接的时候,指定加入某一个房间
io.on("connection", (socket) => {
socket.join("some room");
});
// 给指定房间的客户端连接发送消息
io.to("some room").emit("some event");
下面是房间的相关事件:
有兴趣的可以试试
以上便是群聊的相关方法。
5.2 私聊:
官方文档并没有过多的描述私聊的实现,只是给了一段示例代码:
// to individual socketid (private message)
io.to(socketId).emit(/* ... */);
对于这个私聊,下面我说说我的理解。
6. socket.io 私聊实现方案:
6.1 新建数组实现
上述中的代码,我们只要找到你想私聊的客户端的socketid,调用这个API即可。但是连接上来的这么多客户端,我们是如何得知你想发送的对象的sockeid呢?
我们修改服务器端的代码如下:
// 定义数组接收数据
var socketInfo=[];
/* 监听登录事件 */
socket.on('login', data => {
console.log('login', data);
/* 将用户的id与socketid对应 */
socketInfo.push({
userid:data,
socketid:socket.id
});
console.log(socketInfo);
});
客户端:
服务器端:
这样一来,你想给谁发消息,只要在数组中遍历userid,取到这个socketid,即可实现。具体代码如下:(现在我是固定的 2 号,具体根据实际项目调整)
客户端:
/* 发送消息 */
send() {
/* 给服务器发送消息,就是发布一个事件,服务器监听即可! */
this.$socket.emit("1-2", {
userid:'2',
msg:this.msg,
});
this.msg=''
},
不难理解,需要带一个用户id,来唯一识别你想给谁发消息。同时,还要监听服务器发送过来的数据:
/* socket是监听服务器发布的自定义事件 */
sockets: {
/* 监听私聊事件 */
chatPrivate:function(data){
console.log('有人找我私聊了',data);
},
/* 监听welcome事件 */
welcome:function(data){
console.log("welcome data 数据返回 = >", data);
},
},
服务器端:
/* 这里我们模拟 1 给 2 发消息 */
socket.on('1-2',data=>{
/* 遍历数组 */
socketInfo.forEach(s=>{
/* 判断该对象是不是我们需要发送的那个人 */
if(s.userid==data.userid){
/* 发送数据 */
io.to(s.socketid).emit('chatPrivate',data.msg);
}
});
});
实现效果:
1号发送数据:
2号接收数据:
以上便是基于数组实现私聊,总结一下,就是要对应连接上来的用户ID,和连接的socketid,当我发起私聊的时候,能够找到对应用户的socketid即可!根本就是找到对方的socketid!
6.2 基于io服务器实例实现
io是socket服务器对象(servers);
socket是每一个连接上来的客户端实例;
这次我们不用建立数组,用原始的方式实现。socket是连接实例,肯定是一个对象,因为我们取id,是通过socket.id实现的。所以我们可以在对象上追加一个属性:userid;
那么,大概会变成这样子:
原来的socket:
socket:{
...
id:XXXXXXXXX
...
}
加了自定义属性后:
socket:{
...
id:XXXXXXXXX
userid:XXXXXX
...
}
这样,我们不就可以直接遍历存放socket的容器,找到userid,再取socketid不就实现了嘛??换句话说,连接上来的socket,应该是放在一个默认的数组中。这个默认的数组是什么?io?
socket添加自定义属性:
/* 监听登录事件 */
socket.on('login', data => {
console.log('login', data);
/* 添加自定义属性 */
socket.userid=data;
});
下面是输出的io
感兴趣的小伙伴可以慢慢研究。
Server {
_events: [Object: null prototype] {},
_eventsCount: 0,
_maxListeners: undefined,
.....
//此处省略
encoder: Encoder {},
_adapter: [class Adapter extends EventEmitter],
sockets: Namespace {
_events: [Object: null prototype] { connection: [Function (anonymous)] },
_eventsCount: 1,
_maxListeners: undefined,
sockets: Map(2) {
'R_pZZihjSjxrh8VaAAAA' => [Socket],
's2KCyEX2y0tBgDXhAAAC' => [Socket]
},
_fns: [],
_ids: 0,
server: [Circular *1],
name: '/',
adapter: Adapter {
_events: [Object: null prototype] {},
_eventsCount: 0,
_maxListeners: undefined,
nsp: [Circular *2],
rooms: [Map],
sids: [Map],
encoder: Encoder {},
[Symbol(kCapture)]: false
},
[Symbol(kCapture)]: false
},
........
//此处省略
}
相信有感觉的已经找到了socket!没错,就是io.sockets.sockets。(一定要注意:io.sockets.sockets 是存放所有连接的用户实例的数组!!数组!!(Map(2)))
我简单说一下为什么是io.sockets.sockets:
理论上,应该是io.sockets就行了,第一个很好理解,io指的是Server,第二个sockets指的是命名空间,因为同一个服务器,可以根据不同的namespace分配不同的用户连接。这个请详细看官网说明,这里不再深入研究。
涉及了一个命名空间,所以才是io.sockets.sockets
那么,有了这个数据,我们就遍历这个,找到对应userid和socketid,不就能实现了嘛?
/* 定义变量接收这个数组:一般我简写 scs */
var SocketConnections=io.sockets.sockets;
console.log("客户端有连接");
/* 监听登录事件 */
socket.on('login', data => {
console.log('login', data);
/* 添加自定义属性 */
socket.userid=data;
});
/* 这里我们模拟 1 给 2 发消息 */
socket.on('1-2',data=>{
/* 遍历数组 */
SocketConnections.forEach(s=>{
/* 判断该对象是不是我们需要发送的那个人 */
if(s.userid==data.userid){
/* 发送数据 */
io.to(s.id).emit('chatPrivate',data.msg);
}
});
});
注意:这里循环找的是id,不是socketid,socketid是我们自己定义的数组写的变量,而我们通过io.sockets.sockets找到的是socket本身,id就是他的属性!!!
以上就是 io 原生方式找到对应的连接实例。
7. 总结:
7.1 emit 发布事件
7.2 on 监听事件
7.3 io.emit() 群聊 [ 所有连接服务器的实例都会接收到消息 ]
7.4 Rooms:io.to('room name').emit() [ 在某房间里的用户能接收到数据 ]
7.5 私聊实现1:自定义数据,存放连接的唯一标识和socketid
7.6 私聊实现2:io.sockets.sockets,默认的服务器实例对象数组
7.7 私聊代码:io.to(
7.8 socket.io事件速查表
以上是我的理解,可能有些错误或者不准确,欢迎留言指正。
想要源码,欢迎留言 ,后续会更新基于vuex管理的socket.io的使用。