QQ通讯过程原理分析
闲来没事,写篇关于 QQ 通讯的文章,写这篇文章的起因是因为前段时间和某位萌妹子用 QQ 唠嗑,结果应该是腾讯服务器出现问题了,发送消息显示发送失败,但对方却是接收到消息的,持续了一段时间。因为自己是从事 IT 行业的,后来就被妹子问起了缘由(为妹子的好奇心点赞,还不停的追问我网络通信相关问题),自己之前也没对类似 QQ 的即时通讯软件的通信机制有过多的了解,只隐约记得在某处了解过 QQ 消息传送貌似使用的是 UDP 协议,当时也没太在意,比较随意的解释了一翻,但是秉着对知识对妹子负责的态度,感觉自己还是得去整清楚这里面的门道,方便下次好好的给人家解释一翻,Google 了许多资料学习了一下,也回顾了一下自己计算机网络相关的知识,便写下了这篇自己总结出来的文章。
注:这里只是拿 QQ 作为例子大致的分析了一下其通讯过程中的原理,至于腾讯内部究竟是怎么实现的只有其内部项目参与人员才知晓,但是殊途同归,类 QQ 的即时通讯软件实现的大致的方向应该就是我接下去所描述的了。
一、QQ 登录服务器
QQ 在登录的时候,可以使用 UDP 和 TCP 方式,在登录界面也可以自行设定登录服务器使用的协议(不设置的话默认是使用 UDP)。不过不管使用的是 UDP 还是 TCP 协议登录腾讯的服务器,只要是登录成功后,QQ 客户端和服务器端都会和服务器建立起一个长连接(使用的貌似是 TCP 长连接),让 QQ 保持在线状态。
-
1. 那由于 IPV4 的 IP 资源有限,现如今我们使用的都是内网 IP (这里真的是很期待 IPV6 的全面普及....),我们是如何连接上腾讯的服务器的呢?
这里就是由我们局域网类的 NAT 服务器来解决了,NAT 是 Network Address Translation 的缩写,中文含义指的是网络地址转换。NAT 服务器做的功能其实很简单,由于内网 IP 在公网在是不可见的,无法和外界互通,只能访问局域网内的主机,在 IPV4 资源有限的情况下往往整个子网中只有少数的几台主机拥有公网 IP ,所以内网中的其他私网 IP 想要访问公网中的其他主机,必须通过局域网中的 NAT 服务器经过地址转换后来访问,这些 NAT 是拥有公网 IP 的,可以访问互联网上所有拥有公网 IP 的主机(除使用了某些特殊手段封锁了某些公网 IP 外)。QQ 在使用内网登录时就是通过 NAT 服务器经过地址转换后来访问腾讯服务器的,过程大致如下图(IP、端口都是捏造的):

