本文主要实现能主动发送握手和心跳的客户端控制台dome,记录一下自己的学习过程。
强烈建议看下面这篇文章
参考文章和代码(代码详情请看这个):跟着源码学IM(二):自已开发IM很难?手把手教你撸一个Andriod版IM-IM开发/专项技术区 - 即时通讯开发者社区!
基于TCP和Protobuf作为通信协议,使用netty构建客户端服务
Protocol Buffers 是一种轻便高效的结构化数据存储格式,用于数据的序列化,很快,很小,比json更牛,深受广大消费者喜爱
效果如图
项目结构如图。使用netty,客户端的主要是一些handler的编写
1. 准备工作
创建项目,添加依赖
创建新项目,添加新module(Android Library),设置名称 im_lib 目录如图
分析消息结构,编写.proto文件,编译 .proto 文件之前需要下载protobuf 编译器,配置环境变量
syntax = "proto3";// 指定protobuf版本
option java_package = "com.sy.im.protobuf";// 指定包名
option java_outer_classname = "MessageProtobuf";// 指定生成的类名
message Msg {
Head head = 1;// 消息头
string body = 2;// 消息体
}
message Head {
string msgId = 1;// 消息id
int32 msgType = 2;// 消息类型
int32 msgContentType = 3;// 消息内容类型
string fromId = 4;// 消息发送者id
string toId = 5;// 消息接收者id
int64 timestamp = 6;// 消息时间戳
int32 statusReport = 7;// 状态报告
string extend = 8;// 扩展字段,以key/value形式存放的json
}
这里msg.proto文件是在com目录下的,右键-Open In-Terminal,输入指令,执行生成java文件
protoc --java_out=../ msg.proto
统一依赖管理,这里使用buildSrc,详情
im_ilb/build.gradle,引入依赖
compileOnly "com.alibaba:fastjson:1.2.49"
api "com.google.protobuf:protobuf-java:3.24.4"
compileOnly files('libs/netty-tcp-4.1.33-1.0.jar')
把 netty-tcp-4.1.33-1.0.jar添加到im_lib/libs目录下
gradle sync,准备工作结束,开始业务逻辑的编写
2. 接口,封装
编写接口是为了把业务抽象出来,对代码进行解耦
对 im 服务的客户端进行分析有
1. 初始化方法
2. 建立和关闭连接
3. 发送消息
新建 com/sy/im/interf/IMSClient.java(详情请看源码)
public interface IMSClient {
/**
* ims 初始化
* @param serverUrlList 服务器地址列表
* @param listener 事件监听器
* @param callback ims连接状态回调
*/
void init(CopyOnWriteArrayList serverUrlList,OnEventListener listener, IMSConnectStatusCallback callback);
void resetConnect(); // 重置连接
void resetConnect(boolean isFirst); // 是否第一次连接
void close(); // 关闭连接,释放资源
boolean isClosed(); // ims状态
void sendMsg(MessageProtobuf.Msg msg);// 发送消息
void sendMsg(MessageProtobuf.Msg msg, boolean isJoinTimeoutManager);// 是否添加超时管理器
}
com/sy/im/interf/IMSConnectStatusCallback.java 连接回调
com/sy/im/interf/OnEventListener.java 事件监听器
编写实现类com/sy/im/netty/NettyTcpClient.java 实现接口ISMClient
编写单例模式
private static volatile NettyTcpClient instance;
private NettyTcpClient() {}
public static NettyTcpClient getInstance(){
if(null == instance){
synchronized (NettyTcpClient.class) {
if (null == instance) {
instance = new NettyTcpClient();
}
}
}
return instance;
}
创建工厂方法
public class IMSClientFactory {
public static IMSClient getIMSClient(){
return NettyTcpClient.getInstance();
}
}
3. 实现
初始化
NettyTcpClient
添加 MsgDispatcher,ExecutorServiceFactory
MsgDispatcher,消息转发器,接收消息并通过OnEventListener转发消息到应用层
ExecutorServiceFactory,线程池工厂,负责调度重连及心跳线程
连接与重连
重写resetConnect方法,把注意力放到 resetConnect(boolean isFirst)上
非首次连接,需要在连接之前多等一会,可能因为当前网络不好
连接时四步如图,双层判断加锁进行并发处理
新添加变量
私有方法onConnectStatusCallback
根据传入的connectStatus,回调callback中的方法
closeChannel
私有内部类 ResetConnectRunnable,执行连接任务
reConnect() — 初始化bootstrap
连接服务器 connectServer(),获得server host和port
调用toServer():
Bootstrap
TCPChannelInitializerHandler:
设置了自定义长度解码器,解决TCP拆包粘报问题
握手消息认证处理
当客户端与服务端长连接建立成功后,客户端主动向服务端发送一条登录认证消息,服务端返回认证结果
客户端发送的认证(握手)消息需要从应用层获取——在 IMSClient,OnEventListener 添加方法getHandshakeMsg()
在connectServer()中channel != null来判断是否连接成功
连接成功后立刻发送握手消息
LoginAuthRespHandler:
接收服务器响应的消息,拉取应用端的握手消息,比较消息类型,相同则查看响应消息中的status是否握手成功
握手成功后立刻发送心跳消息,添加心跳机制管理
消息超时管理
在将心跳之前,先讲一下这个超时重发
MsgTimeoutTimerManager管理MsgTimeoutTimer,添加,移除和重发
MsgTimeoutTimer 一个消息对应一个计时器,对应着一个定时任务
注意初始化
msgTimeoutTimerManager = new MsgTimeoutTimerManager(this);
心跳机制与读写超时
// 添加心跳消息管理 imsClient.addHeartbeatHandler();
IdleStateHandler -> HeartbeatHandler -> TCPReadHandler HeartbeatHandler HeartbeatRespHandler 接收心跳响应消息,打印日志
这里心跳部分就结束了,TCPReadHandler 就是消息处理器
当连接失效,出现异常时,触发重连,主要看channelRead
sendMsg
4. 运行调试和总结
先只测试握手消息,服务端
绑定好端口,拆包粘包处理,编解码器
收到消息,判断消息类型,修改extend字段,返回状态
返回的消息现在LoginAuthRespHandler里进行处理
客户端
IMSClientBootstrap:将数据准备好后,主要是对IMSClient对象的初始化
下面针对初始化参数,创建接口的实现类
IMSEventListener implements OnEventListener 注意,这个类主要负责与应用层交互的 保存私有变量 userId 和 token
IMSEventListener
服务器收到的
下面这两个方法先顶替一下
IMSConnectStatusListener 直接实现 IMSConnectStatusCallback 接口
调试过程中出现Java版本问题,实际上gradle7.4是能兼容Java8的,可以不用改版本。
然后是不能运行main函数的问题修改一下配置,AndroidStudio执行main方法报错,可以运行测试
下面捋一下刚创建的客户端:
创建IMSClient实例(实现类是NettyTcpClient)
首先初始化连接,如果channel创建成功,那么连接成功,onConnectStatusCallback立刻发送一条握手消息。
进入TCPChannelInitializerHandler,这三个处理器已经创建完成。收到服务端返回的消息后才创建HeartbeatHandler,即握手成功后才添加心跳管理