首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

TCP之CLOSE_WAIT

编程知识
2024年09月15日 23:30

之所以会出现CLOSE_WAIT,是因为Linux实现TCP的设计缺陷。毕竟我们都不是那种走过TCP的发明到广泛使用的年代的人,这种错误确实是可能犯的。

close和shutdown

首先Linux关闭一个socket,有两个系统调用closeshutdown。之所以会这样,是因为TCP是由两个单向通道组成的双向通讯协议。close会关闭这两个通道,而shutdown可以选择关闭其中一个,或者两个都关闭。用一个表来分辨。

功能 shutdown close
关闭方式 可以选择关闭读端、写端或同时关闭 同时关闭读写端
发送FIN报文
释放资源 否,直到四次挥手完成 是,立即释放
发送缓冲区 如果关闭写端,则发送缓冲区中的数据会继续发送 如果shutdown函数没有关闭写端,close函数会强制关闭写端,将发送缓冲区中剩余的数据丢弃

而TCP在关闭读端的时候,是没有通知socket另一端的方法的,也就说,客户端关闭读端,调用shutdown(sock, SHUT_RD)的时候,只是在内核里标记了一个状态,不会告知服务端“你再也不能给我发数据了”。

复现CLOSE_WAIT

使用以下代码作为服务端,然后用nc localhost 12345,再对nc Ctrl+C关闭它。此时netstat -anp | grep 12345,你会发现至少有一个处于CLOSE_WAIT状态的通道。

#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define PORT 12345
#define BUFFER_SIZE 1024
#include <signal.h>

void sigpipe_handler(int sig) {
  printf("SIGPIPE received!\n");
}


int main() {
  // 在程序开始时设置信号处理函数
  signal(SIGPIPE, sigpipe_handler);
  int server_fd, client_fd;
  struct sockaddr_in server_addr, client_addr;
  socklen_t client_addr_len = sizeof(client_addr);
  char buffer[BUFFER_SIZE];

  // 创建 socket
  server_fd = socket(AF_INET, SOCK_STREAM, 0);
  if (server_fd < 0) {
    perror("socket");
    exit(EXIT_FAILURE);
  }

  // 绑定地址
  memset(&server_addr, 0, sizeof(server_addr));
  server_addr.sin_family = AF_INET;
  server_addr.sin_addr.s_addr = INADDR_ANY;
  server_addr.sin_port = htons(PORT);

  if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
    perror("bind");
    close(server_fd);
    exit(EXIT_FAILURE);
  }

  // 监听连接
  if (listen(server_fd, 1) < 0) {
    perror("listen");
    close(server_fd);
    exit(EXIT_FAILURE);
  }

  printf("Server is listening on port %d\n", PORT);

  // 接受连接
  client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_addr_len);
  if (client_fd < 0) {
    perror("accept");
    close(server_fd);
    exit(EXIT_FAILURE);
  }

  printf("Client connected, %d\n", client_fd);

  // 读取客户端数据(保持连接)
  while (1) {
    ssize_t bytes_received = recv(client_fd, buffer, BUFFER_SIZE - 1, 0);
    if (bytes_received < 0) {
      perror("recv");
      break;
    }
    sleep(1);

    buffer[bytes_received] = '\0';
    printf("Received: %ld, %s\n", bytes_received, buffer);
    // send(client_fd, "pong", 4, 0);
  }

  // 关闭连接(模拟 CLOSE_WAIT 状态)
  // 注意:不关闭 socket,将导致进入 CLOSE_WAIT 状态
  // close(client_fd);

  // 关闭 server socket
  close(server_fd);

  return 0;
}

避免CLOSE_WAIT问题

  1. 使用长时间未通讯自动关闭机制

这种方案在Redis中也有。cluster communicate bus就有这种方案的。

  1. send + sigpipe + close,虽然可行,但估计没人这么干。当个玩具自己玩玩还可以

进行了send,会触发sigpipe,让套接字处于不可用状态,但是你还是要手动close。这个图就是一个例子。看得出用这种方案确实会去掉两个通道,但是fd还是没有关闭。

image

  1. read + close

如果read返回0,那意味着你该close了。(估计没人会试着往read的size参数里写0吧……)

  1. 多路复用 + read + close

客户端close之后,服务端会触发可读事件。此时read这个socket,会返回0,这意味着服务端该close了。

  1. 非阻塞read + close

