当前位置: 首页 > 工具软件 > Linger > 使用案例 >

优雅的关闭连接 --- 使用shutdown和setsockopt(SO_LINGER)实现

谭晓博
2023-12-01

优雅关闭:如果发送缓存中还有数据未发出则其发出去,并且收到所有数据的ACK之后,发送FIN包,开始关闭过程。TCP连接线关闭一个方向,此时另外一个方向还是可以正常进行数据传输。

强制关闭:如果缓存中还有数据,则这些数据都将被丢弃,然后发送RST包,直接重置TCP连接。两边都关闭了,服务端处理完的信息没有正常传给客户端。

 

一、使用shudown优雅的关闭连接

1.1、close()函数:

       #include <unistd.h>

       int close(int fd);

close()函数会使套接字的引用计数减1,若套接字的引用计数为0,则彻底关闭连接,并且会关闭TCP两个方向的数据流,既不能发送数据也不能接收数据。close函数不能关闭一个方向上的连接,而shutdown函数可以实现。如果服务端还又没有处理完的数据,不能正常发送给客户端。

 

1.2、shutdown函数

int shutdown(int sock, int howto); 

参数howto的取值:
    SHUT_RD:断开输入流。套接字无法接收数据(即使输入缓冲区收到数据也被抹去),无法调用输入相关函数。
    SHUT_WR:断开输出流。套接字无法发送数据,但如果输出缓冲区中还有未传输的数据,则将传递到目标主机。
    SHUT_RDWR:同时断开 I/O 流。相当于分两次调用 shutdown(),其中一次以 SHUT_RD 为参数,另一次以 SHUT_WR 为参数。

 

1.3、close()和shutdown()的区别

  • close会关闭连接了,并释放所有连接对应的资源,而shutdown并不会释放掉套接字和所有资源。
  • close有引用计数,例如父子进程都打开了某个文件描述符,其中某个进程调用了close函数,会使close函数的引用计数减1,直到套接字的引用计数为0,才会真正的关闭连接。而shutdown函数可以无视引用计数,直接关闭连接。
  • close的引用计数的存在导致不一定会发出FIN结束报文,而shutdown一定会发出FIN报文。
  • shutdown() 用来关闭连接,而不是套接字,不管调用多少次 shutdown(),套接字依然存在,直到调用 close() / closesocket() 将套接字从内存清除。
  • 调用 close()关闭套接字时,或调用 shutdown() 关闭输出流时,都会向对方发送 FIN 包。FIN 包表示数据传输完毕,计算机收到 FIN 包就知道不会再有数据传送过来。
  • 默认情况下,close()引用计数为0后会立即往网络中发送FIN包,不管输出缓冲区中是否还有数据,而shutdown() 会等输出缓冲区中的数据传输完毕再发送FIN包。也就意味着,调用 close()将丢失输出缓冲区中的数据,而调用 shutdown() 不会。

 

二、使用setsockopt设置SO_LINGER实现优雅的关闭连接

2.1 LINGER

LINGER结构包含了指定套接字的信息,当调用了close()函数关闭该套接字时,LINGER结构中的信息指定了如何处理未发送的数据。

typedef struct linger {
 
 u_short l_onoff;
 
u_short l_linger;
 
} LINGER, *PLINGER, *LPLINGER;

参数

l_onoff

l_linger

close行为

发送队列

底层行为

忽略

立即返回。

保持直至发送完成。

系统接管套接字并保证将数据发送至对端。(就是正常的close)

非零

立即返回。

立即放弃。

直接发送RST包,自身立即复位,不用经过2MSL状态。对端收到复位错误号。

非零

非零

阻塞直到l_linger时间超时或数据发送完成。(套接字必须设置为阻塞)

在超时时间段内保持尝试发送,若超时则立即放弃。

超时则同第二种情况,若发送完成则皆大欢喜。

取值方案:

  • 设置 l_onoff为0,则该选项关闭,l_linger的值被忽略,等于内核缺省情况,close调用会立即返回给调用者,如果可能将会传输任何未发送的数据;
  • 设置 l_onoff为非0,l_linger为0,当调用close的时候,TCP连接会立即断开.send buffer中未被发送的数据将被丢弃,并向对方发送一个RST信息.值得注意的是,由于这种方式,不是以4次握手方式结束TCP链接,所以,TCP连接将不会进入TIME_WAIT状态,这样会导致新建立的可能和就连接的数据造成混乱。这种关闭方式称为“强制”或“失效”关闭。
  • 设置 l_onoff 为非0,l_linger为非0,在这种情况下,回事的close返回得到延迟。调用close去关闭socket的时候,内核将会延迟。也就是说,如果send buffer中还有数据尚未发送,该进程将会被休眠直到一下任何一种情况发生:

a. send buffer中的所有数据都被发送并且得到对方TCP的应答消息(这种应答并不是意味着对方应用程序已经接收到数据)

b.延迟时间消耗完。在延迟时间被消耗完之后,send buffer中的所有数据都将会被丢弃。

这种关闭称为“优雅的”关闭。

 

2.2 setsockopt

setsockopt()函数的作用是设置套接字的选项。

man手册查阅函数原型:

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int getsockopt(int sockfd, int level, int optname,
                      void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname,
                      const void *optval, socklen_t optlen);

参数:

  • sockfd表示要设置选项的套接字;
  • level指定了选项的级别,对于套接字优雅关闭的选项,该值设置为SOL_SOCKET;
  • optname表示要设置套接字的选项,该选项必须是在level中定义的,如果设置套接字需要优雅关闭,则该参数的值为SO_LINGER,如果套接字直接关闭,则该参数的值为SO_DONTLINGER;
  • optval是缓冲区的指针,该缓冲区中包含了选项的值;
  • optlen是optval指向的缓冲区的大小。

返回值:

  • 如果setsockopt()函数执行成功,则返回值是0,否则返回值为SOCKET_ERROR。

总结:

       通过setsockopt可以设置SO_LINGER,从而实现优雅的关闭连接

示例:

struct linger tmp = {1, 1};
setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &tmp, sizeof(tmp));

 

 类似资料: