网络编程中,当一段发送完数据,想要关闭连接时,一般调用close().但close是不“优雅”的,首先看一下close()到底执行了什么。
首先应清楚一点,TCP虽然是基于字节流的传输,但是TCP模块为了提高发送和接收效率,会将字节流分段打包,也就是我们常说的TCP数据包;但是这样会使得一个包内的数据并不一定是一条完整的消息,所以需要编号来完成拼接(由seq,ack完成同步)。
一、close函数
以服务端调用close(sockfd)为例: close()会立即返回,使得sockfd的引用计数减1,当前进程不能再调用有关socket读写的API,TCP模块会继续将TCP发送缓冲区中的数据尽力发送至对端。 若sockfd的引用计数为0(多进程fork会增加引用计数),则在TCP缓冲区数据发送完后会向对端发送FIN,进行四次挥手,TCP断开连接的过程;若引用计数不为0,则不会发送FIN,所以也不会断开连接,适用于父子进程共享连接套接字的情况。借用UNP里的一张图片说明当描述符引用为0时的情况:
SO_LINGER套接字选项
二、shutdown函数
int shutdown(int sockfd, int howto);
shutdown函数是用来终止连接的,而不是关闭套接字。 shutdown和close的区别 (1)close将描述符引用减1,为0时才关闭套接字;而shutdown直接触发TCP的正常终止序列(发送FIN包) (2)之所以说close不够优雅,是因为当引用计数为0时,close关闭套接字直接返回,即使TCP发送缓冲区还有数据未发送完毕,即使TCP接收缓冲区还有数据未读取完毕(这种情况较少,一般都是read完毕后才调用close),即使对端还有数据要发送过来。假设服务端调用close且未设置LINGER,close立即返回,虽然TCP模块会尽可能发送,但服务端无法确认对方进程是否完整收到数据。若客户端在还未接收完数据时就崩溃(发送FIN包),服务器进程却无法知道(因为close返回,无法调用套接字相关API) 而shutdown通过howto参数来关闭指定方向的连接,howto的方式有三种分别是: SHUT_RD:关闭sockfd上的读功能,此选项将不允许sockfd进行读操作。 SHUT_WR:关闭sockfd的写功能,此选项将不允许sockfd进行写操作。 SHUT_RDWR:关闭sockfd的读写功能。 为了优雅关闭连接,我们需要确认发送缓冲区数据确实已被对方接收到,且仍能够接收对方发送的数据。 常用的为SHUT_WR,下面详细介绍关闭写端的过程,借用UNP中一张图(图中客户端和服务端和以上我的假设相反,不过不影响)
更多关于close和shutdown的说明: 1 只要TCP栈的读缓冲里还有未读取(read)数据,则调用close时会直接向对端发送RST。 2. shutdown与socket描述符没有关系,即使调用shutdown(fd, SHUT_RDWR)也不会关闭fd,最终还需close(fd)。 3. 在已发送FIN包后write该socket描述符会引发EPIPE/SIGPIPE。 4. SO_LINGER与close,当SO_LINGER选项开启但超时值为0时,调用close直接发送RST(这样可以避免进入TIME_WAIT状态,但破坏了TCP协议的正常工作方式),SO_LINGER对shutdown无影响。 5. TCP连接上出现RST与随后可能的TIME_WAIT状态没有直接关系,主动发FIN包方必然会进入TIME_WAIT状态,除非不发送FIN而直接以发送RST结束连接。 6.避免TIME_WAIT更常用的是使用REUSEADDR选项
|