那时,带我手撕QQ下层!上面节录已经开始!
始终想写一则有关imMSN撷取的该文,无可奈何组织工作太忙,极难抽掉天数。那时总算从子公司离任了,急于回去歇息两天再再次找组织工作,趁天数空余,下定决心吕圣索写一则该文,即便从后辈那儿教给了许多小东西。组织工作了三年半,这五六年来始终在做SNS有关的工程项目,有现场直播、MSN、短音频撷取、GTunnel等商品,有感于MSN控制技术在两个工程项目中的必要性,秉持开放源码撷取的信念,也趁这良机归纳呵呵,因此写出这篇该文,该文有不对含意欢迎批评与尖萼。
责任编辑将如是说:
- Protobuf格式化
- TCP回收站与粘包
- 长相连击掌证书
- 心跳机制
- 重连机制
- 消息重发机制
- 读写超时机制
- 离线消息
- 线程池
- AIDL跨进程通信
本想花一部分天数如是说呵呵利用AIDL实现多进程通信,提升应用保活率,无可奈何这种方法在目前大部分Android新版本上已失效,而且也比较复杂,因此考虑再三,把AIDL这一部分去掉,需要了解的童鞋可以私信我。先来看看效果,由于Gif超过平台限制,请大家移步查看:
https://user-gold-cdn.xitu.io/2019/4/22/16a42c85e653b88c?imageslim
不想看该文的同学可以直接移步到Github fork源代码:github地址(https://github.com/FreddyChen/NettyChat)。接下来,让我们进入正题。
为什么使用TCP?
这里需要简单解释呵呵,TCP/UDP/WebSocket的区别。这里就很好地解释了TCP/UDP的优缺点和区别(https://www.cnblogs.com/Leonardo-li/p/8206945.html),以及适用场景,简单地归纳呵呵:
- 优点:
- TCP的优点体现在稳定、可靠上,在传输数据之前,会有三次击掌来建立相连,而且在数据传递时,有确认、窗口、重传、拥塞控制机制,在数据传完之后,还会断开相连用来节约系统资源。
- UDP的优点体现在快,比TCP稍安全,UDP没有TCP拥有的各种机制,是两个无状态的传输协议,因此传递数据非常快,没有TCP的这些机制,被攻击利用的机制就少一些,但是也无法避免被攻击。
- 缺点:
- TCP缺点就是慢,效率低,占用系统资源高,易被攻击,TCP在传递数据之前要先建立相连,这会消耗天数,而且在数据传递时,确认机制、重传机制、拥塞机制等都会消耗大量天数,而且要在每台设备上维护所有的传输相连。
- UDP缺点就是不可靠,不稳定,因为没有TCP的那些机制,UDP在传输数据时,如果网络质量不好,就会很容易丢包,造成数据的缺失。
- 适用场景:
- TCP:当对网络通讯质量有要求时,比如HTTP、HTTPS、FTP等传输文件的协议, POP、SMTP等邮件传输的协议。
- UDP:对网络通讯质量要求不高时,要求网络通讯速度要快的场景。
至于WebSocket,后续可能会专门写一则该文来如是说。综上所述,下定决心采用TCP协议。
为什么使用Protobuf?
对于App网络传输协议,我们比较常见的、可选的,有三种,分别是json/xml/protobuf,老规矩,我们先分别来看看这三种格式的优缺点:
- 优点:
- json优点就是较XML格式更加小巧,传输效率较xml提高了许多,可读性还不错。
- xml优点就是可读性强,解析方便。
- protobuf优点就是传输效率快(据说在数据量大的时候,传输效率比xml和json快10-20倍),格式化后体积相比Json和XML很小,支持跨平台多语言,消息格式升级和兼容性还不错,格式化反格式化速度很快。
- 缺点:
- json缺点就是传输效率也不是特别高(比xml快,但比protobuf要慢许多)。
- xml缺点就是效率不高,资源消耗过大。
- protobuf缺点就是使用不太方便。
在两个需要大量的数据传输的场景中,如果数据量很大,那么选择protobuf可以明显的减少数据量,减少网络IO,从而减少网络传输所消耗的天数。考虑到作为两个主打SNS的商品,消息数据量会非常大,同时为了节约流量,因此采用protobuf是两个不错的选择。
为什么使用Netty?
首先,我们来了解呵呵,Netty到底是个什么小东西。网络上找到的如是说:Netty是由JBOSS提供的基于Java NIO的开放源码框架,Netty提供异步非阻塞、事件驱动、高性能、高可靠、高可定制性的网络应用程序和工具,可用于开发服务端和客户端。
- 为什么不用Java BIO?
- 一相连一线程,由于线程数是有限的,因此这样非常消耗资源,最终也导致它不能承受高并发相连的需求。
- 性能低,因为频繁的进行上下文切换,导致CUP利用率低。
- 可靠性差,由于所有的IO操作都是同步的,即使是业务线程也如此,因此业务线程的IO操作也有可能被阻塞,这将导致系统过分依赖网络的实时情况和外部组件的处理能力,可靠性大大降低。
- 为什么不用Java NIO?
- NIO的类库和API相当复杂,使用它来开发,需要非常熟练地掌握Selector、ByteBuffer、ServerSocketChannel、SocketChannel等。
- 需要许多额外的编程技能来辅助使用NIO,例如,因为NIO涉及了Reactor线程模型,因此必须必须对多线程和网络编程非常熟悉才能写出高质量的NIO程序。
- 想要有高可靠性,组织工作量和难度都非常的大,因为服务端需要面临客户端频繁的接入和断开、网络闪断、半包读写、失败缓存、网络阻塞的问题,这些将严重影响我们的可靠性,而使用原生NIO解决它们的难度相当大。
- JDK NIO中著名的BUG–epoll空轮询,当select返回0时,会导致Selector空轮询而导致CUP100%,官方表示JDK1.6之后修复了这个问题,其实只是发生的概率降低了,没有根本上解决。
- 为什么用Netty?
- API使用简单,更容易上手,开发门槛低
- 功能强大,预置了多种编解码功能,支持多种主流协议
- 定制能力高,可以通过ChannelHandler对通信框架进行灵活地拓展
- 高性能,与目前多种NIO主流框架相比,Netty综合性能最高
- 高稳定性,解决了JDK NIO的BUG
- 经历了大规模的商业应用考验,质量和可靠性都有很好的验证。
以上摘自:为什么要用Netty开发(https://blog.csdn.net/xu_melon/article/details/79201198)
- 为什么不用第三方SDK,如:融云、环信、腾讯TIM?
- 这个就见仁见智了,有的时候,是因为子公司的控制技术选型问题,因为用第三方的SDK,意味着消息数据需要存储到第三方的服务器上,再者,可扩展性、灵活性肯定没有自己开发的要好,还有两个小问题,就是收费。比如,融云免费版只支持100个注册用户,超过100就要收费,群聊支持人数有限制等等…
Mina其实跟Netty很像,大部分API都相同,因为是同两个作者开发的。但感觉Mina没有Netty成熟,在使用Netty的过程中,出了问题很轻易地可以找到解决方案,因此,Netty是两个不错的选择。
好了,废话不多说,直接已经开始吧。
准备组织工作
首先,我们新建两个Project,在Project里面再新建两个Android Library,Module名称暂且叫做im_lib,如图所示:
然后,分析呵呵我们的消息结构,每条消息应该会有两个消息唯一id,发送者id,接收者id,消息类型,发送天数等,经过分析,整理出两个通用的消息类型,如下:
- msgId消息id
- fromId发送者id
- toId接收者id
- msgType消息类型
- msgContentType消息内容类型
- timestamp消息天数戳
- statusReport状态报告
- extend扩展字段
根据上述所示,我整理了两个思维导图,方便大家参考:
这是基础部分,当然,大家也可以根据自己需要自定义比较适合自己的消息结构。
我们根据自定义的消息类型来编写proto文件。
然后执行命令(我用的mac,windows命令应该也差不多):
然后就会看到,在和proto文件同级目录下,会生成两个java类,这个就是我们需要用到的东东:
我们打开瞄一眼:
小东西比较多,不用去管,这是google为我们生成的protobuf类,直接用就行,怎么用呢?直接用这个类文件,拷到我们已经开始指定的工程项目包路径下就可以啦:
加依赖后,可以看到,MessageProtobuf类文件已经没有报错了,顺便把netty的jar包也导进来呵呵,还有fastjson的:
建议用netty-all-x.x.xx.Final的jar包,后续熟悉了,可以用精简的jar包。
至此,准备组织工作已结束,上面,我们来编写java代码,实现MSN的功能。
封装
为什么需要封装呢?说白了,就是为了解耦,为了方便日后切换到不同框架实现,而无需到处修改调用的地方。
举个栗子,比如Android早期比较流行的图片加载框架是Universal ImageLoader,后期因为某些原因,原作者停止了维护该工程项目,目前比较流行的图片加载框架是Picasso或Glide,因为图片加载功能可能调用的地方非常多,如果不作一些封装,早期使用了Universal ImageLoader的话,现在需要切换到Glide,那改动量将非常非常大,而且还很有可能会有遗漏,风险度非常高。
那么,有什么解决方案呢?
很简单,我们可以用工厂设计模式进行一些封装,工厂模式有三种:简单工厂模式、抽象工厂模式、工厂方法模式。在这里,我采用工厂方法模式进行封装,具体区别,可以参见:设计模式有关资料。
我们分析呵呵,ims(IM Service,下文简称ims)应该是有初始化、建立相连、重连、关闭相连、释放资源、判断长相连是否关闭、发送消息等功能,基于上述分析,我们可以进行两个接口抽象:
public interface IMSClientInterface {
/**
* 初始化
*
* @param serverUrlList 服务器地址列表
*@param listener 与应用层交互的listener
*@param callback ims相连状态回调
*/
void init(Vector serverUrlList, OnEventListener listener, IMSConnectStatusCallback callback);
/**
* 重置相连,也就是重连
* 首次相连也可认为是重连
*/
void resetConnect();
/**
* 重置相连,也就是重连
* 首次相连也可认为是重连
* 重载
*
* @param isFirst 是否首次相连
*/
void resetConnect(boolean isFirst);
/**
* 关闭相连,同时释放资源
*/
void close();
/**
* 标识ims是否已关闭
*
* @return
*/
boolean isClosed();
/**
* 发送消息
*
* @param msg
*/
void sendMsg(MessageProtobuf.Msg msg);
/**
* 发送消息
* 重载
*
* @param msg
*@param isJoinTimeoutManager 是否加入发送超时管理器
*/
void sendMsg(MessageProtobuf.Msg msg, boolean isJoinTimeoutManager);
/**
* 获取重连间隔时长
*
* @return
*/
int getReconnectInterval();
/**
* 获取相连超时时长
*
* @return
*/
int getConnectTimeout();
/**
* 获取应用在前台时心跳间隔天数
*
* @return
*/
int getForegroundHeartbeatInterval();
/**
* 获取应用在后台时心跳间隔天数
*
* @return
*/
int getBackgroundHeartbeatInterval();
/**
* 设置app前后台状态
*
* @param appStatus
*/
void setAppStatus(int appStatus);
/**
* 获取由应用层构造的击掌消息
*
* @return
*/
MessageProtobuf.Msg getHandshakeMsg();
/**
* 获取由应用层构造的心跳消息
*
* @return
*/
MessageProtobuf.Msg getHeartbeatMsg();
/**
* 获取应用层消息发送状态报告消息类型
*
* @return
*/
int getServerSentReportMsgType();
/**
* 获取应用层消息接收状态报告消息类型
*
* @return
*/
int getClientReceivedReportMsgType();
/**
* 获取应用层消息发送超时重发次数
*
* @return
*/
int getResendCount();
/**
* 获取应用层消息发送超时重发间隔
*
* @return
*/
int getResendInterval();
/**
* 获取消息转发器
*
* @return
*/
MsgDispatcher getMsgDispatcher();
/**
* 获取消息发送超时定时器
*
* @return
*/
MsgTimeoutTimerManager getMsgTimeoutTimerManager();
}
OnEventListener是与应用层交互的listener:
IMConnectStatusCallback是ims相连状态回调监听器:
然后写两个Netty tcp实现类:
接下来,写两个工厂方法:
封装部分到此结束,接下来,就是实现了。
该文转自公众号: java进阶CTO
最后
我经过多年的收藏目前也算收集到了一套完整的学习资料,包括但不限于:分布式架构、高可扩展、高性能、高并发、Jvm性能调优、Spring,MyBatis,Nginx源代码分析,Redis,ActiveMQ、、Mycat、Netty、Kafka、Mysql、Zookeeper、Tomcat、Docker、Dubbo、Nginx等多个知识点高级进阶干货,希望对想成为CTO的朋友有一定的参考和帮助
关注后后台私信我【架构资料】即可免费获取
2.分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3.不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4.本站提供的源码、模板、插件等其他资源,都不包含技术服务请大家谅解!
5.如有链接无法下载或失效,请联系管理员处理!
6.本站资源售价只是赞助,收取费用仅维持本站的日常运营所需!