close函数和shutdown函数都用于关闭一个四元组标识的连接。接下来简单介绍一下 1)close函数
#include <unistd.h>
int close(int sockfd);
返回:成功返回0,出错则返回-1;
close的默认行为是将套接字标记成关闭,然后立即返回,此后不能使用此套接字调用read和write函数,调用close后在内核层面会将套接字发送缓冲区里面的数据全部发送到对端,这部分数据确认后然后执行四次挥手。我们可以通过setsocket()函数设置 SO_LINGER选项,修改参数,来改变close的默认行为。 在并发编程时,由于父子进程共用一个套接字,父进程调用close套接字时只是导致套接字描述符的引用计数减一,当引用计数为0的时候才会终止连接,
struct linger{
int l_onoff;
int l_linger;
}
-
当l_onoff为0时,LINGER选项被关闭,此时执行tcp的默认选项。 -
当l_onoff非0且l_linger为0时,tcp将丢弃保留在套接字发送缓冲区中的任何数据,然后发送一个RST到对端,这样就避免了time_wait状态,如果此时在2MSL内使用相同的四元组建立一个新的连接,旧的数据就会被传送到这个新的连接。 -
当l_onoff非0且l_linger非0时,当套接字关闭的时候,如果套接字缓冲区仍然残留有数据,那么进程会被睡眠,直到所有的数据都被对端确认或者睡眠时间到。我们可以通过close的返回值来区分这两种情况,如果返回EWOULDBLOCK错误则代表迟滞时间到,并且此时套接字发送缓冲区的任何残留数据都会被丢弃。
其实SO_LINGER参数控制的实际是close函数在四次挥手阶段不同阶段的返回,对于情况1,则是立即发出FIN后立即返回,所以不能保证数据缓冲区的数据是否正确被对端tcp接收到,情况3则是在收到发出数据和FIN的响应后返回。可以保证数据被对端tcp收到。 2)shutdown函数 关闭一个连接通常使用close,但是close有两个限制,可以使用shutdown来解决。
- 使用close的时候会把套接字的引用计数减一,只有计数变为0时才关闭套接字,shutdown可以不用管计数就可以直接关闭套接字
- close关闭的是读和写两个方向的传送,当一方完成了数据传送,但是另一方仍然有数据需要传送的时候,需要使用shutdown来达到半关闭的状态,使得对方的数据能够正确到达。
#include <sys/socket.h>
int shutdown(int sockfd,int howto);
返回:若成功则为0,若出错则为-1;
函数的行为依赖于howto参数的值:
- SHUT_RD: 关闭连接的读这一半,将读缓冲区里面的所有数据都进行确认(向对端发送ack),然后丢弃这部分数据。
- SHUT_WR:关闭连接的写这一半,这成为半关闭,当前留在套接字发送缓冲区的数据将被发送掉,然后正常终止序列,此时不管引用计数是否等于0。
- SHUT_RDWR:这与调用两次shutdown函数相等,第一次使用SHUT_RD,第二次使用SHUT_WR。
我们使用close关闭连接时,设置SO_LINGERCNA参数,close的成功返回只能保证传输层层面的数据确认,并不能保证应用进程也已经读取数据。要确保应用进程读取数据,有两种方法: 1)shutdown+read,在调用shutdown函数后加一个阻塞的read函数,read函数会在第三次挥手的时候收到对端的FIN后返回,保证应用程序端可以处理完数据 2)应用ACK,在调用closed之前先使用阻塞read函数从对端读取一个字节的数据作为应用ACK,这个字节用全零作为标识。 这两个函数对tcp的影响具体入下图:
|