前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >移动互联网IM之协议设计

移动互联网IM之协议设计

作者头像
MelonTeam
发布2018-01-04 17:55:06
3.9K1
发布2018-01-04 17:55:06
举报
文章被收录于专栏:MelonTeam专栏MelonTeam专栏

导语:如果想自己动手实现一个移动互联网IM app,要怎么做?第一个要解决的问题就是IM协议的设计。本文将讲述如何从0到1设计一个私有的tcp协议。

虽然现在市面上已经存在各种各样的消息推送SDK如信鸽,但可能由于各种原因无法全面满足需求,还是想自己实现一个IM或推送功能。那么你需要解决哪些问题呢?首先面临的第一个问题就是如何实现IM协议?

传输协议选择

传输协议一般是指TCP和UDP协议。UDP协议是无连接的,面向消息的,主要提供高效率服务。它的效率高,占资源少,但是其传输不可靠,只管发送,不管对方是否收到,虽然可以通过其他手段来实现可靠性。TCP是面向连接的,面向流的,主要提供可靠性服务。可靠性正是IM最需要的特性,所以现在主流IM基本都是使用TCP协议实现的。      

关于PC QQ仍然在使用UDP的问题,经过私下了解是由于历史原因,所以一直沿用到现在。笔者猜测应该是因为当年C10K问题没有得到很好的解决,因为TCP是面向连接的,当时还没有epoll技术的存在,无法很好地解决同时在线的高负载问题,所以只能使用UDP了,因为UDP是无连接的,没有负载问题,但UDP又不可靠,所以只能在UDP上实现TCP的超时、重传、确认等机制。

协议格式选择

常见的TCP协议格式通常有3种:文本协议、二进制协议、XML协议。

文本协议

文本协议一般是由一串ACSII字符组成的数据。文本协议容易被人类解读,比较适合面向公众,典型的如HTTP协议。举一个HTTP GET的例子:

