跟我一起开发商业级IM(1)—— 技术选型及协议定义

7,316 阅读25分钟

欢迎转载,转载请注明出处:juejin.cn/post/685003…

技术选型及协议定义Banner

写在前面

终于可以开始写这个系列的文章了,本系列文章预计将分为13篇,由于IM涉及的知识点稍复杂,所以每个知识点都会单独用一篇文章来阐述,尽量讲透彻,方便大家理解。

灵魂拷问

  • 为什么需要写这个系列的文章呢?
    可能大家会问,有了之前的NettyChat开源一个自用的Android IM库,基于Netty+TCP+Protobuf实现,为什么还需要写这个系列的文章呢?主要是因为一开始开源NettyChat和发布文章的时候,旨在起一种抛砖引玉的作用,带领大家入门IM而已。而且一篇文章难以阐述所有的知识点,加上NettyChat也是一个Demo,有些代码写得不严谨。一年多以来,从群里的反馈、文章的评论可以看到大家对这一块知识有不少的需求,大家去集成NettyChat到自己项目里也比较麻烦,网上也缺乏完整的IM实现,大多是零零碎碎的知识点。所以打算从零开始,手把手教大家实现自己的IM系统。


  • 对比NettyChat有什么新增的功能和优化?
    根据大家的反馈,支持TCP/WebSocket、Protobuf/Json等。优化消息重发管理器,不再是一条消息一个Timer实现(严重浪费资源,影响程序性能)。另外,优化代码结构,提升可扩展性、可维护性等。


  • 项目包含服务端代码吗?
    这也是重新写本系列文章的重要原因,在本系列文章中,将包含Java服务端代码及Android代码,至于IOS,后续有时间会增加。文章完成后,会在Github开源三个项目,分别是:

  1. ims_kula[Android IM Service SDK]
  2. kulachat_server[Java 服务端]
  3. KulaChat[Android 客户端]


  • 为什么项目叫“Kula”?
    其实就是一时兴起,Kula是作者比较喜欢的SNK拳皇系列里的一个角色,没什么具体含义。附上Kula高清图: Kula
    大纲如图: 跟我一起开发商业级IM——大纲

本系列文章将包含:

  • 技术选型及协议定义
  • 接口定义及封装
  • 粘包/粘包处理之自定义消息编解码器
  • 长连接稳定性之连接及重连
  • 心跳机制
  • 消息重发机制
  • 离线消息机制
  • 消息顺序控制
  • 数据库表设计
  • 单聊实现
  • 群聊实现
  • 系统消息实现
  • UI封装

本文为第一篇:技术选型及协议定义

项目架构

首先,讲一下项目整体架构以及使用到的开源框架。根据群里小伙伴的建议以及大家对JavaKotlin的熟悉程度,Android端开发语言还是采用Java,后续有时间会考虑用Kotlin开发一个版本。
由于项目未完成,目前是边写文章边写代码的方式,所以在后续项目完成后,会专门写一篇文章介绍项目架构,包括Android客户端Java服务端

Android客户端 项目整体采用MVP架构,用到的开源库如下:

  • RxJava 2.2.8
  • RxAndroid 2.1.1
  • Retrofit 2.8.1
  • GreenDao 3.3.0
  • Dagger 2.27
  • Butterknife 10.2.1
  • Glide 4.11.0
  • StatusBarUtil 1.5.1
  • BaseRecyclerViewAdapterHelper 3.0.4
  • AutoSize 1.2.1
  • Fastjson 1.1.71.android
  • LogginInterceptor 3.1.0-rc5
  • RxPermissions 0.10.2
  • Toasty 1.4.2
  • Background 1.6.5
  • EventCenter 1.0.1
  • NiceImageView 1.0.5
  • More...

Java服务端
还在开发中,就不一一列举了。
感谢以上开源库的作者。

以下的通信协议选型、传输协议选型、通信框架选型在开源一个自用的Android IM库,基于Netty+TCP+Protobuf实现文章中有说明过,但既然打算重新写本系列的文章,就在此重新讲解一下,一来加入自己新的理解,二来可以整合到本系列文章里。

