当前位置: 首页 > news >正文

Linux服务器编程实践52-SO_LINGER选项:控制close关闭TCP连接的行为

1. 引言:close关闭TCP连接的默认行为

在Linux网络编程中,close()系统调用是关闭文件描述符的常用方式,对于TCP连接对应的socket也不例外。默认情况下,当我们调用close(sockfd)关闭一个TCP socket时,close会立即返回,但内核并不会马上终止连接——它会负责将该socket对应的TCP发送缓冲区中残留的数据(未被对方确认的数据包)发送给通信对端,直到所有数据发送完成或超时。

这种默认行为虽然安全(避免数据丢失),但在某些场景下并不适用。例如:

  • 服务器需要快速回收资源,不希望等待残留数据发送;
  • 检测到通信异常(如客户端恶意连接),需要立即终止连接以避免资源浪费;
  • 需要精确控制连接关闭的时机,避免长时间阻塞。

为了解决这些问题,Linux提供了SO_LINGER socket选项,允许开发者自定义close关闭TCP连接的行为。

2. SO_LINGER选项的核心原理

SO_LINGER选项通过setsockopt()系统调用设置,其核心是通过一个linger结构体控制close的行为。该结构体定义如下:

#include <sys/socket.h>
struct linger {int l_onoff;   // 开启(非0)或关闭(0)SO_LINGER选项int l_linger;  // 滞留时间(秒),仅当l_onoff非0时有效
};

其中,l_onoff是开关变量,l_linger是滞留时间(单位:秒)。根据这两个变量的不同组合,close会产生三种不同的行为,我们通过下表详细说明:

l_onoffl_lingerclose行为适用场景
0忽略默认行为:close立即返回,内核异步发送残留数据,连接正常关闭(四次挥手)。大多数常规场景,确保数据不丢失。
非00close立即返回,内核丢弃发送缓冲区残留数据,向对端发送RST报文(复位连接),强制终止连接。异常场景(如客户端超时),快速回收资源。
非0>0close阻塞等待l_linger秒:
1. 若期间残留数据发送完成且收到确认,连接正常关闭;
2. 若超时仍未完成,内核丢弃残留数据,强制关闭连接。
需要等待数据发送,但又要避免无限阻塞的场景。

注意:当l_onoff=非0l_linger>0时,close的阻塞行为还受socket是否为“阻塞模式”影响:
- 阻塞socket:close会阻塞直到数据发送完成或超时;
- 非阻塞socket:close立即返回,需通过返回值和errno判断数据是否发送成功。

3. SO_LINGER选项的实践示例

下面通过三个完整的代码示例,演示SO_LINGER三种配置的实际效果。所有示例基于TCP通信,包含客户端和服务器端代码。

3.1 示例1:默认行为(l_onoff=0)

该示例展示SO_LINGER关闭时,close的默认行为——内核异步发送残留数据。

服务器端代码(默认关闭SO_LINGER)
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>int main() {// 1. 创建TCP socketint listen_fd = socket(PF_INET, SOCK_STREAM, 0);assert(listen_fd >= 0);// 2. 绑定地址struct sockaddr_in addr;bzero(&addr, sizeof(addr));addr.sin_family = AF_INET;addr.sin_addr.s_addr = htonl(INADDR_ANY);addr.sin_port = htons(8888);assert(bind(listen_fd, (struct sockaddr*)&addr, sizeof(addr)) == 0);// 3. 监听assert(listen(listen_fd, 5) == 0);// 4. 接受客户端连接struct sockaddr_in client_addr;socklen_t client_len = sizeof(client_addr);int conn_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_len);assert(conn_fd >= 0);printf("客户端已连接:%s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));// 5. 发送数据后关闭(默认SO_LINGER关闭)const char* data = "Hello, Client! This is server data.";send(conn_fd, data, strlen(data), 0);printf("发送数据完成,调用close()...\n");// 默认行为:close立即返回,内核异步发送残留数据close(conn_fd);printf("close()已返回,连接将由内核完成关闭\n");close(listen_fd);return 0;
}
客户端代码
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>int main() {// 1. 创建TCP socketint sock_fd = socket(PF_INET, SOCK_STREAM, 0);assert(sock_fd >= 0);// 2. 连接服务器struct sockaddr_in server_addr;bzero(&server_addr, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");server_addr.sin_port = htons(8888);assert(connect(sock_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == 0);// 3. 接收服务器数据char buf[1024] = {0};ssize_t recv_len = recv(sock_fd, buf, sizeof(buf)-1, 0);if (recv_len > 0) {printf("收到服务器数据:%s\n", buf);} else if (recv_len == 0) {printf("服务器正常关闭连接(四次挥手)\n");} else {printf("接收数据失败\n");}close(sock_fd);return 0;
}
运行结果
// 服务器输出
客户端已连接:127.0.0.1:54321
发送数据完成,调用close()...
close()已返回,连接将由内核完成关闭// 客户端输出
收到服务器数据:Hello, Client! This is server data.
服务器正常关闭连接(四次挥手)

