长连接和端连接和心跳包机制

TCP连接简介
当网络通信时采用TCP协议时,在真正的读写操作之前,server与client之间必须建立一个连接,
当读写操作完成后,双方不再需要这个连接时它们可以释放这个连接,
连接的建立是需要三次握手的,而释放则需要4次握手,
所以说每个连接的建立都是需要资源消耗和时间消耗的
​经典的三次握手示意图:​


经典的四次握手关闭图:



一、长连接与短连接
长连接: 指在一个TCP连接上可以连续发送多个数据包,
        在TCP连接保持期间,如果没有数据包发送,需要双方发检测包以维持此连接;
        一般需要自己做在线维持。 
短连接: 指通信双方有数据交互时,就建立一个TCP连接,数据发送完成后,则断开此TCP连接;
        一般银行都使用短连接。 
        它的优点是:管理起来比较简单,存在的连接都是有用的连接,不需要额外的控制手段 

比如http的,只是连接、请求、关闭,过程时间较短,服务器若是一段时间内没有收到请求即可关闭连接。 
其实长连接是相对于通常的短连接而说的,也就是长时间保持客户端与服务端的连接状态。

长连接与短连接的操作过程 
通常的短连接操作步骤是: 
  连接→数据传输→关闭连接;

而长连接通常就是: 
  连接→数据传输→保持连接(心跳)→数据传输→保持连接(心跳)→……→关闭连接; 

这就要求长连接在没有数据通信时,定时发送数据包(心跳),以维持连接状态,
短连接在没有数据传输时直接关闭就行了

什么时候用长连接,短连接?
长连接多用于操作频繁,点对点的通讯,而且连接数不能太多情况。
每个TCP连接都需要三步握手,这需要时间,如果每个操作都是先连接,再操作的话那么处理速度会降低很多,
所以每个操作完后都不断开,下次次处理时直接发送数据包就OK了,不用建立TCP连接。

例如:数据库的连接用长连接, 
如果用短连接频繁的通信会造成socket错误,而且频繁的socket 创建也是对资源的浪费。

二、发送接收方式
1、异步 
报文发送和接收是分开的,相互独立的,互不影响。这种方式又分两种情况: 
(1)异步双工:接收和发送在同一个程序中,由两个不同的子进程分别负责发送和接收 
(2)异步单工:接收和发送是用两个不同的程序来完成。 

2、同步 
报文发送和接收是同步进行,既报文发送后等待接收返回报文。 
同步方式一般需要考虑超时问题,即报文发出去后不能无限等待,需要设定超时时间,
超过该时间发送方不再等待读返回报文,直接通知超时返回。
 
在长连接中一般是没有条件能够判断读写什么时候结束,所以必须要加长度报文头。
读函数先是读取报文头的长度,再根据这个长度去读相应长度的报文。

三. 单工、半双工和全双工
根据通信双方的分工和信号传输方向可将通信分为三种方式:
单工、
半双工、
全双工。

在计算机网络中主要采用双工方式,其中:
局域网采用半双工方式,
城域网和广域网采用全双年方式。   

1. 单工(Simplex)方式:
通信双方设备中发送器与接收器分工明确,只能在由发送器向接收器的单一固定方向上传送数据。
采用单工通信的典型发送设备如早期计算机的读卡器,典型的接收设备如打印机。   

2. 半双工(Half Duplex)方式:
通信双方设备既是发送器,也是接收器,两台设备可以相互传送数据,但某一时刻则只能向一个方向传送数据。
例如,步话机是半双工设备,因为在一个时刻只能有一方说话。   

3. 全双工(Full Duplex)方式:
通信双方设备既是发送器,也是接收器,两台设备可以同时在两个方向上传送数据。
例如,电话是全双工设备,因为双方可同时说话。

而像WEB网站的http服务一般都用短链接,因为长连接对于服务端来说会耗费一定的资源,
而像WEB网站这么频繁的成千上万甚至上亿客户端的连接用短连接会更省一些资源,
如果用长连接,而且同时有成千上万的用户,如果每个用户都占用一个连接的话,那可想而知吧。
所以并发量大,但每个用户无需频繁操作情况下需用短连好。