以下所涉及的原理和理论知识,由于篇幅原因及作者水平有限,没办法详细阐述。本系列文章的着重点在于教大家怎么去实现自己的IM系统,至于偏理论的知识,作者帮大家找了一下原文链接,感兴趣的可以跳转原文阅读。同时感谢以下文章的作者。

通信协议选型

常用的通信协议有以下几种,分别简单地讲讲每种通信协议的优缺点及适用场景。

  • UDP
    • 简单概述
      UDP是一个面向报文的、非连接的协议,也就是无连接的。即发送数据前,双方无需建立连接,数据发送完毕后,也无需断开连接(没有连接可断开)。这样一来,减少了连接和断开连接的开销(无需像TCP一样连接时需要三次握手,断开连接时需要四次挥手)。同时,UDP不存在拥塞机制,即网络拥塞时,不会使源主机的发送速率降低。另外,UDP一个很大的特点就是尽最大努力交付(即不保证交付),也就是可能会存在丢包、乱序等情况。总的来说,UDP是一个不可靠的协议

    • 优点

    • 效率高
      由于UDP在发送数据前,无需建立连接,并且没有TCP一系列的确认机制、重传机制、拥塞机制等,所以在数据传输上,效率较高。

    • 开销小
      UDP首部开销仅8个字节(源端口[16bit],目的端口[16bit],长度[16bit]、校验和[16bit])。

    • 稍安全
      UDP没有TCP拥有的各种机制,被攻击利用的机制就少一些,但是也无法避免被攻击。

    • 支持广播单播组播

    • 缺点

    • 不可靠
      由于UDP没有TCP一系列的可靠性机制保证传输,在网络质量不好时,很容易丢包。所以在使用UDP作为通信协议时,往往需要自己实现可靠性保证,例如确认重传等。据说QQ早期是使用UDP作为通信协议,自己在UDP的基础上,实现类似TCP的可靠性保证,这样一来,既可实现高速率的传输,又可兼顾可靠性。网上找到的一篇讨论文:为什么QQ用的是UDP协议而不是TCP协议?,感兴趣的可以看看。

    • 乱序

    • 适用场景
      对网络通讯质量要求不高、实时性要求较高的情况下,可用UDP。比如:

    • 实时音视频聊天,这种情况丢一些帧影响不大,不需要重传,对传输速度要求高。

    • 遥控器,丢一些指令不影响。


  • TCP
    • 简单概述
      TCP是一个面向字节流、面向连接的全双工协议。通信双方在进行通信之前,需要通过三次握手建立连接,确认双方都具有数据收发的能力。由于TCP是全双工协议,因此在数据发送完毕后,每个方向都需要单独进行关闭,所以需要通过四次挥手来断开TCP连接。另外,TCP提供了确认应答超时重传滑动窗口流量控制慢启动拥塞机制等机制来保证数据有序及可靠性。总的来说,TCP是一个可靠的协议
    • 优点
    • 可靠
      TCP利用一系列机制保证数据传输的可靠性,比如确认应答超时重传校验和最大消息长度滑动窗口机制等。
    • 有序
      TCP协议在发送数据时,会对每个分片做顺序化,也就是会给每个数据包分配一个序列号,并且在特定的时间内等待接收主机对分配的这个序列号进行确认,如果发送主机在一个特定时间内没有收到接收主机的确认,则发送主机会重传此数据包。接收主机利用序列号对接收的数据进行确认,以便检测对方发送的数据是否有丢失或者乱序等,接收主机一旦收到已经顺序化的数据,它就将这些数据按正确的顺序重组成数据流并传递到高层进行处理。
    • 缺点
    • 效率低
      TCP由于发送数据前通过三次握手建立连接,发送数据完成后需要通过四次挥手断开连接,并且在发送数据过程中,需要一系列机制来保证数据的可靠性以及顺序性,所以相对于UDP来说,传输效率就低。
    • 开销大
      TCP首部开销为最小20个字节(源端口[16bit]、目标端口[16bit]、序列号[32bit]、回应序号[32bit]、TCP头长度[4bit]、reserved[3bit]、控制代码[6bit]、窗口大小[16bit]、偏移量[16bit]、校验和[16bit]、选项[长度可变,最长可达40字节](可选))
    • 适用场景
      对网络通讯质量要求较高、传输效率要求相对低,要求数据准确无误传输的场景。比如:金融类、社交App类等。
    注:作者给大家找了几篇很不错的文章,有兴趣的话可以阅读:
    1. 网络基础:TCP协议-如何保证传输可靠性
    2. TCP如何保证消息顺序以及可靠性到达
    3. 深入理解 TCP 协议:从原理到实战


  • WebSocket
    • 简单概述
      WebSocket是基于TCP实现的协议,本身就是TCP长连接应用的一种。不同的是,WebSocket握手采用Http请求,客户端在连接服务器成功后,主动向服务端发起一个Http握手请求,服务端在收到握手请求并验证通过后,给客户端返回一个Response,至此,一个合法的长连接就建立了。握手完成后,往后发送数据就是通过TCP协议了,也就是说WebSocket只是使用Http协议来完成握手操作。总的来说,WebSocket是一个基于TCP实现并封装的协议
    • 优点
      由于WebSocket是基于TCP实现的协议,所以优点跟TCP基本相同。另外,遵循RFC规范的WebSocket标准实现,协议自带包长,是不会产生粘包/拆包的情况的,因此,甚至可以认为WebSocket的功能之一就是专门处理TCP粘包问题。当然,不一定所有的WebSocket实现都遵循RFC规范,所以极端的情况,还是得自己处理,可以参考一下该答案的解释:Websocket需要像TCP Socket那样进行逻辑数据包的分包与合包吗。另外,WebSocket在TCP的基础上,封装了一些帧实现,方便我们使用。
    • 缺点
      同TCP。
    • 适用场景
      同TCP。另外,从WebSocket的命名可以看出,比较适合Browser的即时通讯实现。
    注:WebSocket和HTTP一样都是基于TCP的应用层协议。握手部分的设计目的就是兼容现有的基于HTTP的服务端组件(web服务器软件)或者中间件(代理服务器软件)。这样一个端口就可以同时接受普通的HTTP请求或者WebSocket请求了。为了这个目的,WebSocket客户端的握手是一个 HTTP升级版的请求(HTTP Upgrade request)。感兴趣可以阅读以下文章:
  1. WebSocket(1) 简单介绍及握手连接


  • MQTT

    • 简单概述
      MQTT是一种基于发布/订阅模式的“轻量级”通信协议,也是TCP长连接应用的一种(当然中间可能多一层WebSocket)。为什么既然有了TCP和WebSocket了,还需要有MQTT的存在呢?这是因为,MQTT有一个很大的优点:可以以极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务,同时根据网络环境不同,可以选择三种消息发布的质量(QoS Level):
    1. QoS0,At most once,至多一次
    2. QoS1,At least once,至少一次
    3. QoS2,Exactly once,确保只有一次

    作为一种低开销、低带宽占用的即时通讯协议,使其在物联网、小型设备、移动应用等方面有较广泛的应用。在MQTT协议中,一个MQTT数据包由固定头(Fixed header)、可变头(Variable header)、消息体(payload)三部分构成:

    1. 固定头(Fixed header)。存在于所有MQTT数据包中,表示数据包类型及数据包的分组类标识。
    2. 可变头(Variable header)。存在于部分MQTT数据包中,数据包类型决定了可变头是否存在及其具体内容。
    3. 消息体(Payload)。存在于部分MQTT数据包中,表示客户端收到的具体内容。

    总的来说,MQTT是在TCP之上的应用层协议,对物联网应用环境做了非常多的优化,TCP传输层协议,是更通用层的协议

    • 优点
      由于MQTT是基于TCP实现的协议,所以优点跟TCP基本相同。同时,MQTT也是标准的RFC协议,相比于私有协议而言更加标准,优点有:
    1. 协议非常完善,能够马上用于生产。各端实现同一套协议之后,就能进行通信;私有协议还需要进行大量的验证,看有无缺陷或欠考虑的地方。
    2. 协议的标准化带来大量的开源组件,降低开发难度。随着物联网+5G生态越来越好,开源组件越来越多,可以减少重复编码量。
    3. 标准协议利于第三方接入。当第三方设备、平台想要对接的时候,拿出一套标准的MQTT协议拍在他们脸上,再也没人有理由要求改接口了。
    4. 有很多开源的Broker,接入较方便等。

    当然,以上用TCP自己开发协议也能实现,那为什么需要MQTT呢?其实就是MQTT另外还实现了很多功能,降低了开发复杂度,比如:心跳机制、异步机制、遗嘱消息、订阅发布机制,QoS消息质量等,而且MQTT做了一些优化,比如消息头最小只有两个字节等。所以,可以简单理解为,MQTT其实就是TCP协议的一种封装实现,在TCP的基础上做了一系列优化,并且封装了很多实用的机制,一句话总结:MQTT就是观察者模式的网络放大版

    • 缺点
      同TCP。另外,虽然MQTT封装了很多机制,但还是不够成熟,实现起来较复杂。

    • 适用场景

    • 物联网IoT

    • 即时通讯IM

    • 嵌入式开发设备(不能经常联网或网络环境较差)

    • 推送

    • 车联网平台

    • 其它协议开销较小的场景等

    注:感兴趣可阅读以下文章:

    1. 一文读懂MQTT协议
    2. MQTT 入门介绍
    3. MQTT相比于TCP长连接透传的优势

