1.什么是连接?
TCP协议属于运输层协议,运输层协议是在端系统中,而不是在路由器中实现的,所以,这种连接不是一条电路,它的连接状态,完全保存在两个端系统中.中间的网络元素,包括路由器和链路层交换机这些网络元素不会维持TCP的连接状态.一个进程往另一端的某进程发送数据之前,必须先相互握手,即它们必须相互发送某些预备报文段,以建立确保数据传输的参数.
2.连接的三次握手过程
第一次握手:客户端首先向服务器端发送一个被称为SYN的特殊TCP报文段,此报文段的SYN比特位被置为1,另外客户端会随机选择一个初始序号client_seq并将此编号放置在TCPSYN报文段的序号字段中并发送给服务器
第二次握手:服务器收到此TCPSYN报文段后,提取出SYN字段,并为该连接分配TCP缓存和变量,然后向客户端发送允许连接的报文段SYNACK,此报文段中SYN比特位被置为1,然后该报文段的确认号字段被置为client_seq+1,序列号置为server_seq
第三次握手:客户端在收到SYNACK之后,客户端也要给该连接分配缓存和变量,然后向服务器发送另一个报文段,这最后一个报文段对服务器的允许连接的报文段进行了确认(将确认号置为server_seq+1).同时,因为连接已建立 ,SYN标志为此时为0
要注意的是,以上三次握手过程中,在前两个握手阶段,都不含有应用层的任何数据,而在第三个阶段中,可携带应用层的数据 .三次握手完成后,以后的每个报文段,SYN比特位都被置为0,实际上,只有握手过程中的第一二次握手,SYN比特位才为1.
三次握手过程图标如下:
(TCP三次握手过程)
3.连接的关闭
TCP连接需要三次握手,而关闭需要四次恢手,TCP连接的任一方都能主动发起关闭
a.A方向B方发送一个特殊的报文段,此报文段FIN标志位被设为1.此时,A方进入FIN_WAIT_1状态,并关闭写通道
b.B方收到A发来的带有FIN标志的报文段后,先发送ACK报文段,同时关闭读通道(也就是说现在不再从这个连接上读取数据了,现在read会返回0),此时B将进入CLOSE_WAIT状态,再发送一个FIN标志位为1的报文段
c.A方处于FIN_WAIT_1状态时,收到B方发来的ACK确认报文后,进入FIN_WAIT_2状态
d.A方处于FIN_WAIT_2状态时,收到B方发来的带有FIN标志位为1的报文段后,进入TIME_WAIT状态,此状态维持的时间与具体实现相关,一般为2分钟.经过等待后,连接正式关闭,所有资源将被释放
主动发起方,在TIME_WAIT后,才会彻底关闭,被动关闭方,在对方的ACK到来后关闭
4.TCP的半关闭
TCP提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力,这就是所谓的半连接.当向对方发送一个FIN给另一端时,就表明本方已结束发送数据,但依然可以接收另一端发来的数据,直到对方也发来了FIN.也就是说,在FIN_WAIT_1-FIN_WAIT_2这一段时间内,都处于半关闭状态.
服务器和客户端应用程序判断对方是否已经关闭连接的方法是:read调用返回0(收到结束报文段).socket网络编程接口通过shutdown函数提供了对半关闭的支持.
5.CLOSE_WAIT状态
当一方主动关闭连接时(通过close或shutdown系统调用向服务器发送结束报文段),另一方通过返回确认ACK使连接进入CLOSE_WAIT状态。这个状态含义就是:等待主动关闭方应用程序关闭。通常,被动方也会立即给主动关闭方发送FIN报文段来关闭连接。
这将使连接转移到LAST_ACK状态 ,以等待主动关闭方对FIN报文段的最后一次确认,一旦确认完成,连接就彻底关闭了。
6.TIME_WAIT状态
主动关闭的一方,在收到对方发来的FIN后,并没有直接进入CLOSED状态,而是转移到TIME_WAIT状态.在这个状态,主动关闭的一方要等待长为2MSL(报文段最大生存时间)的时间,
才能完全关闭.MSL是TCP报文段在网络中的最大生存时间,RTF建议值是2min.
time_wait状态存在的原因有二:
a.可靠地终止TCP连接
b.保证让迟来的TCP连接报文段有足够的时间被识别并丢弃
对于第一个原因,如果主动关闭方收到对方发来的FIN后,发送给被动关闭方的ACK没有被正常收到,则被动关闭方会再次发送FIN,所以如果在TIME_WAIT状态时,对方没有再次发送FIN,则表示对方已经收到了ACK.已经正确关闭了.在Linux系统中, 一个TCP端口不能被同时打开多次,当一个TCP连接处于TIME_WAIT状态时,将无法立即使用该连接占用着的端口.反过来,如果不存在TIME_WAIT状态,则应用程序能够立即建立一个和刚关闭的连接具有相同IP和端口的连接.这个连接可能会收到原来的连接的数据,这显然不应该发生,这就是time_wait状态存在的第二个原因.
另外,TCP报文段的最大生存时间是MSL,所以坚持2MSL时间的TIME_WAIT状态能够确保网络上两个传输方向上尚未被接收到的,迟到的TCP报文段都已经消失.因此一个具有相同IP和端口的连接就可以安全地建立,而绝对不会收到属于原来连接的应用数据.
对于客户端来说,通常不用担心上面的TIME_WAIT导致不能绑定对应端口的问题,因为客户端一般使用系统自动分配的临时端口号,而由于随机性,临时端口号一般和程序上一次使用的端口号(还处于TIME_WAIT状态的那个连接使用的端口号)不同,所以客户端可以立即重启.而如果是服务器主动关闭连接后异常终止,则因为它总是使用一个相同的端口号,所以因为TIME_WAIT状态将导致它不能立即重启,不过可以通过设置socket选项SO_REUSEADDR来强制进程立即使用TIME_WAIT状态的连接占用的端口.