开始的时候客户端和服务器都是处于CLOSED。主动打开连接的是客户端,被动打开连接的是服务端。
- 服务器端调用listen后进入LISTEN(监听)状态, 等待客户端连接;
- 客户端调用connect,然后向服务器发出连接请求,请求报文中SYN=1,同时选择一个初始序列号 seq=x ,此时TCP客户端进程进入了SYN-SENT(同步已发送状态)状态。
- 服务端收到请求报文后,如果同意连接的话,则创建新的socket套接字,并发出确认报文。确认报文中ACK=1,SYN=1,确认号是ack=x+1,同时也要为自己初始化一个序列号 seq=y,此时,TCP服务器进程进入了SYN-RCVD(同步收到)状态。
- TCP客户进程收到确认后,还要向服务器给出确认。确认报文中ACK=1,ack=y+1,自己的序列号seq=x+1。此时,TCP连接建立,客户端进入ESTABLISHED(已建立连接)状态。
- 当服务器收到客户端的确认后也进入了ESTABLISHED状态。此后双方就可以开始通信了。
- 客户端进程发出连接释放报文FIN=1,并且停止发送数据。此时,客户端进入FIN-WAIT1(终止等待1)状态。这时候客户端处于一个半关闭的状态,即客户端已经没有数据需要发送了,但是服务器若要发送数据,客户端依然需要接受。
- 服务器收到连接释放报文后,发送确认报文ACK=1。此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。进入CLOSE_WAIT后说明服务器准备关闭连接。
- 客户端收到服务器的确认请求之后,此时客户端就进入了FIN-WAIT2(终止等待2)状态,等待服务器发送连接释放报文。(在这个之前还需要接受服务器发送的最后的数据)。
- 当服务器真正调用close关闭连接时, 会向客户端发送FIN=1, 此时服务器进入LAST_ACK(最后确认)状态, 等待客户端的最后一次ACK回复。
- 客户端收到服务器的链接释放报文之后,必须发出确认报文ACK=1。此时,客户端就进入了TIME-WAIT(时间等待)状态,等待用户关闭套接字。注意此时TCP链接还没有释放,必须经过2*MSL(报文最大生命周期)的时间后,当客户端撤销相应的TCP后,才进入CLOSED状态。
- 服务器只要收到客户端发出的确认,彻底关闭连接,立即就进行CLOSED状态,于是就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。
握手为什么是三次?握手两次或者握手四次可以吗?
可以总结一句: 只能握手三次,因为两次不安全,四次没有必要:tcp是一个双工通信,需要双方都要确保对方具有数据收发的能力。
- 如果使用的是两次握手建立连接,假设有这样一种场景:客户端发送一个SYN请求连接并没有丢失,只是因为在网络结点中滞留的时间太长了,由于TCP的客户端迟迟没有收到确认报文,以为服务器没有收到,此时重新向服务器发送SYN请求,此后客户端和服务器经过两次握手完成连接,传输数据,然后关闭连接。此时此前滞留的那一次请求连接,网络通畅了到达了服务器,这个报文本该是失效的,但是,两次握手的机制将会让客户端和服务器再次建立连接,会造成服务端创建多余的socket套接字,这将导致不必要的错误和资源的浪费。如果采用的是三次握手,就算是那一次失效的报文传送过来了,服务端接受到了那条失效报文并且回复了确认报文,但是客户端不会再次发出确认。由于服务器收不到确认,就知道客户端并没有请求连接。还有一种情况:如果客户端发送SYN请求之后就退出了,这时候服务端如果不进行一次确认的话,就会导致服务端发送的数据无客户端进行接收,导致丢包。
- 如果使用的是四次握手建立连接,其中服务端发送的SYN 与 ACK 没必要拆开发送,应为如果拆开发送就不多创建一个TCP头部,浪费资源。
挥手为什么是四次?而不能是三次?
套接字的关闭有好多的操作:shutdown(fd,SHUT_RD/SHUT_WR)可以分别关闭套接字的读和写、close。当发送端close或者通过shutdown关闭套接字写的操作时, 会触发向接收端发送FIN包,接收端收到FIN包之后,仅仅认为发送端不在继续发送数据(但是可以接收数据),因此recv会返回0,此时接收端会进入CLOSE_WAIT状态,说明接收端准备关闭连接。
- 关闭写端会触发向对端发送FIN包,所以在发送端若只是关闭了套接字写端,并没有关闭套接字读端。接收端收到了发送端的FIN包时,仅仅表示发送端不再发送数据了但是还可以接收数据。因此ACK 和 FIN 不能够一起发送,否则发送端在有些数据还没有接受成功的情况下收到了FIN包,则会造成有些数据没有处理完毕,或者发送端有数据没有发送完毕。因此ACK与FIN一般都会分开发送,所以挥手不能够是三次,必须是四次!
理解TIME_WAIT状态
现在做一个测试,首先启动server,然后启动client,然后用Ctrl-C使server终止,这时马上再运行server, 结果是:
这是因为,虽然server的应用程序终止了,但TCP协议层的连接并没有完全断开,因此不能再次监 听同样的server端口. 我们用netstat命令查看一下:
TCP协议规定,主动关闭连接的一方要处于TIME_ WAIT状态,等待两个MSL(maximum segment lifetime) 的时间后才能回到CLOSED状态. 我们使用Ctrl-C终止了server, 所以server是主动关闭连接的一方, 在TIME_WAIT期间仍然不能再次监听 同样的server端口; MSL在RFC1122中规定为两分钟,但是各操作系统的实现不同, 在Centos7上默认配置的值是60s;
TIME_WAIT有什么用呢,为什么客户端最后要等待2个MSL时间?
- 假设主动关闭方没有这个等待时间,而是直接关闭了套接字,所以可能有新的客户端使用关闭的套接字地址信息。如果此时已经关闭的客户端发送的最后一个ACK报文丢失了。服务端看到客户端还没有给我回应,认为客户端没有收到我发送的请求断开报文,所以服务器又会重新发送一次FIN包。所以,新的客户端有可能收到这个FIN包,对新链接造成影响;还有可能新启动的客户端向服务端发送SYN请求就会被认为状态错误,导致重置连接。因此综上:主动关闭方最后一个ACK之后,必须等待一段时间。在等待时间中,收到了服务端重发的FIN包就可以重新回复一次ACK。PS:TCP协议规定,主动关闭连接的一方要处于TIME_ WAIT状态,等待2个MSL(maximum segment lifetime:报文最大生存周期) 的时间后才能回到CLOSED状态.,等待的时间是2个MSL,在理论上保证最后一个报文可靠到达。(MSL:报文的最大生命周期,在传输中,一个报文的最大生命周期就是一个MSL时间,过了这个时间该报文就会被丢弃)。在这个等待时间中,目的是就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失,使本次通信的所有数据都消失在网络之中,避免随后续连接造成影响,这样新的连接中就不会出现旧连接的影响。
服务端主机上出现了大量的TIME_WAIT连接,是什么原因,如何处理?
服务器需要处理非常大量的客户端的连接(每个连接的生存时间可能很短, 但是每秒都有很大数量的客户 端来请求). 这个时候如果由服务器端主动关闭连接(比如某些客户端不活跃, 就需要被服务器端主动清理掉), 就会产 生大量TIME_WAIT连接. 由于我们的请求量很大, 就可能导致TIME_WAIT的连接数很多, 每个连接都会占用一个通信五元组(源ip, 源端口, 目的ip, 目的端口, 协议). 其中服务器的ip和端口和协议是固定的. 如果新来的客户端连接的ip和 端口号和TIME_WAIT占用的链接重复了, 就会出现问题.
-
原因:应为TIME_WAIT是主动关闭方才有的,所以是因为服务端大量关闭了客户端的套接字产生的。
-
处理方式
1、调整time_wait等待时间,将他调短一点
2、int setsockopt(int fd, int level, int name, void* options,int len)。使用该接口可以重用地址,即可以将处于TIME_WAIT的地址信息进行重新使用