注册

开源一个自用的Android IM库,基于Netty+TCP+Protobuf实现(4)

调试


我们先来看看连接及重连部分(由于录制gif比较麻烦,体积较大,所以我先把重连间隔调小成3秒,方便看效果)。



  • 启动服务端:启动服务端
  • 启动客户端:启动客户端

可以看到,正常的情况下已经连接成功了,接下来,我们来试一下异常情况,比如服务端没启动,看看客户端的重连情况:
调试重连
这次我们先启动的是客户端,可以看到连接失败后一直在进行重连,由于录制gif比较麻烦,在第三次连接失败后,我启动了服务端,这个时候客户端就会重连成功。


然后,我们再来调试一下握手认证消息即心跳消息:
握手消息及心跳消息测试
可以看到,长连接建立成功后,客户端会给服务端发送一条握手认证消息(1001),服务端收到握手认证消息会,给客户端返回了一条握手认证状态消息,客户端收到握手认证状态消息后,即启动心跳机制。gif不太好演示,下载demo就可以直观地看到。


接下来,在讲完消息重发机制及离线消息后,我会在应用层做一些简单的封装,以及在模拟器上运行,这样就可以很直观地看到运行效果。




消息重发机制


消息重发,顾名思义,即使对发送失败的消息进行重发。考虑到网络环境的不稳定性、多变性(比如从进入电梯、进入地铁、移动网络切换到wifi等),在消息发送的时候,发送失败的概率其实不小,这时消息重发机制就很有必要了。

我们先来看看实现的代码逻辑。
MsgTimeoutTimer:
MsgTimeoutTimer1
MsgTimeoutTimer2
MsgTimeoutTimerManager:
MsgTimeoutTimerManager1
MsgTimeoutTimerManager2

然后,我们看看收消息的TCPReadHandler的改造:
加入消息重发机制的TCPReadHandler
最后,看看发送消息的改造:
加入消息重发机制的发送消息


说一下逻辑吧:发送消息时,除了心跳消息、握手消息、状态报告消息外,消息都加入消息发送超时管理器,立马开启一个定时器,比如每隔5秒执行一次,共执行3次,在这个周期内,如果消息没有发送成功,会进行3次重发,达到3次重发后如果还是没有发送成功,那就放弃重发,移除该消息,同时通过消息转发器通知应用层,由应用层决定是否再次重发。如果消息发送成功,服务端会返回一个消息发送状态报告,客户端收到该状态报告后,从消息发送超时管理器移除该消息,同时停止该消息对应的定时器即可。

另外,在用户握手认证成功时,应该检查消息发送超时管理器里是否有发送超时的消息,如果有,则全部重发:
握手认证成功检查是否有发送超时的消息




离线消息


由于离线消息机制,需要服务端数据库及缓存上的配合,代码就不贴了,太多太多,我简单说一下实现思路吧:
客户端A发送消息到客户端B,消息会先到服务端,由服务端进行中转。这个时候,客户端B存在两种情况:



  • 1.长连接正常,就是客户端网络环境良好,手机有电,应用处在打开的情况。
  • 2.废话,那肯定就是长连接不正常咯。这种情况有很多种原因,比如wifi不可用、用户进入了地铁或电梯等网络不好的场所、应用没打开或已退出登录等,总的来说,就是没有办法正常接收消息。

如果是长连接正常,那没什么可说的,服务端直接转发即可。

