一.TCP特性概览
1.面向连接
TCP是基于连接进行数据交互,通信双方在进行数据交互之前需要建立连接,该连接也只能用在双方之间进行交互。这点不像UDP中的组播和广播,可以在同一组中多个主机交互数据。这也是导致TCP协议的复杂性的因素之一,建立连接涉及到三次握手和四次挥手的过程。
2.可靠性保证
- 分段机制。TCP将需要传输的数据分成合适的报文段,然后逐个传输
- 定时器的超时和重传机制。TCP对每个发送的报文段都设置一个定时器等待目标端返回确认收到的响应,如果未收到,则超时重传
- 确认报文机制。接受方收到报文后会返回确认报文告诉发送方收到报文
- 校验和机制。TCP保持首部和数据的校验和,接收方将校验段的校验和,如果错误将丢弃不确认等待发送方超时重传
- 有序机制。TCP将对报文段进行排序,保证报文段有序性
- 去重机制。TCP将对报文段进行去重
- 流控机制。由于接收方和发送的TCP缓冲区容量不一致和处理速度,需要对发送方的流量进行控制,防止网络阻塞
3.字节流
TCP双方进行数据交互时,数据形式为8bit组成的字节流,TCP也不在这些字节流中做任何特殊标识。
这种字节流的特点表现如下:
- TCP不关注数据的具体内容是二进制还是ASCII字符
- 发送方发送的字节流和接收方接受的字节流整体表现一致,但是发送的次数和每次的大小与接受方接收的次数和大小并没有关系。
二.TCP报文格式
TCP报文段分为两部分:
- TCP首部。TCP首部是描述TCP报文段的信息,一般为20个字节
- 数据部分。数据部分包含应用层需要传输的数据
TCP首部中包含以下信息:
- 16位源端口号和16位目的端口号。网络中进程通信需要IP + PORT来确定唯一进程。因为IP地址是有IP协议涉及,所以在TCP传输中只需要确定接收方和目标方的端口号便可以建立连接,双方的进程才能正常通信
- 32位序号和32位确认序号。用于标识发送的字节流和期望接收的字节流
- 4位首部长度。表示TCP首部长度,首部中有可选收据长度
- 6位标志位。用于标识不同的连接状态
- 16位窗口大小。用于流量控制,接收方期望接受的数据大小
- 16位校验和。TCP首部和TCP数据的校验码,由发送方计算生成,接收方用于校验
- 16位紧急指针。用于发送紧急数据
三.建立和关闭TCP连接
这里使用telent wwww.baidu.com 80和tcpdump -S -t host www.baidu.com演示建立和关闭TCP连接。并通过TCP建立连接和终止连接的时序图以及报文分析。
其中建立TCP连接需要三次握手,关闭TCP连接需要四次挥手。
1.三次握手
这里将通信双方分别称为发送端和接收端。三次握手是应用在发送端和接收端在数据交互前建立TCP连接的过程。这个过程需要三步骤才能完成:
- 发送端为即将建立的连接生成一个SEQ作为TCP报文段中的序号,并将SYN位置1,发送SYN包至接收方,然后进入SYN_SEND状态
- 接收端在收到SYN包后,进入SYN_RCVD状态,也生成一个SEQ作为返回给接收端报文的序号,并将接收到的SEQ+1作为返回报文的确认序号,发送SYN包至接收端,且SYN和ACK位置1。该包有两个作用,作为SYN包进行建立连接,同时作为ACK包回复发送端
- 接收端收到SYN包后,进入ESTABLISHED状态,表示在发送端连接已经建立。然后将接收到的SYN包的SEQ+1,将结果作为发送给接收端的ACK包的确认序号,且ACK位置1
因为建立连接的过程需要三次交互通信,所以将其称为三次握手
从tcpdump抓取的报文段可以看出,三次握手的过程:
IP 10.1.133.253.58062 > 183.232.231.174.http: Flags [S], seq 1209507409, win 65535, options [mss 1460,nop,wscale 5,nop,nop,TS val 1469077440 ecr 0,sackOK,eol], length 0
IP 183.232.231.174.http > 10.1.133.253.58062: Flags [S.], seq 2198201899, ack 1209507410, win 8192, options [mss 1452,nop,wscale 5,nop,nop,nop,nop,nop,nop,nop,nop,nop,nop,nop,nop,sackOK,eol], length 0
IP 10.1.133.253.58062 > 183.232.231.174.http: Flags [.], ack 2198201900, win 8192, length 0
报文格式为:源IP.端口 > 目标IP.端口 状态位标志 序号 确认序号 窗口大小 可选项。
Note:为什么需要三次握手?
发送端和接收端各发送一次,需要告诉对方需要建立连接同时包含窗口大小等信息,这是必不可少的二次。由于接收端无法确定发送端是否接受到自己的请求,所以需要发送端发一次回复进行确认。但是接收端为什么不需要回复发送端的ACK,因为这是一个循环依赖的问题,没有尽头。所以至少需要三次握手才能基本建立连接。
2.四次挥手
在数据交互完成后,需要关闭连接释放系统资源,如内存,文件句柄等。因为TCP连接时全双工模式,需要关闭双向的数据数据发送。为了保证释放资源的可靠性,需要四次握手。四次握手的过程中,发送端和接受端都要关闭各自连接释放资源,从而避免半关闭状态。四次握手过程:
- 接收端主动发起关闭,向发送端发送FIN包,进入FIN_WAIT状态,表示接收端方向上将不会再有数据发出,但是同样还可以收数据
- 为了确保发送端收到了FIN包,发送端需要返回ACK包,确认序号为FIN包的SEQ+1,同时进入CLOSE_WAIT。因为TCP连接时全双工模式,所以发送端这里不一定需要关闭连接,它仍然可以发送数据
- 待发送端将不再有数据需要发送时,将发送FIN包至接收端,进入LAST_ACK状态
- 为了确保接收端收到FIN包,需要回复ACK包,同时进入TIME_WAIT状态。
因为关闭连接的过程有双方之间的四次交互过程,所以将其称为四次挥手
IP 183.232.231.174.http > 10.1.133.253.58062: Flags [F.], seq 2198201900, ack 1209507427, win 776, length 0
IP 10.1.133.253.58062 > 183.232.231.174.http: Flags [.], ack 2198201901, win 8192, length 0
IP 10.1.133.253.58062 > 183.232.231.174.http: Flags [F.], seq 1209507427, ack 2198201901, win 8192, length 0
IP 183.232.231.174.http > 10.1.133.253.58062: Flags [.], ack 1209507428, win 776, length 0
3.2MSL的TIME_WAIT
TIME_WAIT是主动发起关闭TCP连接在四次挥手过程中出现的连接状态。当主动发起关闭连接的一方在接收到被动关闭方请求的FIN包后,将响应ACK包后,将进入该状态。该状态的持续时间为2MSL时间,处于该状态的TCP连接将等待关闭。
MSL(Maximum Segment Lifetime)是报文段在网络中存活的最长时间,超过这个时间的报文将会被丢弃。
即主动关闭TCP连接一方在发送ACK包时,必须等待TIME_WAIT状态2倍MSL时间。TCP协议这样处理主要有以下两方面原因:
-
确保被动关闭一方未能接收到主动关闭连接一方发的ACK包时能够重发FIN包。因为因为网络问题,被动关闭一方可能无法接收到最后的ACK包,而不能正常关闭连接。这时被动关闭方可以重新发一个FIN包给主动关闭方,由于主动关闭方处于TIME_WAIT状态,仍然可以恢复ACK包。由于ACK包和FIN包都在网络中停留最长时间为MSL,所以TIME_WAIT为2MSL,保证尽可能最长的时间接受被动关闭方的FIN包。如果主动关闭方在恢复最后的ACK包后,不经过TIME_WAIT状态而直接CLOSED,那么可能存在被动关闭方未能接收到ACK包,从而超时重传FIN包,这时主动关闭方的TCP连接已经关闭,在接收到这个FIN包后寻找不到对应的连接,从而回复RST复位包,被动关闭方接收到RST包后会处理错误从而导致无法正常关闭连接释放资源。
-
避免前一个连接产生的报文由于在网络中停留从而与新连接的报文耦合在一起产生数据错误。都知道代表TCP连接的标识是(源IP,源端口号,目的IP,目的端口号)。因为TCP连接时全双工的双向通信,关闭时需要双端都关闭保证双向都没有数据流通。当主动方发起关闭,只能保证主动方将没有任何数据向外发送。此时被动仍然可能会发送报文,加入这些报文在网络节点中停留,然后主动方又立即与被动方建立新的TCP连接且新的连接和上次TCP连接的标识是一样的,此时在网络中停留的报文也刚好抵达主动方,主动方处理该报文必然会发生错误。主动关闭方在停留2MSL的TIME_WAIT状态,保证TCP连接中网络报文尽可能的过期。
总结来说,主动发起关闭TCP连接的一方停留在TIME_WAIT状态2倍的MSL时间主要为了:
- 为了被动关闭一方能够重传FIN包,保证正常的关闭TCP连接释放资源
- 避免上一次TCP连接的网络中残留报文耦合干扰新的TCP连接
当2MSL过后,TCP连接将正式被关闭CLOSED释放资源。但是停留在TIME_WAIT状态的TCP连接有以下的特征:
-
处于这种状态的TCP连接将会仍然占用该端口号。比如建立TCP连接的源端口好为52222,当发起方主动关闭TCP连接,当处于TIME_WAIT状态式,该端口将被占用
-
TCP连接没有被关闭,所以资源仍然没有被释放,比如:内存、CPU、IO等
以下是笔者获取本机的网络连接,通过netstat -an发现:
其中端口为61321的TCP连接处于TIME_WAIT状态,然后通过Java的ServerSocket监听61321端口:
ServerSocket serverSocket = new ServerSocket();
SocketAddress address = new InetSocketAddress("10.1.133.253", 61321);
serverSocket.setReuseAddress(false);
serverSocket.bind(address);
在运行以上代码时抛出以下异常:
对于以上问题,可以通过设置Socket参数SO_REUSEADDR来改变以上的行为,如果该参数为TRUE,则可以重复使用绑定端口,从而避免TIME_WAIT导致端口被占用的问题。
对于客户端这两点一般影响不是很大,但是对于服务端而言,如果出现大量的TIME_WAIT状态的连接,可能会造成服务端的性能大幅度下降,处理能力减弱,甚至严重服务器崩溃瘫痪。因为大量的网络连接没有及时释放,占用大量的系统资源,导致无法处理新的连接和请求。
对于这种问题一般的解决方式为两种方式:
-
从操作系统层进行优化。通过配置系统参数,从而解决该问题
-
客户端发起连接关闭,避免服务端主动关闭连接。比如:客户端使用完Socket连接后,主动进行关闭释放资源,这样服务端将不会存在TIME_WAIT
修改/etc/sysctl.conf配置,加入:
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_tw_reuse = 1表示开启重用,可以将TIME_WAIT状态的Socket重新用于新的TCP连接,默认为0表示关闭;
net.ipv4.tcp_tw_recycle = 1表示开启接快速回收TIME_WAIT状态的TCP连,默认为0表示关闭;
net.ipv4.tcp_fin_timeout = 30修改默认的TIME_WAIT的时间,改为30s;
4.RST报文的作用
在前文中介绍了TCP报文格式时,提到几种状态标志位,其中有RST标志位。当该标志位为1时表示报文段是RST报文。在TCP的设计中RST报文有以下几种作用:
- 不存在的端口的连接请求
当想没有使用的端口上发送报文时,目标端将会恢复RST报文。这个可以用检测目标端口是否被监听。
- 异常终止一个连接
当应用发生异常时,终止连接释放资源,此时将发生RST报文到对端。而非通过FIN这种正常关闭连接,可以达到资源快速释放。比如对端可能会出现:
read error: Connection reset by peer
这种情况表明,对端发生异常,及时关闭TCP连接回复了RST报文。
- 检测半打开的连接
TCP是全双工的双向通信模式,当关闭释放资源时,需要双端都关闭连接进行资源释放。这样的情况就存在一端关闭了TCP连接,而另一端没有关闭连接。这种状态被称为半关闭状态。
对于高访问量的服务型应用而言,经常都会使用连接池这种长连接方式,这种情况经常会出现半关闭状态。应为通信双端无法感知彼此连接状态的变化,大多数应用都通过心跳检测,断连重连解决。
心跳检测的原理即通过持续隔断的发送心跳数据至对端,对端回复相应的数据来检测连接的正常有效性。当出现回复超时,或者连接被对端断开的情形式可以认为连接失效,这时可以关闭该端连接,并进行重连。
其中连接被对端断开的情形,就和第一种情况类似,对端已经不存在该连接的信息,从而回复RST报文,表示该连接已经被对端关闭。
该种情况与第一种的区别在于,第一种情况是尝试与未监听的端口上建立连接过程被回复RST报文。而该中情况是在已建立的连接上发送报文,由于对端关闭,从而回复RST报文。
总结
本篇文章介绍了TCP的基础特性,连接建立和终止的过程以及相关状态的含义,其中针对2MSL状态和RST报文段的作用做了说明。这些内容主要都介绍TCP的面向连接的特性,对于TCP另一个非常重要的特征-可靠性后续文章介绍。