传输协议选型

常用的传输协议有以下几种,分别讲讲每种传输协议的优缺点及使用场景。

  • JSON

    • 简单概述
      JSON是为了数据交换设计的一种数据格式,采用key/value键值对存储。相信大家平时在开发时已经有过许多接触,就不展开介绍了。
    • 优点
    • 可读性良好
    • 更加小巧,传输效率良好
    • 易解析
    • 缺点
    • 传输效率不算高(有需要冗余的符号,比如":"、空格等)
    • 适用场景
      绝大多数需要数据交互的场景,都可以使用。

  • XML

    • 简单概述
      XML指可扩展标记语言,这里也不作过多介绍。
    • 优点
    • 格式统一,符合标准
    • 可读性较高、自我描述性高
    • 简单、可扩展性高
    • 缺点
    • 体积庞大、无用的冗余信息较多
    • 较难解析
    • 适用场景
      配置文件、XMPP的通信格式。

  • Protobuf

    • 简单概述
      Protocol Buffers(简称Protobuf) ,是Google出品的序列化框架,与开发语言无关,和平台无关,具有良好的可扩展性。Protobuf和所有的序列化框架一样,都可以用于数据存储、通讯协议。 Protobuf的序列化的结果体积要比XML、JSON小很多,XML和JSON的描述信息太多了,导致消息要大;此外Portobuf还使用了Varint编码,减少数据对空间的占用。 Protobuf序列化和反序列化速度比XML、JSON快很多,是直接把对象和字节数组做转换,而XML和JSON还需要构建成XML或者JSON对象结构。 在一个需要大量的数据传输的场景中,如果数据量很大,那么选择protobuf可以明显的减少数据量,减少网络IO,从而减少网络传输所消耗的时间。考虑到作为一个主打社交的产品,消息数据量会非常大,同时为了节约流量,所以采用protobuf是一个不错的选择。
    • 优点
    • 传输效率快(序列化后体积较小)
    • 支持跨平台多语言
    • 序列化/反序列化速度很快
    • 缺点
    • 可读性较差(二进制格式)
    • 缺乏自描述
    • 使用不太方便(貌似找不到支持原生c语言的protobuf,大都是经过别人编译后的库)
    • 适用场景
      数据量大并且要求传输效率较高的场景。