非阻塞read在没有数据的时候,会返回EAGAIN/EWOULDBLOCK,但是如果返回0,依然还是该close了。

不过都用上了多路复用了,应该没有人还会想着用非阻塞read吧……非阻塞write确实有必要,因为阻塞的write会等至少一个RTT,但read不会。

From:https://www.cnblogs.com/Afeather/p/18414896
本文地址: http://shuzixingkong.net/article/2056
0评论
提交 加载更多评论
其他文章 学习问题记录:RocketMQ集成到SpringBoot后,消费者无法自动进行消息消费。
情况说明 在SpringBoot中集成了RocketMQ,实践过程中,通过RocketMQ DashBoard观察,生产者可以正常将进行消息提交;通过日志及DashBoard观察,消费者成功在RocketMQ中进行了注册和订阅且观察到了消费者启动的日志行。问题是消费者依旧不会自动消费生产者提交的消息
Java获取Object中Value的方法
本文简要介绍了在Java中,获取对象(Object)中的值通常依赖于对象的类型以及我们希望访问的属性。由于Java是一种静态类型语言,直接从一个Object类型中访问属性是不可能的,因为Object是所有类的超类,但它本身不包含任何特定的属性或方法(除了那些定义在Object类中的)。本文简要介绍了
ubuntu莫名的 系统出现文件系统只读
运维记录 日期:2024年9月15日 问题描述: 在安装多个 LNMP 服务后,系统重启出现了问题。当尝试运行 apt update 命令时,系统出现了如下错误信息: 忽略:1 http://security.ubuntu.com/ubuntu jammy-security InRelease 忽略
AI老照片修复神器,Anole下载介绍
最近AI老照片修复上色,再一次火出圈,一些社交平台关于此话题内容流量满满,尤其是在小红书和抖音火的不得了,本期文章就来给大家分享下AI修复老照片的方式方法 本文主要介绍使用Anole修复老照片的方法,只需输入一张黑白或彩色照片,即可得到修复后的彩色结果,让往日的老照片坐上时光机重焕新生 Anole最
AI老照片修复神器,Anole下载介绍 AI老照片修复神器,Anole下载介绍 AI老照片修复神器,Anole下载介绍
Go runtime 调度器精讲(八):sysmon 线程和 goroutine 运行时间过长的抢占
原创文章,欢迎转载,转载请注明出处,谢谢。 0. 前言 在 Go runtime 调度器精讲(七):案例分析 一文我们介绍了一个抢占的案例。从案例分析抢占的实现,并未涉及到源码层面。本文将继续从源码入手,看 Go runtime 调度器是如何实现抢占逻辑的。 1. sysmon 线程 还记得 Go
Go runtime 调度器精讲(八):sysmon 线程和 goroutine 运行时间过长的抢占
C#/.NET/.NET Core技术前沿周刊 | 第 5 期(2024年9.9-9.15)
前言 C#/.NET/.NET Core技术前沿周刊,你的每周技术指南针!记录、追踪C#/.NET/.NET Core领域、生态的每周最新、最实用、最有价值的技术文章、社区动态、优质项目和学习资源等。让你时刻站在技术前沿,助力技术成长与视野拓宽。 欢迎投稿,推荐或自荐优质文章/项目/学习资源等。每周
C#/.NET/.NET Core技术前沿周刊 | 第 5 期(2024年9.9-9.15) C#/.NET/.NET Core技术前沿周刊 | 第 5 期(2024年9.9-9.15) C#/.NET/.NET Core技术前沿周刊 | 第 5 期(2024年9.9-9.15)
上周热点回顾(9.9-9.15)
热点随笔: &#183;&#160;秋天希望的田野,九月最后的救园:终身会员计划&#160;(博客园团队)&#183;&#160;41岁的大龄程序员,苟着苟着,要为以后做打算了&#160;(Tobin)&#183;&#160;关于.NET在中国为什么工资低的分析&#160;(迅捷网络[来送福利])&
Go runtime 调度器精讲(九):系统调用引起的抢占
原创文章,欢迎转载,转载请注明出处,谢谢。 0. 前言 第八讲介绍了当 goroutine 运行时间过长会被抢占的情况。这一讲继续看 goroutine 执行系统调用时间过长的抢占。 1. 系统调用时间过长的抢占 看下面的示例: func longSyscall() { timeout := sys
Go runtime 调度器精讲(九):系统调用引起的抢占