首先根据简要介绍TCP的各个状态
此图片是《TCP/IP详解卷一:协议》第十八章中的图片
图中一共包括TCP的11个状态:
1、 LISTEN:TCP正等待从对端TCP节点发来的连接请求
2、 SYN_SENT:TCP发送一个SYN报文,代表应用程序执行了一个主动打开的操作,并等待对端回应以此完成连接的建立
3、 SYN_RECV:之前处在LISTEN状态的TCP节点收到了对端发送的SYN报文,并已经通过发送SYN/ACK报文做出了相应(即,这个TCP报文同时设置了SYN和ACK位),正等到对端TCP节点发送一个ACK以此完成连接的建立,进入ESTABLISHED
4、 ESTABLISHED:与对端TCP节点间的连接建立完成。数据报文此时可以再两个TCP节点间双向交换
5、 FIN_WAIT1:应用程序关闭了连接。TCP节点发送一个FIN报文到对端,以此终止本段的连接,并等待对端发来的ACK。这个状态以及接下来的三种状态都与应用程序执行主动关闭有关——也就是说,首先关闭本段连接的应用程序
6、 FIN_WAIT2:之前处在FIN_WAIT1状态的TCP节点现在已经收到了对端TCP节点发来的ACK(半关闭状态)
7、 CLOSING:之前处在FIN_WAIT1状态的TCP节点正在等待对端发送ACK,但却收到了FIN。这表示对端也正在尝试执行一个主动关闭。(换句话说,这两个TCP节点几乎在同一时刻发送了FIN报文。即同时关闭)
8、 TIME_WAIT:完成主动关闭后,TCP节点接收到了FIN报文。这表示对端执行了一个被动关闭。此时这个TCP节点将在TIME_WAIT状态中等待一段固定的时间,这是为了确保TCP连接能够可靠的终止,同时为了确保任何老的重复报文在重新建立同样的连接之前在网络中超时小时。当这个固定的时间段超时后,连接就关闭了,相关的内核资源得到释放。
9、 CLOSE_WAIT:TCP节点从对端收到FIN报文后将处在CLOSE_WAIT状态。该状态以及接下来的一个状态都同应用程序执行的被动关闭有关,也就是第二个执行关闭操作的应用。
10、LAST_ACK:应用程序执行被动关闭,而之前处于CLOSE_WAIT状态的TCO节点发送一个FIN报文给对端,并等待对端的确认。当收到对端发来的确认ACK报文时,连接关闭,相关的内核资源都会得到释放。
11、LCLOSED:关闭状态。
图2-1 正常的TCP连接和断开状态切换过程
如图所示:一个正常的TCP过程,状态变化为:
服务器: CLOSED-- LISTEN--SYN_RCVD-- ESTABLISHED--CLOSE_WAIT-- LAST_ACK--CLOSED
客户: CLOSED-- SYN_SENT-- ESTABLISHED--FIN_WAIT1-- FIN_WAIT2--TIME_WAIT--CLOSED
当然上述过程并不绝对,因为服务器在这是被动方,客户是主动方,而实际情况中,虽然客户多数情况下是主动方,但是由于服务器可能要踢掉某个“僵尸”连接,而成为主动方。发生这样的情况,只需将上述的过程中的服务器与客户对调即可。
a: 第一次握手:建立连接。客户端发送连接请求报文段,将SYN位置为1,Sequence Number为J;然后,客户端进入SYN_SEND状态,等待服务器的确认;
b: 第二次握手:服务器收到SYN报文段。服务器收到客户端的SYN报文段,需要对这个SYN报文段进行确认,设置Acknowledgment Number为J+1(Sequence Number+1);同时,自己自己还要发送SYN请求信息,将SYN位置为1,SequenceNumber为K;服务器端将上述所有信息放到一个报文段(即SYN+ACK报文段)中,一并发送给客户端,此时服务器进入SYN_RECV状态;
c: 第三次握手:客户端收到服务器的SYN+ACK报文段。然后将Acknowledgment Number设置为K+1,向服务器发送ACK报文段,这个报文段发送完毕以后,客户端和服务器端都进入ESTABLISHED状态,完成TCP三次握手。
下图为同时打开过程的状态切换图
a: 第一次挥手:主机1(可以使客户端,也可以是服务器端),设置Sequence Number和Acknowledgment Number,向主机2发送一个FIN报文段;此时,主机1进入FIN_WAIT_1状态;这表示主机1没有数据要发送给主机2了;
b: 第二次挥手:主机2收到了主机1发送的FIN报文段,向主机1回一个ACK报文段,Acknowledgment Number为Sequence Number加1;主机1进入FIN_WAIT_2状态;主机2告诉主机1,我也没有数据要发送了,可以进行关闭连接了;
c: 第三次挥手:主机2向主机1发送FIN报文段,请求关闭连接,同时主机2进入CLOSE_WAIT状态;
d: 第四次挥手:主机1收到主机2发送的FIN报文段,向主机2发送ACK报文段,然后主机1进入TIME_WAIT状态;主机2收到主机1的ACK报文段以后,就关闭连接;此时,主机1等待2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,主机1也可以关闭连接了。
1、 三次握手最主要是防止已过期的连接再次传到被连接的主机。如果采用两次的话,会出现下面这种情况。比如是A机要连到B机,结果发送的连接信息由于某种原因没有到达B机;于是,A机又发了一次,结果这次B收到了,于是就发信息回来,两机就连接。
传完东西后,断开。结果这时候,原先没有到达的连接信息突然又传到了B机,于是B机发信息给A,然后B机就以为和A连上了,这个时候B机就在等待A传东西过去。
2、 四次挥手主要是因为TCP是全双工的传输协议。所以关闭两个通道最少也应该像连接那样,有三个报文。但是为了实现TCP的半关闭功能(即有一条单向的传输关闭,一条单向的传输正常工作)。所以有四个报文,就是四次挥手。
1. PC1的应用程序使用端口7777与PC2的端口8888 执行主动打开
2. PC2的应用程序使用端口8888与PC1的端口7777执行主动打开
3. SYN包同时打开对端,这种情况即为同时打开
TCP中,对于同时打开它仅建立一条连接而不是两条连接,状态变迁图如下:同时发送SYN包,然后收到进行确认直接进入ESTABLISHED状态,可以看到同时打开需要连接建立需要4个报文段,比三次握手多一次!
有同时打开,理所应当的也有同时关闭的场景,TCP协议也允许同时关闭。状态变化可以看到下图了,同时发送FIN包,两端同时执行主动关闭,进入FIN_WAIT_1的状态,从FIN_WAIT_1状态收到FIN包的时候进入CLOSING状态,然后回复ACK,进入TIME_WAIT状态。
上述的这些已经将TCP的基本的连接与断开问题完毕。但是在TCP状态机的图中仍有几条状态转换需要另外说明
a: LISTEN->SYN_SENT:服务器有时候也要打开连接
b: SYN_SENT->SYN收到:服务器和客户端在SYN_SENT状态下如果收到SYN数据报,则都需要发送SYN的ACK数据报并把自己的状态调整到SYN收到状态,准备进入ESTABLISHED。
这个过程就是同时打开连接
c: SYN_SENT->CLOSED:在发送超时的情况下,会返回到CLOSED状态。
d: SYN_收到->LISTEN:如果受到RST包,会返回到LISTEN状态。
e: SYN_收到->FIN_WAIT_1:这个迁移是说,可以不用到ESTABLISHED状态,而可以直接跳转到FIN_WAIT_1状态并等待关闭。
f: FIN_WAIT_1->CLOSING->TIME_WAIT:同时关闭的过程
e: FIN_WAIT_1->TIME_WAIT:主动方收到一个FIN/ACK一起的报文段。
书中给的图里面,有一个TIME_WAIT等待状态,这个状态又叫做2MSL状态,说的是在TIME_WAIT2发送了最后一个ACK数据报以后,要进入TIME_WAIT状态,这个状态是防止最后一次握手的数据报没有传送到对方那里而准备的(注意这不是四次握手,这是第四次握手的保险状态)。这个状态在很大程度上保证了双方都可以正常结束,但是,问题也来了。
由于插口的2MSL状态(插口是IP和端口对的意思,socket),使得应用程序在2MSL时间内是无法再次使用同一个插口的,对于客户程序还好一些,但是对于服务程序,例如httpd,它总是要使用同一个端口来进行服务,而在2MSL时间内,启动httpd就会出现错误(插口被使用)。为了避免这个错误,服务器给出了一个平静时间的概念,这是说在2MSL时间内,虽然可以重新启动服务器,但是这个服务器还是要平静的等待2MSL时间的过去才能进行下一次连接。
一般来说,无论何时一个报文段发往基准的连接出现错误,TCP都会发出一个复位报文段。
基准的连接:指由目的IP地址和端口号以及源IP地址和端口号指明的连接。、
RST报文段,接收方不会进行确认。收到RST的一方将终止该连接,并通知应用层连接复位
应用场合:
1、 到不存在的端口的连接请求:服务器程序端口未打开而客户端来连接。比如主机1向主机2发送一个SYN请求,表示想要连接主机2的40000端口,但是主机2上根本没有打开40000这个端口,于是就向主机1发送了一个RST。
2、 异常终止一个连接:终止一个连接的正常方式是一方发送FIN。有事这也称为有序释放,因为所有排队数据都已发送之后才发送FIN,正常情况下没有任何数据丢失。
但是也有可能发送一个复位报文段而不是FIN来中途释放一个连接,有时也被称为异常释放。
异常释放有两个优点:(1)丢弃任何待发数据并立即发送复位报文段(优先级高)。
(2)RST的接收方会区分另一端执行的是异常关闭还是正常关闭。
3、 检测半打开连接:如果一方已经关闭或者异常终止连接,而另一方却不知道。即半打开状态。比如,客户因为一些原因重启,而服务器不知道TCP已经处于半打开状态,客户再次连接,服务器将重新创建一个新的服务器程序。这样会导致服务器主机上有很多半打开的TCP连接。所以需要在重新连接的时候先发送一个RST报文段。断开之前的半打开状态的TCP连接。
4、 向一个已经关闭的连接发送报文段
5、close(sockfd)时,直接丢弃接收缓冲区未读取的数据,并给对方发一个RST。这个是由SO_LINGER选项来控制的;