即时通讯(Instant Messaging,简称IM)是一个实时通信系统,允许两人或多人使用网络实时的传递文字消息、文件、语音与视频交流。实现方式有两种。第一种基于Server转发的,Client双方通信会经过Server转发来完成消息传递。例如QQ、微信。
第二种是基于P2P(点对点)的。P2P的实现依赖于客户端之间的互联,但由于NAT与防火墙的存在,客户端无法直接互联,需要coturn服务器用来穿越NAT网络。
本文主要讲述基于Server转发实现的即时通讯原理和实现过程,包括私聊和群聊两类。私聊和群聊,原理基本类似,但私聊是单次转发,群聊属于遍历转发。不同点为,群聊以群ID(多人)划分,私聊以会话ID(两人)划分,其次消息存储上也有较大差异。
总体设计
实现方式
- 协议:WebSocket
- 框架:Socket IO/Swoole
- 关键点:可扩展,支持分布式部署。短链接负责业务逻辑,长链接负责Websocket。
总体架构图
时序图
详细实现
接入层实现
接入层的目的:1保证IM服务的可靠性,避免用户同时集中在同一聊天服务器中。2当聊天服务器压力过大时,能实现扩容。接入层可采用ngx_lua实现,保证性能。
- 客户端请求接入IM,调用接入API。参数为:接入类型(群聊,私聊),接入uid,群聊ID
- 群聊:保证隶属同群ID的用户落在同一Socket实例上。如果用户ABC,都属于群ID=123中,那么一定要保证ABC都分配在同一实例上。具体分配策略自定,一般按照群ID取余分配在不同机器实例上。
- 私聊:A和B私聊,也要保证A和B落在同一实例上。一般分配策略为,(A+B)对机器取余。
- 根据群聊类型,得到接入实例的IP和端口,返回给客户端。
连接逻辑
连接逻辑比较简单,Socket connect的过程。
- 客户端得到接入层提供的Socket Ip和端口,发起Websocket请求。
- Socket服务,根据请求参数,判断客户端合法性(签名验证)和登录验证。
- 记录请求日志,保存Socket连接句柄(当前实例数组中)。
订阅消息逻辑
上一步连接成功后,用户触发进群(聊天窗口)操作,订阅该群消息。订阅消息用来保证,用户通过socket实时接收到该群其他用户发送的消息。否则,服务端会采用推送完成消息转发。
- 订阅事件。emit(‘sub’),群聊:发送当前群ID和用户的uid。私聊:发送对方uid和用户自身uid(会话ID)。
- 服务端判断该uid是否已经在线,如果已经在线,则主动关闭旧的Socket,保证只有当前窗口在线。并将当前socket句柄push到数组中保存。
- 业务逻辑判断。群聊:判断用户是否属于该群,判断群合法性。私聊:判断是否为好友。
- 客户端在线,接收Socket消息,对消息解码,区分类型并展示。编码协议自定。
- 客户端不在线,收到Push推送。客户端根据接收到的推送,拉取历史消息。更新未读数。
发布消息逻辑
用户完成订阅群ID(群聊)或者订阅会话ID(私聊)后,可主动发送消息到该订阅ID(群ID和会话ID)中。
- 发布事件。emit(‘pub’),图片(缩略图)、视频、语音,完成上传(静态文件服务)后,编码消息内容并提交。编码协议自定。
- 服务端收到消息后,解码,识别过滤色情、政治内容。写入队列,完成其他操作(判断图片色情内容,消息统计,落地入库,更新未读数)。
- 群聊:遍历当前群存在的socket句柄,如果在线则转发内容,不在线推送通知。
- 私聊:判断对方是否在线,如果在线转发,不在线push推送。
用户下线
用户退出聊天窗口,触发disconnect事件。服务端关闭socket句柄,标示用户下线。
广播逻辑
广播逻辑,主要用在运营管理上。是对所有在线用户或者特定群ID(会话ID),发送消息(系统消息)。实现方法为:遍历所有在线socket句柄,发送内容。
监控服务
在运营管理或者监控上,需要对聊天服务性能加以判定。
- 统计在线人数,得到每个实例的Socket句柄长度相加
希望转载的朋友能够尊重作者的劳动成果,加上转载地址。谢谢!
http://gglinux.com/2017/04/15/IM_design/