总之,长连接和短连接的选择要视情况而定。

心跳包机制

   跳包之所以叫心跳包是因为:它像心跳一样每隔固定时间发一次,以此来告诉服务器,这个客户端还活着。事实上这是为了保持长连接,至于这个包的内容,是没有什么特别规定的,不过一般都是很小的包,或者只包含包头的一个空包。
    在TCP的机制里面,本身是存在有心跳包的机制的,也就是TCP的选项:SO_KEEPALIVE。系统默认是设置的2小时的心跳频率。但是它检查不到机器断电、网线拔出、防火墙这些断线。而且逻辑层处理断线可能也不是那么好处理。一般,如果只是用于保活还是可以的。
    心跳包一般来说都是在逻辑层发送空的echo包来实现的。下一个定时器,在一定时间间隔下发送一个空包给客户端,然后客户端反馈一个同样的空包回来,服务器如果在一定时间内收不到客户端发送过来的反馈包,那就只有认定说掉线了。
    其实,要判定掉线,只需要send或者recv一下,如果结果为零,则为掉线。但是,在长连接下,有可能很长一段时间都没有数据往来。理论上说,这个连接是一直保持连接的,但是实际情况中,如果中间节点出现什么故障是难以知道的。更要命的是,有的节点(防火墙)会自动把一定时间之内没有数据交互的连接给断掉。在这个时候,就需要我们的心跳包了,用于维持长连接,保活。
    在获知了断线之后,服务器逻辑可能需要做一些事情,比如断线后的数据清理呀,重新连接呀……当然,这个自然是要由逻辑层根据需求去做了。
    总的来说,心跳包主要也就是用于长连接的保活和断线处理。一般的应用下,判定时间在30-40秒比较不错。如果实在要求高,那就在6-9秒。

心跳检测步骤
1 客户端每隔一个时间间隔发生一个探测包给服务器
2 客户端发包时启动一个超时定时器
3 服务器端接收到检测包,应该回应一个包
4 如果客户机收到服务器的应答包,则说明服务器正常,删除超时定时器
5 如果客户端的超时定时器超时,依然没有收到应答包,则说明服务器挂了

TCP keep-alive的三个参数

用man命令,可以查看linux的tcp的参数:

man 7 tcp

其中keep-alive相关的参数有三个:

       tcp_keepalive_intvl (integer; default: 75; since Linux 2.4)
              The number of seconds between TCP keep-alive probes.

       tcp_keepalive_probes (integer; default: 9; since Linux 2.2)
              The  maximum  number  of  TCP  keep-alive  probes  to send before giving up and killing the connection if no
              response is obtained from the other end.

       tcp_keepalive_time (integer; default: 7200; since Linux 2.2)
              The number of seconds a connection needs to be idle before TCP begins sending out keep-alive probes.   Keep-
              alives  are  sent only when the SO_KEEPALIVE socket option is enabled.  The default value is 7200 seconds (2
              hours).  An idle connection is terminated after approximately an additional 11 minutes (9 probes an interval
              of 75 seconds apart) when keep-alive is enabled.

这些的默认配置值在/proc/sys/net/ipv4 目录下可以找到。

可以直接用cat来查看文件的内容,就可以知道配置的值了。
也可以通过sysctl命令来查看和修改:

# 查询
cat /proc/sys/net/ipv4/tcp_keepalive_time
sysctl net.ipv4.tcp_keepalive_time
#修改
sysctl net.ipv4.tcp_keepalive_time=3600

上面三个是系统级的配置,在编程时有三个参数对应,可以覆盖掉系统的配置:

TCP_KEEPCNT 覆盖  tcp_keepalive_probes,默认9(次)
TCP_KEEPIDLE 覆盖 tcp_keepalive_time,默认7200(秒)
TCP_KEEPINTVL 覆盖 tcp_keepalive_intvl,默认75(秒)
 ```

## tcp keep-alive的本质
###TCP keep-alive probe
上面了解了tcp keep-alive的一些参数,下面来探究下其本质。

在远程机器192.168.66.123上,用nc启动一个TCP服务器:
```bash
nc -l 9999

在本地机器上,用python创建一个socket去连接,并且用wireshark抓包分析

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
s.setsockopt(socket.SOL_TCP, socket.TCP_KEEPIDLE, 20)
s.setsockopt(socket.SOL_TCP, socket.TCP_KEEPINTVL, 1)

s.connect(('192.168.66.123', 9999))

上面的程序,设置了TCP_KEEPIDLE为20,TCP_KEEPINTVL为1,系统默认的tcp_keepalive_probes是9。

当网络正常,不做干扰时,wireshark抓包的数据是这样的(注意看第二列Time):

tcp-keepalive-good.png

可以看到,当3次握手完成之后,每隔20秒之后66.120发送了一个TCP Keep-Alive的数据包,然后66.123回应了一个TCP Keep-Alive ACK包。这个就是TCP keep-alive的实现原理了。

当发送了第一个TCP Keep-Alive包之后,拨掉192.168.66.123的网线,然后数据包是这样子的:
tcp-keepalive-bad.png

可以看到,当远程服务器192.168.66.123网络失去连接之后,本地机器(192.168.66.120)每隔一秒重发了9次tcp keep-alive probe,最终认为这个TCP连接已经失效,发了一个RST包给192.168.66.123。

在本地机器上,用python创建一个socket去连接,并且用wireshark抓包分析
```python
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
s.setsockopt(socket.SOL_TCP, socket.TCP_KEEPIDLE, 20)
s.setsockopt(socket.SOL_TCP, socket.TCP_KEEPINTVL, 1)

s.connect(('192.168.66.123', 9999))

上面的程序,设置了TCP_KEEPIDLE为20,TCP_KEEPINTVL为1,系统默认的tcp_keepalive_probes是9。

当网络正常,不做干扰时,wireshark抓包的数据是这样的(注意看第二列Time):

tcp-keepalive-good.png

可以看到,当3次握手完成之后,每隔20秒之后66.120发送了一个TCP Keep-Alive的数据包,然后66.123回应了一个TCP Keep-Alive ACK包。这个就是TCP keep-alive的实现原理了。

当发送了第一个TCP Keep-Alive包之后,拨掉192.168.66.123的网线,然后数据包是这样子的:
tcp-keepalive-bad.png

可以看到,当远程服务器192.168.66.123网络失去连接之后,本地机器(192.168.66.120)每隔一秒重发了9次tcp keep-alive probe,最终认为这个TCP连接已经失效,发了一个RST包给192.168.66.123。

为什么应用层需要heart beat/心跳包?

默认的tcp keep-alive超时时间太长

默认是7200秒,也就是2个小时。

socks proxy会让tcp keep-alive失效

socks协议只管转发TCP层具体的数据包,而不会转发TCP协议内的实现细节的包(也做不到),参考socks_proxy

所以,一个应用如果使用了socks代理,那么tcp keep-alive机制就失效了,所以应用要自己有心跳包。

socks proxy只是一个例子,真实的网络很复杂,可能会有各种原因让tcp keep-alive失效。

移动网络需要信令保活

前两年,微信信令事件很火,搜索下“微信 信令”或者“移动网络 信令”可以查到很多相关文章。

这里附上一个链接:微信的大规模使用真的会过多占用信令,影响通讯稳定吗?

总结

  • TCP keep-alive是通过在空闲时发送TCP Keep-Alive数据包,然后对方回应TCP Keep-Alive ACK来实现的。

  • 为什么需要heart beat/心跳包?因为tcp keep-alive不能满足人们的实时性的要求,就是这么简单。

猜你喜欢

转载自my.oschina.net/qchats/blog/1632357
今日推荐