前言
上一节我们简单介绍了一下im的一些基础概念和技术,这一节我们来说一下聊天流程的具体实现和相关代码
服务端与客户端的概念
上次我们讲了websocket和它的一些应用,那为什么我们要用websocket呢?这就要说一下聊天背后的逻辑了,聊天涉及到的过程是 客户端->服务端->客户端的一个流程,也就是说,需要客户端与服务端的双向通信;而上期说过,websocket的一大特点就是双向通信,这意味着客户端和服务器可以同时独立地向对方发送数据,而不需要等待对方的响应。
服务端客户端连接过程
这里涉及到两个地方,服务端向客户端推送消息,客户端向服务端推送消息;这里首先是连接时候的握手,要进行一个双向绑定,代码如下
双向绑定分别是建立用户唯一标识和channel的绑定,确保消息推送时能够找到相关channel,channel在这里就是一个客户端与服务端连接的一个桥梁,服务端可以通过channel推送消息到指定客户端,一次连接对应一个channel;同时还要绑定channel和用户唯一标识的关系,这样可以通过channel将用户移除在线列表;具体代码如下
可能这样看一点懵,channelGroup和User_channel的作用是干什么的呢?会在哪里用到呢?为什么channel要关联user的信息?
这还需要结合开启连接、关闭连接、推送消息等来说;
websocket事件处理
为什么要维护一个user和channel的对应Map?
通过消息推送流程我们可以看到,当我后端要给指定的人推送消息的时候,我就会用到这个关系,比如我要给a推送消息,但我没法直接根据a的信息做到精准推送,我要先拿到他的channel,根据channel去推送消息,才能做到精准推送;
那为什么要维护channel和user信息的关系(channel为主动)?
因为当一个用户的连接关闭时候,我需要移除他在Map中的数据,而我关闭的时候并不能拿到用户信息,只能拿到一个channel,但我Map中userId为键,总不能每次移除的时候在遍历一遍整个map数据吧,毕竟大家也都清楚,聊天人数是很多的,这样做即对性能有影响,也不合理,因此我们将每一个channel都携带上用户信息,这样拿到channel的时候,就可以取出这个userId,然后根据键移除对应Map中的数据;
为什么要每次移除呢?一个原因是性能的考虑,如果一直堆积,可能最后会报内存溢出,每次添加的和查询的时候也会造成效率的降低;另一个原因就是,这一条数据是用户在线的一个体现,我想判断有多少人在线,谁在线的时候,就可以通过这个map的数据拿到对应结果,只要根据userId找到对应channel,就说明对方在线;
消息的收发
消息的收发应该从服务端和客户端两个方面来看,因为这是一个双向通信的过程,如果只单看其中一端,无法了解整个流程和逻辑,下面先说服务端的消息收发:
服务端
在netty中,这个方法是对读到的消息进行操作,业务上因为客户端发消息过来是json类型,所以第一步要做一个类型转化,然后进入到我自定义的处理器工厂,这个作用是用来处理不同类型的消息,比如登陆、登出、单聊、群聊、心跳包等不同类型的消息,由于不同类型的消息处理逻辑不同,因此用工厂模式加策略模式来解决这个问题,下面是工厂和自定义处理器的部分代码:
这里的逻辑大概就是消息进入channelRead方法,解析消息,然后根据消息类型在工厂中进入对应的过滤器中执行对应逻辑;设计模式不展开说了,后面会单独说一下常用的设计模式;
然后以私聊消息为例子说一下消息发送的流程:
首先要根据redis自增序列,获得当前消息的一个会话序列号,用来进行一个消息的排序和记录会话的进度,然后这个时候首先向发送方的客户端推送一个回执,表示消息成功发送,然后向接受方的客户端发送对应消息(前提对方在线),对方的id在消息类的字段中有体现,因此可以通过id拿到其对应channel,回执的一个好处就是保证了消息投递的可靠性,参考了tcp连接的3次握手机制,最后mq异步存储消息到数据库;
客户端
这里以vue3代码为例子,python同样可以作为websocket的客户端
这里的逻辑和服务端一一对应,当打开这个页面的时候,websocket会进入onopen方法,然后到服务端进行握手操作,进行双向绑定,当点击发送消息时,消息会到服务端的channelRead方法,然后由服务端对当前消息进行分发,确定服务端收到消息后,再将消息添加到消息列表,同时websocket是实时更新,不需要手动刷新页面,然后客户端执行onclose时候,服务端会断开连接,执行remove操作;
整个过程就是由客户端进行连接,然后服务端双向绑定,客户端发送消息到服务端,服务端进行分发的一个流程;
websocket连接测试工具
如果说对vue或者说对python不熟悉,又想进行websocket测试怎么办?这里给大家推荐一个网站
WebSocket在线测试工具 (wstool.js.org)
在这里指定连接信息,可以拼接上个人信息,例如用户id,点击开启连接,如果两边都没问题,就会连接成功
由于我们发送消息是json格式,所以在这里发送消息,也要写成json格式,不然会在json转化时报错;
如图,我们消息成功发到服务端并且得到回调,那如何体现两个客户端之间的交互?
开两个一起就可以:
可以看到用户1111成功收到3333的消息,这也算是一个简易的聊天室了。
结语
从上面代码,大家可能感到疑惑,为什么remove时要加锁?为什么在websocket不能直接注解注入对象?这个我们放到下期来讲,同时下期会给大家补充一下心跳包和断线重连以及一些其它业务,等个人项目完成后,相关代码都会开源,敬请期待。