linux网络编程(二)TCP通讯状态
- TCP状态转换
- 为什么需要等待2MSL?
- 端口复用
TCP状态转换
tcp协议连接开始会经过三次握手,客户端和服务器开始都会处于CLOSED状态
第一次握手:客户端会先发送SYN请求给服务器,客户端处于SYN_SET状态,
第二次握手:服务器接收到SYN后,发给客户端ACK回答和SYN请求,服务器从LISTEN变成SYN_RCVD
第三次握手:客户端接收到ACK和SYN请求后,发送给服务器ACK回应,客户端从SYN_SET变成ESTABLISHED状态,服务器接收到ACK回应后,也变为ESTABLISHED状态
tcp协议关闭会经过四次握手,假设客户端为主动关闭,服务器为被动关闭
第一次握手:客户端发送FIN请求,状态变为FIN_WAIT_1
第二次握手:服务器接收到FIN请求,同时发送ACK应答,状态为CLOSE_WAIT,客户端收到ACK应答后,变为FIN_WAIT_2状态,此时处于半关闭的状态
第三次握手:服务器发送FIN请求,状态为LACK_ACK
第四次握手:客户端接收到FIN请求后,发送ACK应答,状态变为TIME_WAIT,等待2MSL之后关闭
- CLOSED:表示初始状态。
- LISTEN:该状态表示服务器端的某个SOCKET处于监听状态,可以接受连接。
- SYN_SENT:这个状态与SYN_RCVD遥相呼应,当客户端SOCKET执行CONNECT连接时,它首先发送SYN报文,随即进入到了SYN_SENT状态,并等待服务端的发送三次握手中的第2个报文。SYN_SENT状态表示客户端已发送SYN报文。
- SYN_RCVD: 该状态表示接收到SYN报文,在正常情况下,这个状态是服务器端的SOCKET在建立TCP连接时的三次握手会话过程中的一个中间状态,很短暂。此种状态时,当收到客户端的ACK报文后,会进入到ESTABLISHED状态。
- ESTABLISHED:表示连接已经建立。
- FIN_WAIT_1: FIN_WAIT_1和FIN_WAIT_2状态的真正含义都是表示等待对方的FIN报文。区别是:FIN_WAIT_1状态是当socket在ESTABLISHED状态时,想主动关闭连接,向对方发送了FIN报文,此时该socket进入到FIN_WAIT_1状态。
FIN_WAIT_2状态是当对方回应ACK后,该socket进入到FIN_WAIT_2状态,正常情况下,对方应马上回应ACK报文,所以FIN_WAIT_1状态一般较难见到,而FIN_WAIT_2状态可用netstat看到。 - FIN_WAIT_2:主动关闭链接的一方,发出FIN收到ACK以后进入该状态。称之为半连接或半关闭状态。该状态下的socket只能接收数据,不能发。
- TIME_WAIT: 表示收到了对方的FIN报文,并发送出了ACK报文,等2MSL后即可回到CLOSED可用状态。如果FIN_WAIT_1状态下,收到对方同时带
FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。 - CLOSING: 这种状态较特殊,属于一种较罕见的状态。正常情况下,当你发送FIN报文后,按理来说是应该先收到(或同时收到)对方的 ACK报文,再收到对方的FIN报文。但是CLOSING状态表示你发送FIN报文后,并没有收到对方的ACK报文,反而却也收到了对方的FIN报文。什么情况下会出现此种情况呢?如果双方几乎在同时close一个SOCKET的话,那么就出现了双方同时发送FIN报文的情况,也即会出现CLOSING状态,表示双方都正在关闭SOCKET连接。
- CLOSE_WAIT: 此种状态表示在等待关闭。当对方关闭一个SOCKET后发送FIN报文给自己,系统会回应一个ACK报文给对方,此时则进入到CLOSE_WAIT状态。接下来呢,察看是否还有数据发送给对方,如果没有可以close这个SOCKET,发送FIN报文给对方,即关闭连接。所以在CLOSE_WAIT状态下,需要关闭连接。
- LAST_ACK: 该状态是被动关闭一方在发送FIN报文后,最后等待对方的ACK报文。当收到ACK报文后,即可以进入到CLOSED可用状态。
为什么需要等待2MSL?
当客户端也就是主动关闭的一端,接收到服务器也就是被动关闭的一端的FIN请求时,需要回应ACK回应,但是客户端并不能保证ACK回应服务器一定能收到,就添加了一个保护机制,在2MSL时间内,如果服务器没有收到ACK回应,服务器会再一次发送FIN请求,知道能收到客户端收到ACK回应。服务器在2MSL之内一直没有收到的话,客户端将做超时处理,关闭失败。
作用:
- 让4次握手关闭流程更加可靠,4次握手的最后一个ACK是是由主动关闭方发送出去的,若这个ACK丢失,被动关闭方会再次发一个FIN过来。若主动关闭方能够保持一个2MSL的TIME_WAIT状态,则有更大的机会让丢失的ACK被再次发送出去。
- 防止lost duplicate对后续新建正常链接的传输造成破坏。lost uplicate在实际的网络中非常常见,经常是由于路由器产生故障,路径无法收敛,导致一个packet在路由器A,B,C之间做类似死循环的跳转。IP头部有个TTL,限制了一个包在网络中的最大跳数,因此这个包有两种命运,要么最后TTL变为0,在网络中消失;要么TTL在变为0之前路由器路径收敛,它凭借剩余的TTL跳数终于到达目的地。但非常可惜的是TCP通过超时重传机制在早些时候发送了一个跟它一模一样的包,并先于它达到了目的地,因此它的命运也就注定被TCP协议栈抛弃。
端口复用
在server的TCP连接没有完全断开之前不允许重新监听是不合理的
在server代码的socket()和bind()调用之间插入如下代码:
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));