代码语言:javascript
复制
GET /HTTP/1.1
User-Agent: curl
Host: qq.com
Accept: */*

文本协议的特点: a. 可读性好,便于开发调试; b. 扩展性好,key-value扩展容易; c. 解析效率较好; d. 流量较小。        

曾经一方霸主的IM产品MSN使用的是就是文本协议。

XML协议

主流IM协议之一XMPP就是一种以XML为基础的开放式实时通信协议。举一个XMPP发送消息的例子:

代码语言:javascript
复制
<message from="sendinguser@somedomain" to="recipient@somedomain" xml:lang='en'>
  <body>
    Body of message
  </body>
</message> 

XML协议的特点: a. 继承了XML的优点,可读性好,扩展性好; b. 解析代价较高,效率低,占用资源多; c. 流量大。      

 Google出品的IM产品GTalk正是使用XMPP协议。

二进制协议

二进制协议就是一串字节流,一般包括定长的包头和可扩展变长的包体,典型的如MQTT协议。举一个二进制协议例子:

Alt text
Alt text

二进制协议特点: a. 可读性差,难于调试; b. 扩展性较差; c. 解析效率高,几乎没有解析代价; d. 流量占用极少。        

QQ和微信正是使用二进制的典型代表,现在市面上大部分IM产品也都是使用二进制。虽然它可读性差,难于调试,可这正也是提高协议被破解的门槛。所以对流量和电量敏感的移动互联网IM来说,二进制协议最为适合。

主流协议比较

在比对了协议格式后,我们接着比较一下各种协议标准。目前市面上主流的IM协议主要有应用于PC互联网的XMPP,嵌入式设备物联网上的MQTT,一起来看下它们之间的优缺点比较:

| 名称 | 优点 | 缺点 | | :—- |:——— | :——— | | XMPP | 基于XML协议,容易理解,使用广泛,易于扩展 | 流量大,在移动终端解析也耗电。交互过程复杂,多被pc时代的产品使用,不适合应用于移动互联网IM | | MQTT | 低带宽,适合推送,适配多平台 | 协议简单,但是需要自己扩展好友,群组等功能 | | 私有协议 | 灵活、低带宽、自主控制 | 要考虑可扩展、兼容性、序列化和反序列化、安全等问题 |

私有协议设计

基于TCP的应用层协议一般都分为包头和包体(如HTTP),IM协议也不例外。所以常见的做法是:定长二进制包头,可扩展变长包体,包体可以使用文本如Protobuf、MessagePack、JSON、XML等扩展性好的协议。包头负责传输和解析效率,是所有包的公共部分,与业务无关。包体保证扩展性,与业务相关。一个典型的二进制协议如下:

| 字段 | length | message_id | version | type | data | | :—: | :—: | :—: | :—: | :—: | :—: | | 类型 | int | int | byte | int | byte[] | | 字节数 | 4 | 4 | 1 | 4 | n |

1、length:包长度,告知服务端要接收多长的包数据;

2、message_id:消息ID,由于网络复杂性,客户端和服务端的交互消息可能无法保证必达,所以需要重发来保证,为了避免消息重复,可以使用消息的唯一标识来去重;

3、version:消息版本号,由于二进制格式扩展性不好,如果要扩展字段,旧版协议就不兼容了,所以一般会有一个version字段用于区分版本;

4、type:消息类型,用来区分不同功能的消息包,如密钥交换消息、心跳消息、业务消息、错误返回消息、推送消息等;

5、data:包体数据,业务不同,长度可变。

粘包问题

值得一提的是,由于TCP是基于流式数据传输的,所以会存在“粘包”问题,所谓“粘包”,是指在一次接收数据不能完整收到一个完整的消息包数据。举个例子,假设服务端按顺序发了3个包消息,如下图表示:

Alt text
Alt text

但客户端读取到的数据很可能会被分成下面几个片段:

Alt text
Alt text

这就是所谓的“粘包”问题,其解决办法一般有如下两种: 1、消息包头中包含表示消息包的总长度的字段(或者消息包体长度),上述举例的length正是采用该方案; 2、包尾添加特殊分隔符,例如每条报文结束都添加回车换行符(例如FTP协议)或者特定字符作为报文分隔符,接收方通过特殊分隔符切分报文,比如上述举例可以修改成如下格式: | 字段 | length | message_id | version | type | data_length | data | delmiter | | :—: | :—: | :—: | :—: | :—: | :—: | :—: | :—: | | 类型 | int | int | byte | int | int | byte[] | byte| | 字节数 | 4 | 4 | 1 | 4 | 4 | n | 1 |

其中delmiter可以固定为“@”等特殊字符,delmiter应尽量小,减少流量占用。另外由于包体可能包含分隔符,所以delmiter需要转义以防止解析错误,所以一般更为建议使用第一种方案解决“粘包”问题。

反设计

包头和包尾都包含分包分隔符:笔者过往接触到不少项目的协议都采用了这种方法来分包,通过以上“粘包”问题分析可知,这种做法只会浪费流量,不会有更多好处。

序列化选择

包体可以使用文本如Protobuf、MessagePack、JSON、XML等扩展性好的协议,但我们推荐优先考虑Protobuf,网上对序列化和反序列化的方案选择的讨论也非常多,我们这里就不再赘述,这也是目前主流IM的选择。 Protobuf优点:

  1. 标准的IDL和IDL编译器,这使得其对工程师非常友好;
  2. 序列化数据非常简洁,紧凑,序列化后的大小是json的1/10,xml格式的1/20,是二进制序列化的1/10;
  3. 解析速度非常快,比对应的XML快约20-100倍;
  4. 提供了非常友好的动态库,使用非常简介,反序列化只需要一行代码。

Protobuf适合的场景:

  1. 需要和其它系统做消息交换的,对消息大小敏感的,消息空间相对xml和json等节省很多;
  2. 小数据的场合。如果你是大数据,用它并不适合;
  3. 项目语言是c++,java,python,因为它们可以使用google原生类库,序列化和反序列化效率非常高。 所以Protobuf解析性能高,序列化后数据量相对少,非常适合应用到移动互联网IM的场景。

安全性考虑

敏感信息直接通过IM进行网络传输,所以安全层是必不可少的,一般只需要对包体进行加密,包头明文即可。换句话说,TCP协议的安全性主要可以从以下几个方面进行考虑:

使用SSL

和HTTPS一样,使用SSL安全性高,但不同的是,HTTPS是由专门机构去验证证书合法性的,而IM不可能这样做,可行的做法是把证书打包进客户端,证书更新可以随客户端升级而一起升级,或者通过协议升级。加密的交互流程就是客户端产生一个对称的密钥,并通过证书加密后请求交给服务器,服务器解密后获得这个对称密钥,后续的通讯就全部使用这个对称的密钥来加解密,具体原理请参考SSL,这里不再赘述。不过证书成本稍高和管理稍复杂,代价较高。

自己加解密

自己实现加解密,重点在于密钥的生成与管理,密钥管理方式主要有这么两种:

1) 固定密钥    

服务端和客户端约定好一个密钥,同时约定好一个对称加密算法如AES,每次客户端发送消息前,使用约定好的算法和密钥对消息进行加密,服务端收到报文后,使用约定好的算法和密钥进行解密。这种方式优点是实现比较简单,但缺点也很明显,约定好的密钥和算法存在客户端,存在被反编译破解的风险,该方案比较适合对加密要求不高的场景;

2) 动态密钥

由于固定密钥容易暴露,所以动态密钥的理念就是对固定密钥再加一层保护。和SSL密钥协商过程类似,动态密钥的中心思想就是客户端和服务器通过非对称RSA加解密(增加破解难度)进行协商,最终客户端获得一个当前session的密钥,后续的数据传输都通过这个密钥进行AES对称加解密。流程比较复杂,具体如下图所示:

Alt text
Alt text

公钥请求:

1、客户端携带帐号发起请求;

2、服务端根据帐号生成对应的RSA公、私钥;

3、服务端下发公钥,保留私钥;

4、服务端返回RSA公钥给客户端,客户端保存RSA公钥;

Alt text
Alt text

登录鉴权: 1、客户端使用RSA公钥对帐号和密码等价物(帐号密码按一定规则编码)进行RSA非对称加密,然后携带这个加密结果发起请求; 2、服务端使用RSA私钥解密,获得帐号和密码; 3、服务端验证帐号和密码是否正确; 4、服务端给客户端分配当前session的密钥session_key; 5、服务端返回经过AES加密的session密钥session key,AES的密钥为帐号/密码等价物。后续请求都使用session_key作为密钥进行加解密。

Alt text
Alt text

非登录请求:

1、客户端使用session_key作为密钥对请求进行AES对称加密,发起请求;

2、服务端使用session_key对请求进行AES解密;

3、根据请求处理业务逻辑;

4、服务端使用session_key作为密钥对处理结果进行AES加密,返回给客户端。    

 终上所述,文章主要阐述了移动互联网IM的协议设计会面临的主要包括传输协议、协议格式、协议设计、协议序列化、协议安全等问题,以及对应的解决方案,这些是笔者对过往项目的总结和思考。在身处微信和QQ两大主流移动互联网IM压力下,该文章确有班门弄斧之嫌,如有不足或错误,还请各路IM大神指教:)        值得一提的是,文章的思考也将同样也适用于其他使用tcp长连接的场景,如物联网、手游等。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017-03-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 传输协议选择
  • 协议格式选择
    • 文本协议
      • XML协议
        • 二进制协议
        • 主流协议比较
        • 私有协议设计
          • 粘包问题
            • 反设计
            • 序列化选择
            • 安全性考虑
              • 使用SSL
                • 自己加解密
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档