QQ 客户端(10.11.19.1:3545)要访问腾讯的服务器端时,会先经过局域网内的 NAT 服务器,在 NAT 服务器中则会将原 IP 地址修改成如图所示的情况(65.49.218.12),此时 NAT 服务器就会创建一个 Session,并且为这个 Session 分配一个端口号(3000),所以在局域网中本来是(10.11.19.1:3545 > 120.119.114.118:80)的数据包到了互联网之后就变成了(65.49.218.12:3000 > 120.119.114.118:80), NAT 服务器 Session 中也记录了本机(65.49.218.12)的 3000 端口对应于局域网内 10.11.19.1 主机的 3545 端口(也就是QQ使用的端口) ,所以当腾讯服务器(120.119.114.118)发送到 NAT 服务器 3000 端口的数据就会被转发到 QQ 客户端(10.11.19.1:3545)中,也就是说 65.49.218.12:3000 和 10.11.19.1:3545 之间就是形成了一个映射(65.49.218.12:3000 在互联网上就代表着 10.11.19.1:3545),相当于子网在 NAT 服务器上打了一个洞(有关内网打洞实现 P2P 在第三节),好让公网主机能够访问到自己。
然后我们在本地主机中的看到的网络连接是这样的 (netstat )
10.11.19.1:3545 120.119.114.118:80 establish
但是对于腾讯的服务器来说,与腾讯服务器保持连接的是局域网类的 NAT 服务器,所以腾讯服务器中,网络连接情况是这样的:
120.119.114.118:80 65.49.218.12:3000 establish
其中 NAT 服务器中的端口(3000)映射出的就是本机的端口(3545),而此时的QQ也是与腾讯服务器保持长连接的状态,所以当腾讯服务器有消息需要传递给你时,只需把消息发送给 NAT 服务器即可,NAT 服务器通过前面的映射关系会转发到 QQ 客户端中。
注:以 TCP 方式连接,腾讯服务器貌似使用的远程端口是 443 (客户端登录设置时可以查看),而 UDP 使用的是 8000,所以假如所在局域网中或者本机的防火墙禁用掉了这些端口,是没法登录 QQ 的。
二、通讯过程
即时通讯软件的通讯过程分为以下两种方式来实现端对端的通讯:
- 服务器中转方式:直接登陆服务器,所有信息由服务器转发,你的发给对方的消息先经过中转服务器,再由中转服务器转发给对方。
- P2P 连接:首先连接登陆服务器,在给对发发消息的时候,在服务器的辅助下与对方进行内网打洞,实现 P2P 连接(有关内网打洞实现 P2P 在第三节)。
QQ 的通讯过程个人分析应该是由服务器中转和 P2P (内网打洞穿透)两种方式混合使用的,传输层使用的是 UDP 协议。其实不管是传递什么消息都是可以走这两种方式的,但是综合各种原因,比如法律的监管问题、代价大小、不同人所处的网络环境是不同的等等,现实中在不同情况下腾讯肯定有不同的应对策略,所以这里只分析绝大部分情况下,QQ 的通讯过程是怎么个样子的。
对于 QQ 文字聊天消息我猜测是使用第一种服务器中转的方式,而代价比较大的,比如视频、语音、传输文件(估计现如今传输文件也是走的中转方式)应该使用的是 P2P 的方式。所以我们在使用文字聊天时显示消息发送失败,但是对方却是接收到了的情况(文章开头这类情形),就是因为 UDP 协议是不安全的连接,往往在传输过程中会发生丢包等情况。腾讯这里在应用层有做相关处理:发送方将消息发送给接收方(下图蓝色箭头即是发送方消息的传输路线),经中转服务器中转消息之后,接收方接到消息。接收方接收成功后会回复一条接收成功的消息(红色箭头),经由中转服务器告知发送方消息发送成功。