通信框架选型

常用的通信框架有以下几种,分别讲讲每种通信框架的优缺点及使用场景。

  • Java Nio

    • 简单概述
      Java NIO 是 java 1.4, 之后新出的一套面向缓冲区、非阻塞的IO接口。既可以说是New IO,也有人认为是No-Blocking IO,但这种观点不太严谨。NIO不仅仅就是等于Non-blocking IO(非阻塞IO),NIO中有实现非阻塞IO的具体类,但不代表NIO就是Non-blocking IO(非阻塞IO)。Java NIO由以下几个核心部分组成:
    1. Buffer 缓冲区
    2. Channel 通道
    3. Selector 多路复用器

    传统的IO操作面向数据流,意味着每次从流中读一个或多个字节,直至完成,数据没有被缓存在任何地方。NIO操作面向缓冲区,数据从Channel读取到Buffer缓冲区,随后在Buffer中处理数据。NIO主要的事件有:

    1. OP_READ 读就绪
    2. OP_WRITE 写就绪
    3. OP_CONNECT 客户端连接服务端事件
    4. OP_ACCEPT 服务端接收客户端连接事件

    更多细节可以查看官方文档介绍。

    • 优点
    • 并发性高。相较于传统的BIO(Blocking IO)的实现(一个连接占用一个线程资源,并且线程资源得不到充分利用),NIO的Selector模型允许一个单独的线程来监视多个输入通道,也就是启用了少量的线程(也许是单个线程)去对事件池做监控,对比BIO来说会节省很多cpu的资源,BIO是每个连接建立一个线程,并且监控工作也是该线程完成,NIO将监控工作归为很少的线程去处理,当然这个线程不会再用于通信,保证了每个线程的利用率,自然提升了高并发性能。
    • 缺点
    • API较复杂。必须对NIO的Buffer(缓冲区)、Channel(通道)、Selector(多路复用器)了解比较透彻才能开发出健壮的程序。
    • 需要很多额外的编程技能来辅助使用NIO,例如,因为NIO涉及了Reactor线程模型,所以必须必须对多线程和网络编程非常熟悉才能写出高质量的NIO程序。
    • 想要有高可靠性,工作量和难度都非常的大,因为服务端需要面临客户端频繁的接入和断开、网络闪断、半包读写、失败缓存、网络阻塞的问题,这些将严重影响我们的可靠性,而使用原生NIO解决它们的难度相当大。
    • JDK NIO中著名的BUG--epoll空轮询,当select返回0时,会导致Selector空轮询而导致CPU100%,官方表示JDK1.6之后修复了这个问题,其实只是发生的概率降低了,没有根本上解决。
    • 适用场景
      服务器需要支持超大量的长时间连接。比如10000个连接以上,并且每个客户端并不会频繁地发送太多数据。例如总公司的一个中心服务器需要收集全国便利店各个收银机的交易信息,只需要少量线程按需处理维护的大量长期连接。 Jetty、Mina、Netty、ZooKeeper等都是基于NIO方式实现。

  • Mina

    • 简单概述
      Mina其实跟Netty很像,大部分API都相同,因为是同一个作者开发的。但感觉Mina没有Netty成熟,在使用Netty的过程中,出了问题很轻易地可以找到解决方案。在线程模型上,Mina和Netty并没有太大的差异性,主要的差异还是在任务调度的粒度的不同。另外,Mina是早期作品,Netty是在Mina之后开发的,同一个作者,功能大致的框架,所以Netty是一个不错的选择。
    • 优点
      和Netty大致相同,只是某些实现上有所不同。
    • 缺点
    • Mina将内核和一些特性的联系过于紧密,使得用户在不需要这些特性的时候无法脱离,相比下性能会有所下降,Netty解决了这个设计问题。
    • Netty的文档更清晰,很多Mina的特性在Netty里都有。
    • Netty更新周期更短,新版本的发布比较快。
    • 它们的架构差别不大,Mina靠apache生存,而Netty靠jboss,和jboss的结合度非常高,Netty有对google protocal buf的支持,有更完整的ioc容器支持(spring,guice,jbossmc和osgi)。
    • Netty比Mina使用起来更简单,Netty里你可以自定义的处理upstream events或/和downstream events,可以使用decoder和encoder来解码和编码发送内容。
    • Netty和Mina在处理UDP时有一些不同,Netty将UDP无连接的特性暴露出来;而Mina对UDP进行了高级层次的抽象,可以把UDP当成”面向连接”的协议,而要Netty做到这一点比较困难。
    • 适用场景
      同Netty。

  • Netty

    • 简单概述
      Netty是由JBOSS提供的基于Java NIO的开源框架,Netty提供异步非阻塞、事件驱动、高性能、高可靠、高可定制性的网络应用程序和工具,可用于开发服务端和客户端。 Netty是目前最流行的 NIO 框架,Netty 在互联网领域、大数据分布式计算领域、游戏行业、通信行业等获得了广泛的应用,知名的 Elasticsearch、Dubbo、阿里云IoT框架内部都采用了 Netty。
    • 优点
    • 设计优雅。适用于各种传输类型的统一 API 阻塞和非阻塞 Socket;基于灵活且可扩展的事件模型,可以清晰地分离关注点;高度可定制的线程模型 - 单线程,一个或多个线程池。
    • 使用方便。详细记录的 Javadoc,用户指南和示例;没有其他依赖项,JDK 5(Netty 3.x)或 6(Netty 4.x)就足够了。
    • 高性能。吞吐量更高、延迟更低、减少资源消耗、最小化不必要的内存复制。
    • 安全。完整的 SSL/TLS 和 StartTLS 支持
    • 社区活跃。不断更新、版本迭代周期短,发现的 Bug 可以被及时修复,同时更多的新功能会被加入。
    • 高稳定性。解决了JDK NIO臭名昭著的epoll空轮询bug。
    • 定制能力高。可以通过ChannelHandler对通信框架进行灵活地拓展。
    • 支持各种协议。比如TCP、UDP、WebSocket、MQTT等,高度封装的编解码器,简单易用。
    • 经历了大规模的商业应用考验,质量和可靠性都有很好的验证。
    • 缺点
      暂未发现,有知道的可以告诉作者,谢谢。
    • 适用场景
    • 互联网行业。在分布式系统中, 各个节点之间需要远程服务调用, 高性能的 RPC 框架必不可少, Netty 作为异步高性能的通信框架, 往往作为基础通信组件被这些 RPC 框架使用。
    • 游戏行业。Netty 作为高性能的基础通信组件, 提供了 TCP/UDP 和 HTTP 协议栈, 方便定制和开发私有协议栈, 账号登录服务器;地图服务器之间可以方便的通过 Netty 进行高性能的通信。
    • Netty除了能开发即时通讯类的应用外,也能用来实现HTTP服务器。


  • Socket.IO
    Socket.IO也是一个开源框架,可用于在浏览器和服务器之间进行实时,双向和基于事件的通信。用得比较少,就不详细介绍了。

通用协议定义

主要是讲讲Protobuf的文件格式定义,JSON就是key/value键值对,没什么好说的。
我们先分析一下,怎样的消息格式,才算是通用的,也就是单聊、群聊、系统消息等,都可以用的统一消息格式,这个比较重要,关系到后续的扩展性、通用性等,先看个图:
Protobuf格式定义

对应编写的msg.proto代码如下:

syntax = "proto3";// 指定protobuf版本
option java_package = "com.freddy.kulaims.protobuf";// 指定包名
option java_outer_classname = "MessageProtobuf";// 指定类名

message Msg {
    Head head = 1;// 消息头
    Body body = 2;// 消息体
}

message Head {
    string msgId = 1;// 消息id
    int32 msgType = 2;// 消息类型
    string sender = 3;// 发送者
    string receiver = 4;// 接收者
    int64 timestamp = 5;// 发送时间戳,单位:毫秒
    int32 report = 6;// 消息发送状态报告
}

message Body {
    string content = 1;// 消息内容
    int32 contentType = 2;// 消息内容类型
    string data = 3;// 扩展字段,以key/value形式存储的json字符串
}

编写完msg.proto文件后,通过以下步骤即可生成我们需要用到的MessageProtobufJava类:

  1. 在项目src/main目录下,新建proto文件夹,与src/main/java同级。

  2. msg.proto文件复制到项目src/main/proto文件夹。

  3. project级的build.gradle文件的dependencies节点下,加入

classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.12'
  1. app级的build.gradle文件,加入
apply plugin: 'com.google.protobuf'
  1. app级的build.gradle文件的android节点,加入
sourceSets {
    main {
        java {
        	srcDir 'src/main/java'
    	}

    	proto {
    		srcDir 'src/main/proto'
    	}
    }
}
  1. app级的build.gradle文件的dependencies节点,加入
implementation 'com.google.protobuf:protobuf-java:3.8.0'
  1. app级的build.gradle文件根节点(也就是与androiddependencies节点同级),加入
protobuf {
    //配置protoc编译器
    protoc {
        artifact = 'com.google.protobuf:protoc:3.8.0'
    }
    //这里配置生成目录,编译后会在build的目录下生成对应的java文件
    generateProtoTasks {
        all().each { task ->
            task.builtins {
                remove java
            }
            task.builtins {
                java {}
            }
        }
    }
}
  1. 点击build->Make Project,即可在项目生成的build/generated/source/proto/debug/java/proto文件java_package指定的包名下看到生成的MessageProtobuf.java文件,文件自动生成,不需要改动:
    生成的MessageProtobuf文件

注:以上protobuf版本不是规定的,大家可以选择自己喜欢的版本,但强烈建议前后端版本一致,否则有可能会出现兼容性的问题。

