# netdemo **Repository Path**: UEFIStart/netdemo ## Basic Information - **Project Name**: netdemo - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-10-15 - **Last Updated**: 2026-10-15 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # netdemo —— 把从 socket() 到 accept() 的内核行为跑给你看 一套自包含的 C 程序,用**可观察的输出**验证前面讨论过的每个结论:匿名 inode、 bind 与 listen 的区别、两个队列、accept 只是"取件"、connect 的隐式 bind。 ## 文件 | 文件 | 作用 | |---|---| | `common.h` | 读内核状态的工具:`/proc/self/fd/N` 取 inode、反查 `/proc/net/tcp`、读 `ss` 的 Recv-Q | | `server.c` | 服务端:socket → bind → listen → accept 四步逐步打印内核状态 | | `client.c` | 客户端:演示 connect 前"没有地址"、connect 的内核五步、隐式 bind | | `demo.sh` | 一键场景:1 服务端 + 3 客户端同时连,服务端故意延迟 accept | | `Makefile` | `make` 编译,`make demo` 跑演示 | ## 运行 ```bash make # 编译(-Wall -Wextra 零警告) ./demo.sh # 完整演示(默认端口 18080,约 7 秒) ./demo.sh 18080 3 2000 # 端口 / backlog / 每次 accept 前的延迟毫秒 # 单独用 ./server 0 3 2000 3 # 端口 0 = 让内核分配临时端口 ./client 127.0.0.1 18080 # 常规客户端(不 bind) ./client 127.0.0.1 18080 60083 # 显式指定源端口 60083 ``` ## 知识点 → 代码 → 观察到的现象 | 讨论过的结论 | 代码位置 | 运行时的现象 | |---|---|---| | socket 是"匿名 inode 上的 file 对象" | `common.h: fd_link/fd_inode` | `/proc/self/fd/3 -> socket:[11363]` | | `bind()` 只登记端口,**不进**连接查找表 | `server.c` 第 2 步 | 输出"已验证:inode=11363 不在 /proc/net/tcp 中" | | `listen()` 才挂进查找表并初始化两个队列 | `server.c` 第 3 步 | listen 后同 inode 出现在表里,`state=LISTEN(0A)` | | backlog 会被 `somaxconn` 截断 | `server.c` 第 3 步 | 打印 `/proc/sys/net/core/somaxconn` | | 三次握手由内核完成,与 `accept()` 无关 | `server.c` 第 4 步延迟段 | 应用没 accept,`ss -ltn` 的 `Recv-Q=3`,三条 ESTAB | | 内核已有连接但应用拿不到 → **inode=0** | `proc_net_tcp_dump_estab()` | `127.0.0.1:18080 <-> 127.0.0.1:59514 inode=0` | | 数据在 accept 之前就进了子 socket 缓冲区 | 延迟段的 `ss -tn` | `ESTAB Recv-Q=41` | | `accept()` 返回**新 fd**(不是监听 fd) | `server.c` 第 4 步 | `fd=3`(监听)→ `fd=4`(连接) | | accept 只是补 file/inode 外壳,四元组不变 | 前后两次反查 | 同一对端地址,`inode` 从 `0` 变成 `11387` | | 对端地址来自 SYN 包头,不是客户端"告知" | `dump_names("新连接")` | 与客户端 `getsockname()` 完全一致 | | connect 之前客户端**根本没有地址** | `client.c` 第 2 步 | `[connect 前] 本地=0.0.0.0:0` | | connect 内含隐式 bind(路由定 IP + 哈希挑端口) | `client.c` 第 4 步 | `[connect 后] 本地=127.0.0.1:50248` | | 客户端显式 bind 才需要特殊理由 | `client.c` 第 3 步 | `./client ... 60083` → 源端口就是 60083 | | 没人 listen 时立刻 `Connection refused` | `client.c` 第 4 步 | `connect 失败: Connection refused` | | accept 队列是"取走即腾位",不是连接数上限 | `server.c` 结尾 | 取完后 `Recv-Q=0`,连接仍活着 | ## 观察要点(demo.sh 中最值得看的两处) ``` ### 观察点 2:3 个客户端同时 connect(服务端此时还没 accept) LISTEN 3 3 0.0.0.0:18080 ^ Recv-Q=3 = 等候区里已坐满 ESTAB 41 0 127.0.0.1:18080 127.0.0.1:59514 ^ Recv-Q=41 = 客户端发的 41 字节早就躺在子 socket 缓冲区里了 ``` ``` (b) 已完成三次握手的连接(/proc/net/tcp 视角): 127.0.0.1:18080 <-> 127.0.0.1:59514 inode=0 <- 内核已有连接,应用还拿不到 ... >>> accept() 返回 fd=4 -> socket:[11387] 同一条连接在内核表里的样子: 127.0.0.1:18080 <-> 127.0.0.1:59514 state=ESTABLISHED(01) inode=11387 ``` **同一对端地址、同一个四元组,inode 从 0 变成 11387** —— 这就是 "accept 不参与握手,只是给已经存在的连接发一个 fd 手柄"的最直接证据。 ## 依赖 只需要 gcc 和 iproute2 的 `ss`(读 accept 队列深度用;没有 `ss` 时相关输出显示为空, 其余功能不受影响)。