虽然在应用层有做这些处理,但是如上图中,只要蓝色箭头的消息成功发送到接收方,而红色箭头的消息传送有一环出现丢包,发送方就会出现即使自己的消息发送成功了,但是客户端中显示的就是发送失败,而且这种模式下可能还会出现发送的消息是无序的情况(这里就可以推断出其传输层用的不是 TCP 协议了)。
也有人说应该使用的是 P2P 方式进行通讯的,因为假如是服务器做中转的话,服务器会承受不住,但是我个人认为应该是这两种方式并存的,而且是服务器中转的方式用的比较多!因为比例说下面三个原因该如何?
- 消息会做相应的过滤,有些敏感信息貌似会发不了,而且聊天记录还会保存在服务器里(P2P当然也可以实现保存到云端,但是还是得传到服务器)。
- QQ 群聊天,这个总不可能用 P2P 方式来做吧?
- 给离线的 QQ 好友发送消息又该如何?
所以我猜腾讯在涉及到不得不用服务器中转来实现的地方,那肯定是不想砸钱也得砸下去,这中间肯定有其平衡拿捏的地方(像视频语音这类代价过大的就是能走 P2P 就走 P2P),而且腾讯家大业大,能靠钱堆出的服务器应该不是什么大问题。
前面提到过 QQ 使用服务器中转的方式多于 P2P 方式,那是因为我个人觉得像 QQ 如果视频、音频、文件传输全经过服务器来中转的话,对于服务器是一个沉重的负担,而且代价过大,对于这一块腾讯肯定是做了取舍的,能够使用 P2P 就用 P2P 方式,所以这一块应该就是 P2P 方式优先了,要是双方打洞不成功实现不了 P2P 连接就是退而求其次,使用服务器中转的方式。
但是就现在 TIM 的情况来看,发送的文件还会在云端服务器保存一份,而且文件传输往往是别人传完了,自己这边才开始接收,所以这里又有极大的可能现在使用的 QQ 传输文件走的就是服务器中转的方式了。也在网上看见有人说通信双方通过局域网下传输文件会比非局域网上快,我猜测应该是老版本的 QQ 上在传输文件中就是使用 P2P 优先的方式进行的传输的,假如双方能打洞成功实现 P2P 连接,又发现双发其实是在同一个局域网类,传输速度肯定是会快于非局域网的,毕竟传输过程中不需要通过其他的外部网络。
注:以上都是本人结合自己所学的一些主观分析,QQ 真实的内部实现,肯定只有其内部相关工作人员才知道。本人水平有限,有机会还请大神赐教。
三、内网打洞实现 P2P 连接
通过第一节可知,子网内的主机通过 NAT 服务器是可以主动连接到拥有公网 IP 的主机的,但是反过来公网上的主机想要主动访问不同局域网内的子网主机是不可能的。而由于 IPV4 资源有限的原因,用户都是通过 NAT 服务器解析后来上网的,都处于子网状态,所以两个不同子网内的两台主机实现 P2P 连接就需要使用到所谓的内网打洞技术了。
想要内网打洞实现穿透必须要有一台公网 IP 的服务器作为桥梁,话不多少,先上一波图,内网打洞过程大致流程如下图:

通过第一节可知,两台内网中的主机 Client 1 和 Client 2 都可以通过各自的 NAT 服务器建立起相应的 Session 连接到腾讯服务器(T Server),并且不断发送心跳包保持与服务器的连接状态,而此时 Clinent 1 是可以通过 T Server 获得 Client 2 的 NAT 2 公网地址端口并且向这个地址端口发送消息(220.49.211.52:4567)的,但是对于 Client 2 这个时候是接收不到 Client 1 发送来的消息的,因为 NAT 2 服务器 220.49.211.52:4567 的 Session 信息中记录的 IP 和端口是 120.119.114.118:80(T Server),只要不是这个 IP 发来的消息都会被丢弃,是不会被传到 Client 2 中的。
所以内网穿透实现 P2P 连接,主要有以下几个步骤:
- 蓝色箭头:Client 1 和 Client 2 通过各自的 NAT 服务器与 T Server 建立并保持连接状态。
- 绿色剪头: Client 1 通过 T Server 可知道有 Client 2 的存在,它可以直接给它发消息,但是就像上面说的情况一样,肯定是发不通的,会在 NAT 2 的时候被拦截,只能告知 T Server 让 Client 2 来探测自己。
- 3. 橙色箭头:Client 2 收到 T Server 探测 Client 1 的请求,这时候就会发送探测包到 NAT 1 当中,毫无疑问这些消息肯定也会被 NAT 1 丢弃,但是这时 NAT 2 当中已经建立起了 Session 1,并将 NAT 1 的信息记录在自己的 Session 1 中了,就坐等 Client 1 通过 NAT 1 来访问自己即可。
- 灰色箭头:Client 2 发送完探测包后,就告知 T Server 已经探测完成,并且让 T Server 告知 Client 1 来访问自己(Client 2)。
- 黑色剪头:T Server 告知 Client 1 ,Client 2 已经给你(Client 1)发送过探测包并且建立起 Seesion 1,你(Client 1)已经可以访问 Client 2 了。
- 橘黄色剪头:Client 1 收到 T Server 消息后,便通过 NAT 1 向 Client 2 发送消息了,此时 NAT 1 也建立起相应的 Session 2 ,并记录着 NAT 2 的信息。从这时开始,双方所在的 NAT 服务器因为都创建了相应的 Session 信息,所以 Client 1 和 Clieent 2 的 P2P 连接也就大功告成了,已经可以不经过服务器,相互之间传递消息了。