不少同学也会用多个proto文件来表示不同消息,比如用户登录消息user_login.proto、聊天消息chat.proto,这样也未尝不可,只是这样会有很多个proto文件,后期维护比较麻烦,这也就是为什么需要设计通用的proto文件格式的原因。

最后,贴一张JSONProtobuf序列化后的字节长度对比图,两个User对象和一个timestamp字段,可以看到json序列化后,字节长度为140,而同样的内容在Protobuf序列化后,字节长度为49
JSON与Protobuf序列化字节长度对比
拿到MessageProtobuf.java文件,意味着我们已经完成了第一步,距离我们开发完成的商业级IM系统又接近了一步,在下一篇文章,我将会详细介绍接口定义及封装,敬请期待。

最终技术选型

综上所述,在即时通讯方面,最终技术选型如下:

通信协议

通信协议采用TCPWebSocket两种,UDP不考虑,至于MQTT,后续如果有需要的话会考虑实现。

传输协议

传输协议采用ProtobufJSON两种,在IM SDK初始化时指定。XML不考虑。

通信框架

通信框架采用Netty,后续如果有需要,会采用Java NIOMina实现。Socket.IO不考虑。

写在最后

之前在开源一个自用的Android IM库,基于Netty+TCP+Protobuf实现,有同学评论,TCP是面向字节流的,没有包的概念,哪来的拆包/粘包的说法呢?首先说明,作者不会误导大家,TCP确实没有拆包/粘包的说法,相关的TCP/IP书籍上也没有提到过,这个说法只是误传,但已经深入人心,所以作者也就用这词了。拆包/粘包的概念应只存在应用层,TCP不存在粘包/拆包的说法,只是没有消息边界而已。后续在第3篇文章,会专门解释。

终于写完了,发现写原创文章太难了,一来要考虑表述的方式,二来要考虑排版是否美观,还要考虑是否符合大家的需要,所以拖延症又发作了~ 但会坚持把整个系列的文章都写完,把项目完善并开源,希望对大家有所帮助。之所以分系列文章来写,一方面是因为一篇文章实在没办法讲清楚。另一方面,希望在写文章的过程中,大家可以给我提点意见或建议。一个人精力及水平有限,有很多观点也许不太正确和完善,希望大家体谅。欢迎吐槽,欢迎拍砖,接受批评。


PS:新开的公众号不能留言,如果大家有不同的意见或建议,可以到掘金上评论或者加到QQ群:1015178804,如果群满人的话,也可以在公众号给我私信,谢谢。 贴上公众号:
FreddyChen
FreddyChen的微信公众号