技术讨论群【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 and leave. 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

 感兴趣的小伙伴可以慢慢研究。 

<ref *1> Server {
  _events: [Object: null prototype] {},
  _eventsCount: 0,
  _maxListeners: undefined,
  
  .....
  //此处省略
  encoder: Encoder {},
  _adapter: [class Adapter extends EventEmitter],
  sockets: <ref *2> 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(<socketid>).emit()

        7.8 socket.io事件速查表

以上是我的理解,可能有些错误或者不准确,欢迎留言指正。

想要源码,欢迎留言 ,后续会更新基于vuex管理的socket.io的使用。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