https://blog.csdn.net/a987073381/article/details/52206215
TCP的传输连接分为3个阶段:连接建立(三次握手)、数据传送和连接释放(四次挥手)。TCP传输连接的管理就是使传输连接的建立和释放都能正常地进行。
一、TCP的三次握手
准备工作:服务器必须准备好接受外来的连接。这通常通过调用socket,bind和listen这三个函数来完成。我们称之为被动打开。
第一次握手:客户端通过调用connect发起主动打开。客户端向服务器发出连接请求的TCP报文段,其TCP首部中的同步比特SYN置为1,并TCP首部中序号seq设置为x(TCP规定SYN报文段不能携带数据,但是要消耗一个序号),表明要转送数据时初始序列号是x。通常SYN分节不携带数据,其所在IP数据报只含有一个IP首部,一个TCP首部。
第二次握手:服务器收到数据报后,从TCP数据报首部的同步比特SYN位为1就知道这是一个建立连接的请求。服务器如果同意,会发回确认。在确认报文段中把同步比特位SYN设置为1,确认比特位ACK设置为1,由于TCP请求报文段中的序号是x,所以服务器在发送确认报文段中的确认号ack是x+1,同时把确认报文段中的序号seq设置为y,表明服务器发送数据的初始序列号为y。该报文段也不能携带数据(因为SYN=1,所以不携带任何数据)。
第三次握手:客户端收到服务器端的报文段后,要对服务器端中的SYN进行确认。在确认报文段中把确认比特位ACK设置为1,然后把确认号ack设置为y+1,自身的序号seq设置x+1。
注:客户的初始序列号为x,服务器的初始序列号为y,那么确认报文段中的确认号ack就是所期待的对方要发送的下一个序列号。
客户端调用connect将激发TCP的三次握手,仅在连接建立成功或者出错时才返回,上面介绍了建立成功的情况,下面列出了出错返回可能有的几种情况。
(1)客户端发送的建立连接的SYN报文丢失或者服务器回复的ACK报文丢失。客户端因为没有收到服务器的回复,会等待6s再发一次,若无响应则等待24s再发一次,总共等75s还未收到响应就返回本错误。
(2)服务器回复的TCP报文中复位标志RST置位1。表示服务器主机没有在指定的端口上监听或接受该连接或服务器程序根本没有运行。
(3)客户端发送的SYN报文不可达。还记IP数据报中有一个生存时间(TTL)的标志吗?它每进过一个路由器都会减1,当它减到0时还没到达目的地,会发送一个ICMP错误。然后客户端会和第一种情况一样每隔一段时间重发一次。
可以把TCP连接时的三次握手换成两次握手吗?(假设客户端主动,服务器端被动)
假如客户端发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达服务器。也就是说这是一个早已失效的报文段。但服务器端收到此失效的连接请求报文段后,就误认为是客户端再次发出的一个新的连接请求。于是就向客户端发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要服务器端发出确认,新的连接就建立了。由于现在客户端并没有发出建立连接的请求,因此不会理会服务器端的确认。经过三次握手,客户端和服务器都有应有答可以确保TCP正确连接。
二、TCP的四次挥手
数据传输完毕后,通信双方都可以释放连接。对于首先调用close的一端我们称该端为主动关闭,另外一端执行被动关闭。
第一次挥手:假设客户端执行主动关闭,那么它会向服务器端发出释放连接的报文段,这个TCP报文段中终止比特FIN置为1,序号seq设置为u(假设上一个发的数据序号是u-1)。并停止发送数据。主动关闭TCP连接。等待服务器的确认,这里需要注意,因为TCP是全双工的,所以TCP连接上有两条数据通路,发送FIN的一端就不能发送数据,也就是关闭了其中一条数据通路,对方还是可以继续发送数据。
第二次挥手:服务器端收到客户端的释放连接的报文段后会执行被动关闭,它要对客户端的数据报进行确认,服务器端会发送一个确认的数据报,确认比特ACK设置为1,确认号为u+1,自身的序号seq为v(假设上一个发的数据序号是v-1)。这个时候TCP处于半关闭状态,服务器依然可以向客户端发送数据(数据的序号为v+1 ~ w-1),客户端任要接受。
第三次挥手:服务器端已经没有要发送给客户端的数据,那么服务器端也会调用close关闭套接字,这样服务器端也会发送一个FIN的TCP报文段,序号是w(假设上一个发的数据序号是w-1)。这个时候服务器端不会再向客户端发送数据了。
第四次挥手:客户端接受到这个最终的FIN的释放连接报文段后必须对报文段进行确认。在确认的报文段中,ACK=1,确认序号ack=w+1,自己的序号seq=u+1(他的上一个序号的数据报就是申请释放连接的数据报,序号是seq=u)。
为什么TCP握手是三次,挥手却是四次?(假设客户端主动,服务器端被动)
在TCP三次握手中,服务器端的SYN和ACK是放在一个TCP报文段中向客户端发送的,而在断开连接的过程中,服务器端向客户单端发送的ACK和FIN是是分别在两个不同的TCP报文段中。这是因为在服务器端接收到客户端的FIN后,服务器端可能还有数据要传输,所以先发送ACK,服务器端把数据发完之后就可以发送FIN断开连接了。
三、TCP的11种状态转换
CLOSED:起始点,不在连接状态。可以主动打开连接,或者等待对端的连接。
-->收到“被动打开”报文,进入LISTEN状态。
-->收到“主动打开”报文,进入SYN_SENT状态。
-->收到任何报文段,发送RST报文段。
-->收到其它任何报文段,发出差错报文。
LISTEN:被动打开,TCP正在等待对端的连接请求。
-->收到“发送数据”报文,发送SYN报文段,进入SYN_SENT状态。
-->收到任何SYN报文段,发送SYN+ACK报文段,进入SYN_RECEIVED状态。
-->收到任何其它报文段或者报文,发送差错报文。
SYN_SENT:主动打开,发送完一个连接请求后等待回复。
-->超时,进入CLOSED状态。
-->收到SYN报文段,发送SYN+ACK报文段,进入SYN_RECEIVED状态。
-->收到SYN+ACK报文段,发送ACK报文段,进入ESTABLISHED状态。
-->收到任何其它报文段或者报文,发送差错报文。
SYN_RECEIVED:被动打开,接受连接请求以后进行确认同时也向对端发送连接请求发送,等待对方的回复。
-->超时,发送RST报文段,进入CLOSED状态。
-->收到ACK报文段,进入ESTABLISHED状态。
-->收到"关闭"报文,发送FIN报文段,进入FIN_WAIT_1状态。
-->收到RST报文段,进入LISTEN状态。
-->收到任何其它报文段或者报文,发送差错报文。
ESTABLISHED:三次握手完毕,TCP连接建立完成,可以传输数据。
-->收到FIN报文段,进入CLOSED_WAIT状态。
-->收到“关闭”报文,发送FIN报文段,进入FIN_WAIT_1状态。
-->收到RST或SYN报文段,发出差错报文。
-->收到数据或ACK报文段,调用输入模块。
-->收到“发送”报文,调用输出模块。
FIN_WAIT_1:四次挥手开始,主动关闭,发送断开连接请求,等待对端确认。
-->收到FIN报文段,发送ACK报文段,进入CLOSING状态(同时关闭)。
-->收到FIN+ACK报文段,发送ACK报文段,进入FIN_WAIT状态(?)。
-->收到ACK报文段,进入FIN_WAIT_2状态。
-->收到任何其它报文段或者报文,发送差错报文。
FIN_WAIT_2:接收对方确认,但未接受对方的断开连接请求。
-->收到FIN报文段,发送ACK报文段,进入TIME_WAIT状态。
CLOSING:主动关闭的一方本希望收到对方的ACK却收到了对方的断开连接请求。
-->收到ACK报文段,进入TIME_WAIT状态。
-->收到任何其它报文段或者报文,发送差错报文。
TIME_WAIT:对方确认后发起断开连接请求,需要等待2MSL保证正常关闭。
-->超时,进入CLOSED状态。
-->收到任何其它报文段或者报文,发送差错报文。
CLOSE_WAIT:被动关闭,确认对端的连接终止请求,但是未向对端发送连接终止请求(可能数据没传完)。
-->收到"关闭"报文,发送FIN报文段,进入LAST_ACK状态。
-->收到任何其它报文段或者报文,发送差错报文。
LAST_ACK:数据传完,向对端发起断开连接请求后等待确认。
-->收到ACK报文段,进入CLOSED状态。
-->收到任何其它报文段或者报文,发送差错报文。
CLOSED:终点,不在连接状态。可以主动打开连接,或者等待对端的连接。
(1)TIME_WAIT状态(假设客户端主动,服务器端被动)
从状态图中我们可以发现,执行主动关闭的那端(最终重传ACK的那端)会经历这个状态。这个状态停留是的时间是2MSL(MSL:最长报文段生存时间,1~4分钟)。
TIME_WAIT状态的作用:
1、可靠地实现TCP的连接终止。
在终止TCP连接时有4个报文需要交换,其中最后一个ACK报文是由客户端发往服务器。假设这个ACK报文在网络中被丢弃了,那么服务器端收不到这个确认ACK,服务器端会向客户端再次发送FIN。这就是为什么TIME_WAIT状态持续2倍的最长报文段生存时间:1MSL时间留给最后的ACK确认报文段到达服务器端,1MSL时间留给服务器端再次发送的FIN(这一段摘抄自《unix系统编程手册》P1046,我不明白,超过2MSL连接就超时关闭了,再次发送FIN后,即客户端刚收到,2MSL时间也就到了,连接就关闭了,再次发送FIN的意义何在?,希望有知道的同学告诉我)。
2、确保老的重复的报文段在网络中过期失效,这样建立新的连接时将不再接受它们。
TCP协议采用的是出错重传,也就是会生成重复的报文,并且根据路由器的选择,这些重复的报文可能在连接终止后才到达,如果客户端/服务器端收到这个老的报文会把它误认为一个同一连接的新的报文,然后对这个报文进行处理,这样就会出现错误。从状态转换图我们可以看到从TIME_WAIT到连接终止,中间有2MSL,这个时间足以让老的重复的报文段过期失效。
TIME_WAIT会出现在服务器吗?
会的,TIME_WATI存在首先执行主动关闭的那端,比如爬虫服务器他本身其实就是客户端,完成爬取任务后,执行关闭,那么所有的TCP连接都会处于TIME_WAIT状态。
TIME_WAIT的危害:Linux分配给一个用户的文件句柄是有限的,如果系统中存在大量的TIME_WAIT状态,一旦达到句柄数上限,新的请求就无法被处理了,而且大量TIME_WAIT连接占用资源影响性能。
如何关闭TIME_WAIT状态或者让TIME_WAIT状态过早终止?
对/etc/sysctl.conf文件内容进行修改:
-
#表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭
-
net.ipv4.tcp_tw_reuse = 1
-
#表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭
-
net.ipv4.tcp_tw_recycle = 1
虽然我们可以这么做,但是我们应该避免这样做,因为这样会阻碍TIME_WAIT状态提供的可靠性保证。
(2)CLOSE_WAIT状态(假设客户端主动,服务器端被动)
从上面的状态图可以看出,被动关闭的那端会经历这个状态。如果一直保持在CLOSE_WAIT状态,那么只有一种情况,就是在客户端关闭连接之后服务器程序自己没有进一步发出FIN报文,一般原因都是TCP连接没有调用关闭方法,或者对方连接关闭之后程序里没有检测到,或者程序压根就忘记了这个时候需要关闭连接,于是这个资源就一直被程序占着。这种情况,通过服务器内核参数也没办法解决,服务器对于程序抢占的资源没有主动回收的权利,除非终止程序运行,一定程度上,可以使用TCP的keepalive功能,让操作系统替我们自动清理掉CLOSE_WAIT连接。
-
#表示当keepalive起用的时候,TCP发送keepalive消息的频度。缺省是2小时,改为300秒
-
net.ipv4.tcp_keepalive_time=1200
参考:
http://abuve.blog.51cto.com/2237587/611468
http://www.cnblogs.com/sunxucool/p/3449068.html
http://blog.csdn.net/xifeijian/article/details/44284675
http://www.cnblogs.com/heyonggang/p/3386415.html
《UNIX网络编程》
《UNIX系统编程手册 下》
《TCP/IP协议族》