结果说明:close立即返回,内核后续完成TCP四次挥手,客户端正常收到数据并检测到连接正常关闭。

3.2 示例2:强制关闭(l_onoff=1,l_linger=0)

该示例展示SO_LINGER开启且滞留时间为0时,close强制关闭连接的行为——丢弃残留数据并发送RST报文。

// 服务器端关键代码(仅修改SO_LINGER配置,其他代码同示例1)
#include <sys/socket.h>// 设置SO_LINGER:l_onoff=1,l_linger=0
struct linger ling = {1, 0};
setsockopt(conn_fd, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling));// 发送数据(故意预留残留数据)
const char* data = "Hello, Client! This is server data with more content...";
// 只发送部分数据,制造残留
send(conn_fd, data, 10, 0); 
printf("发送部分数据,调用close()强制关闭...\n");// 强制关闭:丢弃残留数据,发送RST
close(conn_fd);
printf("close()已返回,连接已强制关闭\n");
运行结果
// 服务器输出
客户端已连接:127.0.0.1:54322
发送部分数据,调用close()强制关闭...
close()已返回,连接已强制关闭// 客户端输出
收到服务器数据:Hello, Cli
接收数据失败(errno=104: Connection reset by peer)

结果说明:客户端仅收到部分数据,随后收到RST报文,连接被强制重置(Connection reset by peer),服务器端残留数据被丢弃。

3.3 示例3:延迟关闭(l_onoff=1,l_linger=5)

该示例展示SO_LINGER开启且滞留时间为5秒时,close阻塞等待数据发送的行为。

// 服务器端关键代码(修改SO_LINGER配置)
struct linger ling = {1, 5}; // 滞留5秒
setsockopt(conn_fd, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling));// 发送大量数据,确保内核需要时间发送
char large_data[4096];
memset(large_data, 'A', sizeof(large_data));
send(conn_fd, large_data, sizeof(large_data), 0);
printf("发送大量数据,调用close()阻塞等待...\n");// 阻塞等待5秒:若数据发送完成则正常关闭,否则强制关闭
close(conn_fd);
printf("close()已返回,连接关闭完成\n");
运行结果(正常情况:数据5秒内发送完成)
// 服务器输出
客户端已连接:127.0.0.1:54323
发送大量数据,调用close()阻塞等待...
(等待约1秒后)
close()已返回,连接关闭完成// 客户端输出
收到服务器数据:AAAAA...(4096个A)
服务器正常关闭连接(四次挥手)
运行结果(超时情况:数据5秒内未发送完成)
// 服务器输出(模拟网络延迟,客户端未及时接收)
客户端已连接:127.0.0.1:54324
发送大量数据,调用close()阻塞等待...
(等待5秒后)
close()已返回,连接关闭完成// 客户端输出(因网络延迟,仅收到部分数据)
收到服务器数据:AAAAA...(2048个A)
接收数据失败(errno=104: Connection reset by peer)

结果说明:当数据在滞留时间内发送完成,连接正常关闭;若超时未完成,内核丢弃残留数据并强制关闭连接。

4. SO_LINGER选项的行为可视化

为了更直观地理解SO_LINGER三种配置的差异,我们通过JavaScript绘制时序图,展示close关闭连接的过程。

4.1 SO_LINGER三种配置的时序对比图

5. SO_LINGER的使用注意事项

