首页 > 网名 正文
“qq列表设计“qq怎么设计?

时间:2023-05-20 作者:佚名

今天,带你们手撕QQ底层!下面正文开始!

一直想写一篇关于im即时通讯分享的文章,无奈工作太忙,很难抽出时间。今天终于从公司离职了,打算好好休息几天再重新找工作,趁时间空闲,决定静下心来写一篇文章,毕竟从前辈那里学到了很多东西。工作了五年半,这三四年来一直在做社交相关的项目,有直播、即时通讯、短视频分享、社区论坛等产品,深知即时通讯技术在一个项目中的重要性,本着开源分享的精神,也趁这机会总结一下,所以写下这篇文章,文中有不对之处欢迎批评与指正。

本文将介绍:

  • Protobuf序列化
  • TCP拆包与粘包
  • 长连接握手认证
  • 心跳机制
  • 重连机制
  • 消息重发机制
  • 读写超时机制
  • 离线消息
  • 线程池
  • AIDL跨进程通信

本想花一部分时间介绍一下利用AIDL实现多进程通信,提升应用保活率,无奈这种方法在目前大部分Android新版本上已失效,而且也比较复杂,所以考虑再三,把AIDL这一部分去掉,需要了解的童鞋可以私信我。先来看看效果,由于Gif超过平台限制,请大家移步查看:

https://user-gold-cdn.xitu.io/2019/4/22/16a42c85e653b88c?imageslim

不想看文章的同学可以直接移步到Github fork源码:github地址()。接下来,让我们进入正题。

为什么使用TCP?

这里需要简单解释一下,TCP/UDP/WebSocket的区别。这里就很好地解释了TCP/UDP的优缺点和区别(),以及适用场景,简单地总结一下:

  • 优点:
  • 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,从而减少网络传输所消耗的时间。考虑到作为一个主打社交的产品,消息数据量会非常大,同时为了节约流量,所以采用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开发()

  • 为什么不用第三方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的:

建议用ne的jar包,后续熟悉了,可以用精简的jar包。

至此,准备工作已结束,下面,我们来编写java代码,实现即时通讯的功能。

封装

为什么需要封装呢?说白了,就是为了解耦,为了方便日后切换到不同框架实现,而无需到处修改调用的地方。

举个栗子,比如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<String> serverUrlList, OnEventListener listener, IMSConnectStatusCallback callback);

/**

* 重置连接,也就是重连

* 首次连接也可认为是重连

*/

void resetConnect();

/**

* 重置连接,也就是重连

* 首次连接也可认为是重连

* 重载

*

* @param isFirst 是否首次连接

*/

void resetConnect(boolean isFirst);

/**

* 关闭连接,同时释放资源

*/

void close();

/**

* 标识ims是否已关闭

*

* @return

*/

boolean isClosed();

/**

* 发送消息

*

* @param msg

*/

void sendMsg msg);

/**

* 发送消息

* 重载

*

* @param msg

* @param isJoinTimeoutManager 是否加入发送超时管理器

*/

void sendMsg msg, boolean isJoinTimeoutManager);

/**

* 获取重连间隔时长

*

* @return

*/

int getReconnectInterval();

/**

* 获取连接超时时长

*

* @return

*/

int getConnectTimeout();

/**

* 获取应用在前台时心跳间隔时间

*

* @return

*/

int getForegroundHeartbeatInterval();

/**

* 获取应用在后台时心跳间隔时间

*

* @return

*/

int getBackgroundHeartbeatInterval();

/**

* 设置app前后台状态

*

* @param appStatus

*/

void setAppStatus(int appStatus);

/**

* 获取由应用层构造的握手消息

*

* @return

*/

Me getHandshakeMsg();

/**

* 获取由应用层构造的心跳消息

*

* @return

*/

Me 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进阶架构师

最后

我经过多年的收藏目前也算收集到了一套完整的学习资料,包括但不限于:分布式架构、高可扩展、高性能、高并发、Jvm性能调优、Spring,MyBatis,Nginx源码分析,Redis,ActiveMQ、、Mycat、Netty、Kafka、Mysql、Zookeeper、Tomcat、Docker、Dubbo、Nginx等多个知识点高级进阶干货,希望对想成为架构师的朋友有一定的参考和帮助

关注后后台私信我【架构资料】即可免费获取

本文信息为网友自行发布旨在分享与大家阅读学习,文中的观点和立场与本站无关,如对文中内容有异议请联系处理。

本文链接:https://www.paituo.cc/wangming/994289.html

  • 相关阅读

    qq列表设计,QQ列表设计

    qq列表设计,QQ列表设计,qq图标设计相关介绍,看标题这么高大上,实际上,还是运用我么拿到listview去扩展,我们讲什么呢,就是研究一下QQ,微信的这种对话列表,我们先看一个传统的ListView是怎么样的,我们做一个通讯录吧,通讯录的组成就是

    “qq列表设计“QQ列表设计…

    qq列表设计,QQ列表设计,qq图标设计相关介绍,作为熟人社交的QQ,如何成为00后的主流选择,不让人心受到熟人的厌烦? 01 90或80后早上起床的第一件事是什么? 微信打开,看看有没有消息,再点点朋友圈,看看朋友们精彩的“夜生活”。 看了什么电影

    『qq列表设计』QQ列表设计…

    qq列表设计,QQ列表设计,qq图标设计相关介绍,数据列表页面是后台产品设计中非常常见的页面。 本文将讨论如何对这类页面进行设计,让你避免其中可能存在的坑! 在后台产品设计中,数据列表页是非常常见的一个页面,该页面负责展示数据,并提供搜索查询功能来供

    〈qq列表设计〉qq音乐设计

    qq列表设计,qq音乐设计,QQ列表设计相关介绍,支付宝新旧LOGO对比 3月初,支付宝时隔五年后更新LOGO,将主色调由沉稳的蓝色改为更明快的亮蓝色,反映支付宝从金融支付工具到全球最大数字生活开放平台的转变。 近日,我们又发现另外一款工具也开始改变

    qq列表设计qq音乐设计说明…

    qq列表设计,qq音乐设计说明,QQ列表设计相关介绍,马化腾的童年是在八所港度过的,他的父母是八所港港务局的职员,那里是海南岛的最西端,居民以苗族为主。 1984年,13岁的马化腾随父母从海南岛迁居到了深圳。 1 马化腾的学生时期 “它出现在北斗星的西南

    qq列表设计 qq装扮设计…

    qq列表设计,qq装扮设计,QQ列表设计相关介绍,想必差友们都听过这样一句调侃:老年人才用微信,年轻人都爱玩 QQ。 世超一开始还不信,不过自从年龄大了以后,还是不自觉的从企鹅转移到微信上来。 尽管如此,自己有时候难免还是会忍不住打开 QQ 看看有什

    〈qq列表设计〉qq标签设计…

    qq列表设计,qq标签设计,QQ列表设计相关介绍,编辑导语:产品体验是一个很好的培养自己阅读习惯和设计思维的方法,但阅读并把自己理解的东西写出来呈现给别人确是一个很艰难的过程。本篇文章作者为我们分享了他自己在产品体验中培养和运用设计思维的过程,从产品

    qq列表设计——qq怎么设计

    qq列表设计,qq怎么设计,QQ列表设计相关介绍,编辑导语:创新的目的不是在于“新”这一字,而是为了更优、进化和解决问题,让用户的价值有所增长才是我们更新优化的目标。每一位设计师都希望自己的方案带来的效果是前所未有的创新。都抱着同样的希望,为什么却有