各状态详细描述:
CLOSED:表示初始状态。对服务端和C客户端双方都一样。
并等待服务端发送ACK+SYN。
和ACK标志的报文时,可以直接进入TIME_WAIT状态,而无须经过FIN_WAIT_2状态。
双方都在关闭连接。
1. 连接建立
1) Client
当Client端调用socket函数调用时,相当于Client端产生了一个处于Closed状态的套接字。
Client端又调用connect函数调用,系统为Client随机分配一个端口,连同传入connect中的参数(Server的IP和端口),这就形成了一个连接四元组,
connect调用让Client端的socket处于SYN_SENT状态。
当Server返回确认,并发送SYN,Client返回确认及SYN后,套接字处于ESTABLISHED阶段,此时双方的连接已经可以进行读写操作
2)Server
当Server端调用socket函数调用时,相当于Server端产生了一个处于Closed状态的监听套接字
Server端调用bind操作,将监听套接字与指定的地址和端口关联,然后又调用listen函数,系统会为其分配未完成队列和完成队列,此时的监听套接字
可以接受Client的连接,监听套接字状态处于LISTEN状态。
当Server端调用accept操作时,会从完成队列中取出一个已经完成的client连接,同时在server这端会产生一个会话套接字,用于和client端套接字的通信,
这个会话套接字的状态是ESTABLISH。
2. 连接关闭
与连接建立分为server/client不同,连接关闭并没有绝对的server/client之分,连接关闭分为主动关闭和被动关闭。Server和client都可以担任这两个角色
中的任意一个。如client可以关闭它与server的连接,同样的server一样也可以关闭一些长时间无读写事件发生的连接。既然这么说了,下面就会分成两个
部分:client主动关闭,server主动关闭。
Client主动关闭,Server被动关闭:
Client主动关闭,Server被动关闭的情况还是蛮多的,比如说短连接中,当一次会话结束时,client就可以关闭它与server之间的连接。
我们这边直接说close而非shutdown。
当client想要关闭它与server之间的连接,首先client这边会首先调用close函数,client端会发送一个FIN到server端,client端处于FIN_WAIT1状态。当server端
返回给client ACK后,client处于FIN_WAIT2状态,server处于CLOSE_WAIT状态。
当server端检测到client端的关闭操作(read返回为0),server端也需要调用close操作,server端会向client端发送一个FIN。此时server的状态为LAST_ACK,
当 client收到来自server的FIN后,client端的套接字处于TIME_WAIT状态,它会向server端再发送一个ack确认,此时server端收到ack确认后,此套接字
处于CLOSED状态。
Server端主动关闭的流程与Client端关闭类似,就不再多讲,下面还需要关注的是TIME_WAIT这个状态,分别按照client/server两个部分讲述。
首先说一下TCP/IP详解中描述的关于TIME_WAIT的描述及其存在的必要性:
主动关闭的socket当收到对端的FIN操作后,该socket就会处于TIME_WAIT状态,处于TIME_WAIT状态的socket会存活2MSL(Max Segment Lifetime),
之所以存活这么长时间是有理由的:
一方面是可靠的实现TCP全双工连接的终止,也就是当最后的ACK丢失后,被动关闭端会重发FIN,因此主动关闭端需要维持状态信息,以允许它重新
发送最终的ACK。
另一方面TCP在2MSL等待期间,定义这个连接(4元组)不能再使用,任何迟到的报文都会丢弃。设想如果没有2MSL的限制,恰好新到的连接正好满足原先
的4元组,这时候连接就可能接收到网络上的延迟报文就可能干扰最新建立的连接。重复的分节在网络中消逝。
3. Server端的监听套接字与会话套接字的不同:
当server端的socket调用bind和listen之后,监听套接字的状态就会变为LISTEN状态,监听套接字的工作决定了它只是监听连接。
当server端调用accept,相当于从套接字的完成队列中取出一个client的连接,此时可以确定四元组,即:client的IP和port;server的IP和port,
双方各有一个套接字进行交互,这里需要说一下server端的socket,我称server端accept后产生的套接字为会话套接字,这个套接字一出生的状态
就是ESTABLISH。由server端得四元组我们可以看出,一个server从理论上可以接收的连接数量取决于文件描述符的个数。
4,状态为TIME_WAIT是不是所有执行主动关闭的socket都会进入TIME_WAIT状态呢?
有没有什么情况使主动关闭的socket直接进入CLOSED状态呢?
主动关闭的一方在发送最后一个 ack 后就会进入 TIME_WAIT 状态 停留2MSL(max segment lifetime)时间
这个是TCP/IP必不可少的,也就是“解决”不了的。
也就是TCP/IP设计者本来是这么设计的
主要有两个原因:
1。防止上一次连接中的包,迷路后重新出现,影响新连接(经过2MSL,上一次连接中所有的重复包都会消失)
2。可靠的关闭TCP连接。在主动关闭方发送的最后一个 ack(fin) ,有可能丢失,这时被动方会重新发 fin, 如果这时主动方处于 CLOSED 状态 ,
就会响应 rst 而不是 ack。所以 主动方要处于 TIME_WAIT 状态,而不能是 CLOSED 。
TCP再次发送最后的ACK以防止这个ACK丢失(另一端超时重发最后的FIN)。
- 客户端发送一个带SYN标志的TCP报文(报文1)到服务器端,表示希望建立一个TCP连接。
- 服务器发送一个带ACK标志和SYN标志的TCP报文(报文2)给客户端,ACK用于对报文1的回应,SYN用于询问客户端是否准备好进行数据传输。
- 客户端发送一个带ACK标志的TCP报文(报文3),作为报文2的回应。
终止这个方向的连接,收到一个FIN意味着这个方向不再有数据流动,但另一个方向仍能继续发送数据,直到另一个方向也发送FIN报文。
四次挥手的具体过程如下:
- 客户端发送一个FIN报文(报文4)给服务器,表示我将关闭客户端到服务器端这个方向的连接。
- 服务器收到报文4后,发送一个ACK报文(报文5)给客户端,序号为报文4的序号加1。
- 服务器发送一个FIN报文(报文6)给客户端,表示自己也将关闭服务器端到客户端这个方向的连接。
- 客户端收到报文6后,发回一个ACK报文(报文7)给服务器,序号为报文6的序号加1。
TCP三次握手,四次挥手的时序图:
【TCP相关疑问】
- Q1 为什么在TCP协议里,建立连接是三次握手,而关闭连接却是四次握手呢?
- A1因为当处于LISTEN 状态的服务器端SOCKET当收到SYN报文(客户端希望新建一个TCP连接)后,它可以把ACK(应答作用)和SYN(同步作用)放在同一个报文里来发送给客户端。但在关闭TCP连接时,当收到对方的FIN报文时,对方仅仅表示对方没有数据发送给你了,但未必你的所有数据都已经全部发送给了对方,所以你大可不必马上关闭SOCKET(发送一个FIN报文),等你发送完剩余的数据给对方之后,再发送FIN报文给对方来表示你同意现在关闭连接了,所以通常情况下,这里的ACK报文和FIN报文都是分开发送的。
- Q2为什么TIME_WAIT 状态还需要等2*MSL秒之后才能返回到CLOSED 状态呢?
- A2因为虽然双方都同意关闭连接了,而且握手的4个报文也都发送完毕,按理可以直接回到CLOSED 状态(就好比从SYN_SENT 状态到ESTABLISH 状态那样),但是我们必须假想网络是不可靠的,你无法保证你(客户端)最后发送的ACK报文一定会被对方收到,就是说对方处于LAST_ACK 状态下的SOCKET可能会因为超时未收到ACK报文,而重发FIN报文,所以这个TIME_WAIT 状态的作用就是用来重发可能丢失的ACK报文。
- Q3关闭TCP连接一定需要4次挥手吗?
- A3不一定,4次挥手关闭TCP连接是最安全的做法。但在有些时候,我们不喜欢TIME_WAIT 状态(如当MSL数值设置过大导致服务器端有太多TIME_WAIT状态的TCP连接,减少这些条目数可以更快地关闭连接,为新连接释放更多资源),这时我们可以通过设置SOCKET变量的SO_LINGER标志来避免SOCKET在close()之后进入TIME_WAIT状态,这时将通过发送RST强制终止TCP连接(取代正常的TCP四次握手的终止方式)。但这并不是一个很好的主意,TIME_WAIT 对于我们来说往往是有利的。