如果长连接不正常,需要这样处理:服务端接收到客户端A发送给客户端B的消息后,先给客户端A回复一条状态报告,告诉客户端A,我已经收到消息,这个时候,客户端A就不用管了,消息只要到达服务端即可。然后,服务端先尝试把消息转发到客户端B,如果这个时候客户端B收到服务端转发过来的消息,需要立马给服务端回一条状态报告,告诉服务端,我已经收到消息,服务端在收到客户端B返回的消息接收状态报告后,即认为此消息已经正常发送,不需要再存库。如果客户端B不在线,服务端在做转发的时候,并没有收到客户端B返回的消息接收状态报告,那么,这条消息就应该存到数据库,直到客户端B上线后,也就是长连接建立成功后,客户端B主动向服务端发送一条离线消息询问,服务端在收到离线消息询问后,到数据库或缓存去查客户端B的所有离线消息,并分批次返回,客户端B在收到服务端的离线消息返回后,取出消息id(若有多条就取id集合),通过离线消息应答把消息id返回到服务端,服务端收到后,根据消息id从数据库把对应的消息删除即可。

以上是单聊离线消息处理的情况,群聊有点不同,群聊的话,是需要服务端确认群组内所有用户都收到此消息后,才能从数据库删除消息,就说这么多,如果需要细节的话,可以私信我。




不知不觉,NettyTcpClient中定义了很多变量,为了防止大家不明白变量的定义,还是贴上代码吧:
定义了很多变量的NettyTcpClient


应用层封装


这个就见仁见智啦,每个人代码风格不同,我把自己简单封装的代码贴上来吧:

MessageProcessor消息处理器:
MessageProcessor1
MessageProcessor2
IMSEventListener与ims交互的listener:
IMSEventListener1
IMSEventListener2
IMSEventListener3
MessageBuilder消息转换器:
MessageBuilder1
MessageBuilder2
MessageBuilder3
AbstractMessageHandler抽象的消息处理handler,每个消息类型对应不同的messageHandler:
AbstractMessageHandler
SingleChatMessageHandler单聊消息处理handler:
SingleChatMessageHandler
GroupChatMessageHandler群聊消息处理handler:
GroupChatMessageHandler
MessageHandlerFactory消息handler工厂:
MessageHandlerFactory
MessageType消息类型枚举:
MessageType
IMSConnectStatusListenerIMS连接状态监听器:
IMSConnectStatusListener
由于每个人代码风格不同,封装代码都有自己的思路,所以,在此就不过多讲解,只是把自己简单封装的代码全部贴上来,作一个参考即可。只需要知道,接收到消息时,会回调OnEventListener的dispatchMsg(MessageProtobuf.Msg msg)方法:
应用层接收ims消息入口
发送消息需要调用imsClient的sendMsg(MessageProtobuf.Msg msg)方法:
应用层调用ims发送消息入口
即可,至于怎样去封装得更好,大家自由发挥吧。




最后,为了测试消息收发是否正常,我们需要改动一下服务端:
改动后的服务端1
改动后的服务端2
改动后的服务端3
改动后的服务端4
改动后的服务端5
可以看到,当有用户握手成功后,会保存该用户对应的channel到容器里,给用户发送消息时,根据用户id从容器里取出对应的channel,利用该channel发送消息。当用户断开连接后,会把该用户对应的channel从容器里移除掉。


运行一下,看看效果吧:
最终运行效果



  • 首先,启动服务端。
  • 然后,修改客户端连接的ip地址为192.168.0.105(这是我本机的ip地址),端口号为8855,fromId,也就是userId,定义成100001,toId为100002,启动客户端A。
  • 再然后,fromId,也就是userId,定义成100002,toId为100001,启动客户端B。
  • 客户端A给客户端B发送消息,可以看到在客户端B的下面,已经接收到了消息。
  • 用客户端B给客户端A发送消息,也可以看到在客户端A的下面,也已经接收到了消息。

至于,消息收发测试成功。至于群聊或重连等功能,就不一一演示了,还是那句话,下载demo体验一下吧。。。


由于gif录制体积较大,所以只能简单演示一下消息收发,具体下载demo体验吧。。。


如果有需要应用层UI实现(就是聊天页及会话页的封装)的话,我再分享出来吧。



作者:FreddyChen
链接:https://juejin.cn/post/6844903815846559757
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

0 个评论

要回复文章请先登录注册