浅析IM即时通讯开发中TCP协议层KeepAlive保活机制

对于IM这种应用而言,应用层的网络保活的最直接办法就是心跳机制,比如主流的IM里有微信、QQ、钉钉、易信等等,可能代码实现细节有所差异,但理论上无一例外都是这样实现。(PS:没错,当初微信跟运营商间的“信令危机”就是跟这个有关)

所谓的网络心跳,通常是客户端每隔一小段时间向服务器发送一个数据包(即心跳包),通知服务器自己仍然在线(心跳包中同时可能传输一些必要的数据)。发送心跳包,从通信层面来说就是为了保持长连接,至于这个包的内容,是没有什么特别规定的,但在移动端IM中为了省流量,一般都是很小的包(比如某些第3方的IM云为了说明心跳不费流量,号称1字节的心跳包)。

但经常有人会问到,既然TCP协议本身有KeepAlive保活这个东西(见:《TCP/IP详解 卷1 - 第23章·TCP的保活定时器》),为什么还要自已在应用层去实现网络保活/心跳机制呢?

没错,通常面视即时通讯/IM方面的程序员时,这几乎是必提问题!

TCP KeepAlive的初衷

采用TCP连接的C/S模式应用中,当连接的双方在连接空闲状态时,如果任意一方意外崩溃、当机、网线断开或路由器故障,另一方无法得知TCP连接已经失效。

那么,连接的另一方并不知道对端的情况,它会一直维护这个连接。而作为“服务端”来说,长时间的积累会导致非常多的半打开连接,造成端系统资源的消耗和浪费,且有可能导致在一个无效的数据链路层面发送业务数据,结果就是发送失败。

所以各端要做到快速感知失败,减少无效链接操作,这就有了TCP的KeepAlive保活探测机制。

PS:这样宽泛的说TCP的KeepAlive机制的必要性,貌似还不是很有说服力,下节将带着具体的例子深入分析。

从NAT角度更具体地理解TCP KeepAlive的必要性

讲到TCP的KeepAlive的必要性,多数文章都是像上节这样比较笼统的进行说明,但对于爱刨根问底的开发者来说,这还远远不够。

本节将以路由器的NAT机制这个角度来具体分析TCP协议的造物主们设计KeepAlive机制的必要性。

从NAT原理讲起

狭义上,NAT分为SNAT(原地址转换)和DNAT(目标地址转换),关于DNAT,有兴趣的同学可以自行查阅,这里只讨论SNAT。

我们都知道,路由器的最基本功能是对第三层(网络层)上的IP报文进行转发。实际上,路由器还有很关键的一个功能,这便是NAT。特别是对于ISP对普通用户链路上的路由器,NAT功能尤为重要。

为什么要使用NAT?

原因很简单:IPv4地址非常稀缺。上网需求庞大,这使得ISP不可能为每一个入网用户都提供一个独立的公网IP,因此通常情况下,ISP会把用户接入局域网,使得多个用户共享同一个公网IP,而每一个用户各分得一个局域网内网IP。而连接公网和局域网的这台路由器,称之为网关(gateway),NAT的过程就发生在这台网关路由器上。即时通讯聊天软件app开发可以加蔚可云的v:weikeyun24咨询

三层地址转换

局域网内的主机向公网发出的网络层IP报文,将经由网关被转发至公网,而在该转发过程中发生了地址转换。网关将该IP报文中的 源IP地址 从”该主机的内网IP”修改为”网关的公网IP”。

比如:局域网主机获得的内网IP为192.168.1.100,网关的公网IP为210.177.63.2,局域网主机向公网目标主机发出的IP报文中,源IP字段数据为192.168.1.100,在经过网关时,该字段数据将被修改为210.177.63.2。

