赞
踩
------------->【Linux系统编程/网络编程】(学习目录汇总) <--------------
先结合下图回顾一下TCP建立连接的三次握手过程,关闭连接的四次挥手过程,以及在此过程中的状态变化。
主动发起连接请求端: CLOSE状态 --> 发送SYN标志位–> SYN_SEND状态 --> 接收对端发送的 ACK标志位、SYN标志位 --> SEND_SYN状态 --> 发送 ACK标志位 --> ESTABLISHED状态(数据通信态)
主动关闭连接请求端: ESTABLISHED状态(数据通信态) --> 发送 FIN标志位 --> FIN_WAIT_1状态 – 接收对段发送的ACK标志位 --> FIN_WAIT_2状态(半关闭)–> 接收对端发送的 FIN标志位 --> FIN_WAIT_2状态(半关闭)–> 回发ACK标志位 --> TIME_WAIT状态(只有主动关闭的连接方,会经历该状态)–> 等 2MSL时长 --> CLOSE状态
被动接收连接请求端: CLOSE状态 -->程序启动 --> LISTEN状态 --> 接收对端发送的 SYN标志位 --> LISTEN状态 --> 发送 ACK标志位和SYN标志位 --> SYN_RCVD状态 --> 接收对端发送的ACK标志位 --> ESTABLISHED状态(数据通信态)
被动关闭连接请求端: ESTABLISHED状态(数据通信态) --> 接收对端发送的 FIN标志位 --> ESTABLISHED状态(数据通信态)–> 发送ACK标志位 --> CLOSE_WAIT状态 (说明对端【主动关闭连接端】处于半关闭状态) --> 发送FIN标志位 --> LAST_ACK状态 --> 接收对端发送的ACK标志位 --> CLOSE状态
// 相关命令:
netstat -apn | grep client 查看客户端网络连接状态
netstat -apn | grep port 查看端口的网络连接状态
说明:
- 主动建立连接–>实线部分
- 被动建立连接–>虚线部分
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可用状态。
当TCP链接中A发送FIN请求关闭,B端回应ACK后(A端进入FIN_WAIT_2状态),B没有立即发送FIN给A时,A方处在半关闭状态,此时A可以接收B发送的数据,但是A已不能再向B发送数据。从程序的角度,可以使用API来控制实现半关闭状态。
#include <sys/socket.h>
int shutdown(int sockfd, int how);
注意:
使用close中止一个连接,它只是减少描述符的引用计数,并不直接关闭连接,只有当描述符的引用计数为0时才关闭连接。
比如说:如果有多个进程共享一个套接字,close每被调用一次,计数减1,直到计数为0时,也就是所用进程都调用了close,套接字将被释放。
shutdown不考虑描述符的引用计数,直接关闭描述符。也可选择中止一个方向的连接,只中止读或只中止写。
比如说:在多进程中如果一个进程调用了shutdown(sfd, SHUT_RDWR)后,其它的进程将无法进行通信。但如果一个进程close(sfd)将不会影响到其它进程。
让4次握手关闭流程更加可靠;4次握手的最后一个ACK是是由主动关闭方发送出去的,若这个ACK丢失,被动关闭方会再次发一个FIN过来。若主动关闭方能够保持一个2MSL的TIME_WAIT状态,则有更大的机会让丢失的ACK被再次发送出去。
做一个测试,首先启动server,然后启动client,用Ctrl-C
终止server,马上再运行server,运行结果:
这是因为,虽然server的应用程序终止了,但TCP协议层的连接并没有完全断开,因此不能再次监听同样的server端口。我们用netstat
命令查看一下:
server终止时,socket描述符会自动关闭并发FIN段给client,client收到FIN后处于CLOSE_WAIT状态,但是client并没有终止,也没有关闭socket描述符,因此不会发FIN给server,因此server的TCP连接处于FIN_WAIT2状态。
现在用Ctrl-C
把client也终止掉,再观察现象:
client终止时自动关闭socket描述符,server的TCP连接收到client发的FIN段后处于TIME_WAIT状态。TCP协议规定,主动关闭连接的一方要处于TIME_WAIT状态,等待两个MSL(maximum segment lifetime)的时间后才能回到CLOSED状态,因为我们先Ctrl-C终止了server,所以server是主动关闭连接的一方,在TIME_WAIT期间仍然不能再次监听同样的server端口。
如果想要解决上述问题,就必须要设置端口复用,使用的函数原型如下:
// 这个函数是一个多功能函数, 可以设置套接字选项
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
那我们该在代码哪边加入这个函数呢?
在server代码的socket()和bind()调用之间插入如下代码:
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。