46、SO_LINGER选项:l_onoff/l_linger设置、强制关闭与优雅关闭

做网络编程这么多年,关闭连接这件事,看起来简单,坑却不少。

我记得刚入行那会儿,写一个高并发服务器。压测时发现,客户端断开后,服务端偶尔会丢数据。查了两天,最后发现是 close() 的行为没搞明白。嗯,今天我们就聊聊 SO_LINGER 这个选项。

SO_LINGER 到底是什么?

说白了,它控制的是 close()shutdown() 之后,内核怎么处理还没发完的数据。

默认情况下,你调用 close(),内核会立即返回。但内核会在后台继续尝试把发送缓冲区里的数据发完。这叫优雅关闭

但有些场景下,你不想等。你想立刻断掉。这时候就需要 SO_LINGER 了。

结构体长什么样?

struct linger {
    int l_onoff;   // 0: 关闭本选项  非0: 开启
    int l_linger;  // 等待时间,单位秒
};

就这么两个字段。但组合起来,有三种完全不同的行为

三种模式,一张表说清楚

l_onoff l_linger 行为描述
0 忽略 默认模式。close() 立即返回,内核后台发完数据再关闭
非0 0 强制关闭。close() 立即返回,丢弃缓冲区数据,发送 RST
非0 >0 带超时的优雅关闭。close() 阻塞,直到数据发完或超时

模式一:默认模式(l_onoff = 0)

这是最常用的。你调用 close(),函数立刻返回。内核在后台默默地把数据发完,然后完成四次挥手。

好处是不阻塞你的程序。坏处是——你没法知道数据到底发完没有。

我在项目中遇到过一个问题:客户端调了 close() 就退出进程了。服务端那边还在等最后一条数据。结果数据丢了,因为进程退出时,内核直接回收了资源。嗯,这里要注意:进程退出不等于数据发完

模式二:强制关闭(l_onoff = 1, l_linger = 0)

这个模式很暴力。调用 close() 后,内核立刻丢弃发送缓冲区里所有数据。然后发送一个 RST 报文 给对端。

对端收到 RST 会怎样?read() 返回 -1,errno 是 ECONNRESET。说白了就是连接被强制重置了。

什么时候用这个?

  • 你确定不需要再发数据了
  • 你想快速释放资源
  • 你不在乎对端能不能收到最后的数据

我曾经写一个心跳检测模块,发现某个连接已经死了,就直接强制关闭。省得等超时。

警告:强制关闭不会走四次挥手。对端会收到 RST,而不是 FIN。如果对端还在等你的数据,它会一脸懵。

模式三:带超时的优雅关闭(l_onoff = 1, l_linger > 0)

这个模式比较折中。你设置一个超时时间,比如 5 秒。

调用 close() 后,函数会阻塞。内核在这段时间内尽力发完数据。如果数据发完了,正常关闭。如果超时了,强制关闭。

返回值也能告诉你结果:

  • 返回 0:数据发完了,正常关闭
  • 返回 -1,errno = EWOULDBLOCK:超时了,数据没发完

这个模式有个大坑:阻塞。如果你的程序是事件驱动的,一个 close() 卡住 5 秒,整个事件循环就废了。

我个人习惯:非阻塞 socket 下,不要用这个模式。你想想看,一个非阻塞 socket 调了阻塞的 close(),多别扭。

代码示例:设置 SO_LINGER

#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <unistd.h>

int set_linger(int fd, int onoff, int linger_sec) {
    struct linger l;
    l.l_onoff = onoff;
    l.l_linger = linger_sec;

    if (setsockopt(fd, SOL_SOCKET, SO_LINGER, &l, sizeof(l)) < 0) {
        perror("setsockopt SO_LINGER");
        return -1;
    }
    return 0;
}

int main() {
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        perror("socket");
        return 1;
    }

    // 设置强制关闭
    set_linger(sock, 1, 0);

    // ... 连接、通信 ...

    close(sock);
    return 0;
}

什么时候选哪种?

  • 默认模式:绝大多数场景。简单、高效、不阻塞。
  • 强制关闭:你确定连接已经异常了,或者你不在乎数据完整性。
  • 带超时的优雅关闭:你非常在意数据完整性,且能接受阻塞。比如文件传输的最后一步。
小技巧:如果你用默认模式,又想知道数据是否发完,可以在 close() 之前先调用 shutdown(fd, SHUT_WR)。这样对端 read() 会返回 0,知道你要关闭了。然后你再 close()。

核心逻辑流程图

SO_LINGER 决策流程 调用 close() l_onoff == 0? 默认模式 close() 立即返回 内核后台发数据 l_linger == 0? 强制关闭 丢弃数据,发送 RST 带超时的优雅关闭 阻塞等待,超时则强制

避坑指南

我曾经在一个项目里,用 SO_LINGER 设置超时 10 秒。结果压测时,大量连接卡在 close() 上。线程池被打满,服务雪崩。

后来我改成默认模式 + shutdown(SHUT_WR) 的方式。问题解决了。

所以我的建议是:

  • 别在非阻塞 socket 上用带超时的 SO_LINGER
  • 别在生产环境随便设 l_linger 超过 2 秒
  • 如果你不确定,就用默认模式
核心总结:SO_LINGER 控制的是 close() 之后的行为。默认模式最安全。强制关闭用于异常场景。带超时的模式慎用,容易阻塞。

好了,关于 SO_LINGER 就聊这么多。记住一句话:关闭连接,比打开连接更需要小心


公众号:蓝海资料掘金营,微信deep3321