实用指南:Linux 如何创建和计数套接字

news/2025/10/22 11:18:18/文章来源:https://www.cnblogs.com/lxjshuju/p/19157524

大家好!我是大聪明-PLUS

如果您有使用 Web 服务器的经验,您可能遇到过典型的“地址已被使用”(EADDRINUSE)情况。

本文不仅会详细介绍判断这种情况是否会在不久的将来发生的先决条件(为此,查看打开的套接字列表就足够了),而且还会解释如何跟踪内核中的特定代码路径(发生此类检查的地方)。

如果您只是好奇 socket(2)系统调用究竟如何工作以及所有这些套接字究竟存储在哪里,那么请务必阅读本文直到最后!

❯ socket有什么用处?

套接字是一种用于在不同机器上运行的进程之间进行通信的结构,这种通信通过网络进行,而网络是所有这些进程的底层网络。套接字有时也用于在同一主机上运行的进程之间进行通信(在本例中,我们指的是 Unix 套接字)。

《计算机网络:自上而下的方法》一书中给出了一个非常准确的类比,它阐明了套接字的本质,给我留下了深刻的印象。

从  一般的意义上讲,你可以把计算机想象成一座有很多门的“房子”。

在这里,每一扇门都是一个插座,顾客一靠近,就可以“敲”一下。

敲门后(发送数据包 SYN),房屋会立即自动发出响应(SYN+ACK),然后进行自我认证(是的,这就是带有“智能门”的智能房屋)。

与此同时,虽然这个过程本身只是在家里进行,但“智能家居”本身会协调客户的工作并创建两个队列:一个队列用于仍在与房屋交换问候的人,另一个队列用于已经完成问候阶段的人。

一旦这些顾客进入第二队列,流程就会让他们进去。

一旦连接被接受(客户端被告知加入),服务器就可以与客户端通信,根据需要发送和接收数据。

这里值得注意的是,客户端实际上“被禁止”进入房子。服务器在房子里创建了一个“私人门”(客户端套接字),然后通过它与客户端进行通信。

如果您逐步了解如何使用 C 实现 TCP 服务器,那么本文将更容易理解。如果您还不熟悉这个主题,请务必阅读文章“实现 TCP 服务器”。

❯ 我可以在哪里找到我的系统上可用的套接字列表?

一旦你很好地理解了 TCP 连接的建立方式,我们就可以“进入房子”,探索机器是如何创建这些“门”(套接字)的。我们还会了解房子里有多少扇门,以及每扇门的状态(关闭还是打开)。

为了做到这一点,让我们以一个仅创建套接字(门!)但不执行任何操作的服务器为例。

// socket.c
#include 
#include 
int main(int argc, char** argv)
{int listen_fd = socket(AF_INET, SOCK_STREAM, 0);if (err == -1) {perror("socket");return err;}sleep(3600);return 0;
}

在底层,这样一个简单的系统调用运行了一大堆内部方法(下一节将详细介绍),最终将允许我们查找有关写入三个不同文件的活动套接字的信息:  /proc/<pid>/net/tcp、 /proc/<pid>/fd和 /proc/<pid>/net/sockstat

该目录 fd 列出了进程打开的文件,而文件本身则/proc/<pid>/net/tcp 报告了与该进程的网络命名空间关联的当前活动的 TCP 连接(处于各种状态)。或者,该文件 sockstat也可以被视为一种摘要。

从目录 fd 开始,可以注意到,调用之后, socket(2) 套接字文件描述符出现在类似描述符的列表中:

./socket.out &
[2] 21113
ls -lah /proc/21113/fd
dr-x------ 2 ubuntu ubuntu  0 Oct 16 12:27 .
dr-xr-xr-x 9 ubuntu ubuntu  0 Oct 16 12:27 ..
lrwx------ 1 ubuntu ubuntu 64 Oct 16 12:27 0 -> /dev/pts/0
lrwx------ 1 ubuntu ubuntu 64 Oct 16 12:27 1 -> /dev/pts/0
lrwx------ 1 ubuntu ubuntu 64 Oct 16 12:27 2 -> /dev/pts/0
lrwx------ 1 ubuntu ubuntu 64 Oct 16 12:27 3 -> 'socket:[301666]'

考虑到 socket(2) 简单的调用不会建立任何 TCP 连接,我们不会从中找到或收集任何重要信息/proc/<pid>/net/tcp

从摘要(sockstat)中,我们可以猜测分配的 TCP 套接字数量正在逐渐增加:

cat /proc/21424/net/sockstat
sockets: used 296
TCP: inuse 3 orphan 0 tw 4 alloc 106 mem 1
UDP: inuse 1 mem 0
UDPLITE: inuse 0
RAW: inuse 0
FRAG: inuse 0 memory 0

为了确保这个数字在我们工作时确实会增加 alloc ,让我们修改上面的代码并尝试一次分配 100 个套接字:

+ for (int i = 0; i < 100; i++) {int listen_fd = socket(AF_INET, SOCK_STREAM, 0);if (err == -1) {perror("socket");return err;}
+ }

现在,通过再次检查此参数,让我们确保分配数确实增加了:

cat /proc/21456/net/sockstatbigger than before!|
sockets: used 296          .----------.
TCP: inuse 3 orphan 0 tw 4 | alloc 207| mem 1
UDP: inuse 1 mem 0         *----------*
UDPLITE: inuse 0
RAW: inuse 0
FRAG: inuse 0 memory 0

❯ 当执行套接字系统调用时,底层究竟发生了什么?

socket(2) 就像一个工厂,生产用于处理此类套接字上的操作的基本结构。

使用 iovisor/bcc,您可以最大深度地跟踪 sys_socket 堆栈中发生的所有调用,并根据这些信息了解每个步骤。

|  socket()
|--------------- (kernel boundary)
|  sys_socket
|       (socket, type, protocol)
|  sock_create
|       (family, type, protocol, res)
|  __sock_create
|       (net, family, type, protocol, res, kern)
|  sock_alloc
|       ()
˘

从 sys_socket 本身开始,这个系统调用包装器是内核空间中接触到的第一层。在这一层,系统调用会执行各种检查,并准备一些标志以供后续调用使用。

一旦执行了所有初步检查,调用就会struct socket在其自己的堆栈上分配一个指向结构的指针,该结构包含有关套接字的非协议细节:

SYSCALL_DEFINE3(socket,int, family,int, type,int, protocol)
{struct socket *sock;int retval, flags;retval = sock_create(family, type, protocol, &sock);if (retval < 0)return retval;return sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
}
struct socket {socket_state            state;short                   type;unsigned long           flags;struct sock*            sk;const struct proto_ops* ops;struct file*            file;// ...
};

鉴于我们目前正在创建一个套接字,并且可以从各种协议类型和协议族(例如 UDP、UNIX 和 TCP)中进行选择,因此 struct socket 该套接字包含一个接口 ( struct proto_ops*),该接口定义了套接字实现的基本结构。这些结构与类型和协议族无关,此操作通过调用以下方法启动:  sock_create

int __sock_create(struct net *net,int family, int type, int protocol,struct socket **res, int kern)
{int err;struct socket *sock;const struct net_proto_family *pf;if (family < 0 || family >= NPROTO)return -EAFNOSUPPORT;if (type < 0 || type >= SOCK_MAX)return -EINVAL;err = security_socket_create(family, type, protocol, kern);if (err)return err;sock = sock_alloc();if (!sock) {net_warn_ratelimited("socket: no more sockets\n");return -ENFILE;}sock->type = type;pf = rcu_dereference(net_families[family]);err = -EAFNOSUPPORT;if (!pf)goto out_release;err = pf->create(net, sock, protocol, kern);if (err < 0)goto out_module_put;// ...
}

继续这项详细的研究,让我们仔细看看如何struct socket 使用该方法 提取结构sock_alloc()

❯此方法的目的是分配两个实体:一个新的 inode 和一个套接字对象。

它们在文件系统级别上连接 sockfs,这不仅负责跟踪整个系统的套接字信息,而且还提供了一个转换层,通过该转换层,正常的文件系统调用(例如 write(2))和网络堆栈(无论这种通信发生在哪个底层域)都可以进行通信。

sock_alloc_inode通过监视负责分配索引描述符的 方法的操作 sockfs,我们可以观察到整个过程是如何组织的:

trace -K sock_alloc_inode
22384   22384   socket-create.out      sock_alloc_inodesock_alloc_inode+0x1 [kernel]new_inode_pseudo+0x11 [kernel]sock_alloc+0x1c [kernel]__sock_create+0x80 [kernel]sys_socket+0x55 [kernel]do_syscall_64+0x73 [kernel]entry_SYSCALL_64_after_hwframe+0x3d [kernel]
struct socket *sock_alloc(void)
{struct inode *inode;struct socket *sock;inode = new_inode_pseudo(sock_mnt->mnt_sb);if (!inode)return NULL;sock = SOCKET_I(inode);inode->i_ino = get_next_ino();inode->i_mode = S_IFSOCK | S_IRWXUGO;inode->i_uid = current_fsuid();inode->i_gid = current_fsgid();inode->i_op = &sockfs_inode_ops;this_cpu_add(sockets_in_use, 1);return sock;
}
static struct inode *sock_alloc_inode(struct super_block *sb)
{struct socket_alloc *ei;struct socket_wq *wq;ei = kmem_cache_alloc(sock_inode_cachep, GFP_KERNEL);if (!ei)return NULL;wq = kmalloc(sizeof(*wq), GFP_KERNEL);if (!wq) {kmem_cache_free(sock_inode_cachep, ei);return NULL;}ei->socket.state = SS_UNCONNECTED;ei->socket.flags = 0;ei->socket.ops = NULL;ei->socket.sk = NULL;ei->socket.file = NULL;return &ei->vfs_inode;
}

❯ 套接字和资源限制

假设可以使用文件描述符从用户空间引用文件系统 inode,则会出现以下情况:在我们设置了所有基本内核结构之后,它会为用户生成一个文件描述符。

如果您曾经想过为什么socket(2)在使用时可能会出现“打开太多文件”错误,这全都归因于这些资源限制检查:

static int
sock_map_fd(struct socket* sock, int flags)
{struct file* newfile;int          fd = get_unused_fd_flags(flags);if (unlikely(fd < 0)) {sock_release(sock);return fd;}newfile = sock_alloc_file(sock, flags, NULL);if (likely(!IS_ERR(newfile))) {fd_install(fd, newfile);return fd;}put_unused_fd(fd);return PTR_ERR(newfile);
}

❯ 计算系统中的socket数量

如果您一直密切关注 sock_alloc调用的作用,我想指出它实际上增加了当前“正在使用”的套接字的数量。

struct socket *sock_alloc(void)
{struct inode *inode;struct socket *sock;// ....this_cpu_add(sockets_in_use, 1);return sock;
}

由于 this_cpu_add 是一个宏,您可以查看其定义以了解有关它的更多信息:

现在,考虑到我们不断向 中添加套接字 sockets_in_use,我们至少可以假设为/proc/net/sockstat注册的方法 将使用这个值——事实上,情况确实如此。这也意味着我们将把每个 CPU 核心上注册的所有值加起来:

static int sockstat_seq_show(struct seq_file *seq, void *v)
{struct net *net = seq->private;unsigned int frag_mem;int orphans, sockets;orphans = percpu_counter_sum_positive(&tcp_orphan_count);sockets = proto_sockets_allocated_sum_positive(&tcp_prot);socket_seq_show(seq);seq_printf(seq, "TCP: inuse %d orphan %d tw %d alloc %d mem %ld\n",sock_prot_inuse_get(net, &tcp_prot), orphans,atomic_read(&net->ipv4.tcp_death_row.tw_count), sockets,proto_memory_allocated(&tcp_prot));// ...seq_printf(seq,  "FRAG: inuse %u memory %u\n", !!frag_mem, frag_mem);return 0;
}

❯ 那么命名空间呢?

您可能已经注意到,与命名空间相关的代码缺少任何逻辑来计算当前分配了多少个套接字。

这一点最初让我非常惊讶,因为我以为命名空间在网络栈中使用最为频繁。但事实证明也有例外。

这里的要点是:您可以创建一组套接字,查看 sockstat,然后创建一个网络命名空间,进入它,然后发现虽然我们没有一次看到来自整个系统的 TCP 套接字(这就是命名空间分隔的工作方式!),但我们仍然可以看到系统中分配的套接字总数(就好像没有命名空间一样)。

./sockets.out
cat /proc/net/sockstat
sockets: used 296
TCP: inuse 5 orphan 0 tw 2 alloc 108 mem 3
UDP: inuse 1 mem 0
UDPLITE: inuse 0
RAW: inuse 0
FRAG: inuse 0 memory 0
ip netns add namespace1
ip netns exec namespace1 /bin/bash
TCP: inuse 0 orphan 0 tw 0 alloc 108 mem 3
UDP: inuse 0 mem 0
UDPLITE: inuse 0
RAW: inuse 0
FRAG: inuse 0 memory 0

❯ 综上所述

回顾我所取得的成就,真是妙趣横生。我深入研究内核的内部原理,仅仅是因为我对它的工作原理感到好奇/proc。最终,我找到了一些答案,帮助我理解日常工作中遇到的特定函数的行为。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/943201.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

(web cad drawing)Web CAD SDK Integration Method

Preface We have created an online CAD project based on mxcad, which includes various CAD functions such as previewing, editing drawings, and operating the drawing database. After user integration, it s…

记一次 .NET 某药品缺陷高速检测系统 卡慢分析

一:背景 1. 讲故事 上个月有位朋友找到我,说他们公司的程序当内存达到一定阈值(2g+)之后,业务逻辑明显变慢导致下位机超时报警,想让我看下到底怎么回事,这种问题其实抓dump比较难搞,但朋友也说了有一个增长阈值,…

0254-CLAP-参数默认值

环境Time 2022-12-02 WSL-Ubuntu 22.04 CLAP 4.0.29前言 说明 参考:https://docs.rs/clap/latest/clap/index.html 目标 如果没有提供参数,使用默认值。 Cargo.toml [package] edition = "2021" name = &q…

得物火山引擎:Data Agent驱动财务管理智能升级

作为新一代品质生活购物社区,得物 App 以正品电商和品质生活社区作为两大核心服务,成立十年来,得物 App 始终致力于帮助用户得到美好生活,90后用户占比超9成,已成为年轻用户重要的品质生活购物平台。随着 AI 时代…

WPF/C#:使用Stylet中的IWindowManager用于显示等待窗体、对话框与消息框

前言 显示等待框意义 在创建WPF应用的时候,如果我们要执行一个耗时的操作,那么给用户显示一个等待窗体是很常见的需求,通过显示一个等待窗体让用户明白运行的这个软件并没有崩溃,能有效消除用户的焦虑与不确定性,…

2025年钢花钢管厂家最新行业资讯推荐,注浆钢管/超前小导钢管/袖阀钢管/地质钢管/管棚钢管/岩心钢管/基建与矿业升级驱动需求,高品质钢管如何选?最新实力厂商推荐榜发布

随着国家在基础设施建设、能源勘探及矿山开发等领域的持续投入,2025年市场对高品质、高规格钢管的需求预计将进一步提升。尤其在隧道支护、地质加固、精密设备制造等场景中,钢管的材质、精度与耐久性直接关系到工程安…

训练常用

1 利用 Screen 保持 VSCode 连接远程任务持续运行 1.1 远程连接 在 Linux 上使用 screen 是一种保持进程持续运行的便捷方式,即使用户断开 SSH 连接,进程也不会中断。连接远程服务器 首先使用 VSCode 或者 PyCharm 连…

《Vuejs设计与实现》第 18 章(同构渲染)(上) - 详解

《Vuejs设计与实现》第 18 章(同构渲染)(上) - 详解pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas&…

配置git

1 从项目中去掉现有的 Git 信息,并重新建立新的 Git 仓库 删除原有的 Git 信息:打开命令行工具,进入项目所在的目录。 执行以下命令以删除 .git 文件夹,这将移除所有的 Git 版本控制信息:对于 Linux 和 macOS: rm…

0253-CLAP-统计参数出现次数

环境Time 2022-12-02 WSL-Ubuntu 22.04 CLAP 4.0.29前言 说明 参考:https://docs.rs/clap/latest/clap/index.html 目标 统计参数出现的次数。 Cargo.toml [package] edition = "2021" name = "game&q…

什么情况下有必要使用抽象基类ABC?

在面向对象编程中,抽象基类(Abstract Base Class,简称ABC) 是一种不能被实例化的特殊类,其主要作用是定义一组子类必须实现的接口(方法或属性),从而强制子类遵循统一的规范。 抽象基类的核心特点:不能实例化:…

实用指南:TensorFlow2 Python深度学习 - 深度学习概述

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

HTTP/2协议漏洞引发史上最大DDoS攻击——Rapid Reset技术深度解析

本文深度解析基于HTTP/2协议CVE-2023-44487漏洞的Rapid Reset DDoS攻击技术细节。该攻击峰值达每秒3.98亿请求,仅用2万台机器就打破历史记录,文章还探讨了TCP连接终止等防御方案。史上最大DDoS攻击:Rapid Reset技术…

因果机器学习模型实战测试与比较

本文通过实际案例对比传统机器学习模型与专门设计的因果机器学习模型在效果评估上的差异,探讨了因果ML如何弥补预测性模型的局限性,并介绍了PyWhy和因果森林等工具的应用场景。因果机器学习模型实战测试与比较 因果机…

Berry.Live:开箱即用的.NET直播流媒体服务器

🚀 Berry.Live:开箱即用的.NET直播流媒体服务器想要快速搭建自己的直播平台?厌倦了复杂的流媒体服务器配置?Berry.Live 为你提供了一个简单、强大、开源的解决方案!🎯 什么是 Berry.Live? Berry.Live 是一个基…

Vscode误删文件如何恢复(二)?

如果是刚刚删除的,那么可以打开Source Control, 看到changes里面有刚刚删除的文件,拓宽视界窗口,可以看到文件后面有三个图标,选中第二个,即Discard Changes, 弹出提示框,询问你是否恢复该文件,点击Restore F…

01-C程序设计语言-第2版-第1章导言笔记

一、入门 1、编写的第一个程序:打印出“hello, world”点击查看代码 #include <stdio.h> //包含标准库信息 int main() //定义名为main函数,没有参数值 {printf("hello, world\n"); //显示字符re…

0252-CLAP-标记类型的参数

环境Time 2022-12-02 WSL-Ubuntu 22.04 CLAP 4.0.29前言 说明 参考:https://docs.rs/clap/latest/clap/index.html 目标 使用标记类型的参数。 Cargo.toml [package] edition = "2021" name = "game&q…

中国企业DevOps工具链选型标准深度解析:云原生与开源生态的博弈

中国企业DevOps工具链选型标准深度解析:云原生与开源生态的博弈 在数字化转型浪潮席卷各行各业之际,DevOps工具链的选择已成为中国企业技术战略中的关键决策。随着国内企业对于自主可控需求的日益增长,DevOps工具的…

AI智能外呼系统的工作原理解析

在很多企业看来,AI智能外呼系统已经成为销售线索跟进、客户回访、通知提醒等环节中不可或缺的工具。但在真正投入使用前,企业往往会产生疑问:AI外呼系统究竟是怎么“智能”的?它与传统自动拨号器或人工外呼有何不同…