为什么要这么做,相信大家已经猜到了:公网上的目标主机在收到这个IP报文后,需要知道这个IP报文的来源地址,并向该来源地址发送响应报文,但如果不经过NAT,目标主机拿到的来源地址是192.168.1.100,这显然是一个公网上不可访问到的私有地址,目标主机无法将响应报文发送到正确的来源主机上。开启了NAT之后,IP报文的来源地址被网关修改为210.177.63.2,这是一个公网地址,目标主机将向这个地址(即网关路由器的公网地址)发送响应报文。

但是请注意:如果这个IP报文的数据段不含传输层协议报文,而是一个pure的网络层packet,来自目标主机的响应报文是不能被网关准确转发到多台局域网主机中的其中一台的。

PS:ICMP报文除外,其报头中有Identifier字段用于标识不同的主机或进程,网关在处理Identifier时类似于下面提到的运输层端口。

传输层端口转换表

在三层地址转换中,我们可以保证局域网内主机向公网发出的IP报文能顺利到达目的主机,但是从目的主机返回的IP报文却不能准确送至指定局域网主机(我们不能让网关把IP报文广播至全部局域网主机,因为这样必然会带来安全和性能问题)。

为了解决这个问题,网关路由器需要借助传输层端口,通常情况下是TCP或UDP端口,由此来生成一张端口转换表。

让我们通过一个实例来说明端口转换表如何运作:

假设局域网主机A192.168.1.100需要与公网上的目标主机B210.199.38.2:80进行一次TCP通信。其中A所在局域网的网关C的公网IP地址为210.177.63.2。

步骤如下:

1)局域网主机A192.168.1.100发出TCP连接请求,A上的TCP端口为系统分配的53600。该TCP握手包中,包含源地址和端口192.168.1.100:53600,目的地址和端口210.199.38.2:80。

2)网关C将该包的原地址和端口修改为210.177.63.2:63000,其中63000是网关分配的临时端口。

3)网关C在端口转换表中增加一条记录:

4)网关C将修改后的TCP包发送至目的主机B。

5)目的主机B收到后,发送响应TCP包。该响应TCP包含有以下信息:源地址和端口210.199.38.2:80,目的地址和端口210.177.63.2:63000。

6)网关C收到这个来自B的响应包后,随即在端口转换表中查找记录。该记录须符合以下条件:目的主机IP==210.199.38.2,目的主机端口==80,网关端口==63000。

7)网关C搜索到这条记录,记录显示内网主机IP为192.168.1.100,内网主机端口为53600。

8)网关C将该包的目的地址和端口修改为192.168.1.100:53600。

9)网关C随即将该修改后的TCP包转发至192.168.1.100:53600,即局域网主机A。此时运输层数据的一次交换已完成。

在网关C上,由于端口数量有限(0~65535),端口转换表的维护占用系统资源,因此不能无休止地向端口转换表中增加记录。对于过期的记录,网关需要将其删除。

如何判断哪些是过期记录?

网关认为:一段时间内无活动的连接是过期的,应定时检测转换表中的非活动连接,并将之丢弃。而这个丢弃的过程,网关不会以任何的方式通告该连接的任何一端。

如果一个客户端应用程序由于业务需要,需要与服务端维持长连接(例如基于TCP的IM聊天应用),而如果在特别长的时间内这个连接没有任何的数据交换,网关会认为这个连接过期并将这个连接从端口转换表中丢弃。该连接被丢弃时,客户端和服务端对此是完全无感知的。在连接被丢弃后,客户端将收不到服务端的数据推送,客户端发送的数据包也不能到达服务端。

一个具体的例子来感受一下这个问题的严重性:

某财务应用,在客户端需要填写大量的表单数据,在客户端与服务器端建立TCP连接后,客户端终端使用者将花费几分钟甚至几十分钟填写表单相关信息,终端使用者终于填好表单所需信息后,点击“提交”按钮。

结果,这个时候由于中间设备早已经将这个TCP连接从连接表中删除了,其将直接丢弃这个报文或者给客户端发送RST报文,应用故障产生,这将导致客户端终端使用者所有的工作将需要重新来过,给使用者带来极大的不便和损失。

猜你喜欢

转载自blog.csdn.net/wecloud1314/article/details/125804628