5.1 避免滥用强制关闭(l_linger=0)

强制关闭(发送RST)会导致:

  • 对端无法区分“正常关闭”和“异常重置”,可能误判网络故障;
  • 残留数据丢失,破坏TCP的可靠传输特性;
  • 可能引发“半打开连接”(对端仍认为连接有效,但服务器已关闭)。

建议仅在检测到恶意连接、超时等异常场景使用强制关闭。

5.2 合理设置滞留时间(l_linger>0)

滞留时间并非越长越好:

  • 过短(如1秒):可能导致正常数据发送不完整;
  • 过长(如30秒):close阻塞时间过长,影响服务器并发性能。

建议根据业务场景估算数据发送时间(如基于带宽和数据量),设置2~10秒的滞留时间。

5.3 结合非阻塞socket使用

当socket为非阻塞模式时,l_onoff=1l_linger>0的配置不会导致close阻塞,而是立即返回。此时需通过errno判断数据是否发送成功:

// 非阻塞socket + SO_LINGER示例
int ret = close(conn_fd);
if (ret == 0) {printf("数据已发送完成,连接正常关闭\n");
} else if (errno == EWOULDBLOCK) {printf("数据未发送完成,已超时丢弃\n");
}

6. 总结

SO_LINGER选项为开发者提供了对TCP连接关闭行为的精确控制,核心是通过l_onoffl_linger的组合实现三种关闭策略:

  1. 默认策略(l_onoff=0):安全优先,适用于大多数需要可靠传输的场景;
  2. 强制策略(l_onoff=1,l_linger=0):效率优先,适用于异常场景下的快速资源回收;
  3. 延迟策略(l_onoff=1,l_linger>0):平衡安全与效率,适用于需要等待数据发送但避免无限阻塞的场景。

在实际开发中,需根据业务需求选择合适的策略,避免因滥用SO_LINGER导致数据丢失或性能问题。

http://www.dtcms.com/a/506706.html

相关文章:

  • 【Docusaurus】子路径配置后,build 报错 Error: Unable to build website for locale en
  • wordpress 网站变慢深圳龙华邮政编码是多少
  • 【服务器】服务器被攻击植入了挖矿病毒,CPU一直占用100%,@monthly /root/.cfg/./dealer病毒清除
  • DevExpress WinForms v25.1亮点 - 电子表格组件、富文档编辑器全新升级
  • SVN冲突处理相关,标识 C 语言源文件(.c)的不同版本或冲突状态
  • 《掰开揉碎讲编程-长篇》重生之哈希表易如放掌
  • 【Python】绘制椭圆眼睛跟随鼠标交互算法配图详解
  • 【C++模版进阶】如何理解非类型模版参数、特化与分离编译?
  • 字符串专题总结:从模拟运算到模板掌握
  • 【Java链表】从概念结构到单向链表创建,增删查改全流程实战
  • 从C10K到Reactor:事件驱动,如何重塑高并发服务器的网络架构
  • 顺义做网站公司重庆企业网络推广软件
  • 淘宝怎么做网站郑州网站开发公
  • input + React自定义上传组件【可自定义拓展】
  • 「日拱一码」125 多层特征融合
  • 第六部分:VTK进阶(第164章 复合数据集 vtkMultiBlockDataSet 组织)
  • k8s(十一)HPA部署与使用
  • 【ReaLM】结合错误数据与课程学习 提升垂域效果
  • 通了网站建设宿迁网站定制
  • Git仓库推送到GitHub
  • 本地多语言切换具体操作代码
  • 济南建设主管部门网站短视频网站如何做推广
  • AWS US-East-1 区宕机
  • C语言——关机小程序(有system()和strcmp()函数的知识点)
  • php网站案例购物网页设计图片
  • golang面经7:interface相关
  • [Agent可视化] 配置系统 | 实现AI模型切换 | 热重载机制 | fsnotify库(go)
  • 【第7篇】引入低配大模型
  • 【Linux】Linux 进程信号核心拆解:pending/block/handler 三张表 + signal/alarm 实战
  • Java-154 深入浅出 MongoDB 用Java访问 MongoDB 数据库 从环境搭建到CRUD完整示例