Skip to content

第一百二十章:内河

问道期

涉及内核源码:

林小源在海边走了很久,忽然发现脚下有一条不起眼的河流。河流很窄,两岸是整齐的石壁,河水清澈见底,流速极快。

"这条河……不通向大海?"他蹲下身,用手掬了一捧水。

"不通。"一个平静的声音从河对岸传来。

林小源抬头,看到一个穿着朴素的中年人站在对岸。他的衣服上没有任何复杂的标记,只有简单的管道图案。他手中拿着两端各连着一个篮子的绳索。

"我是 Unix domain socket。"中年人说,"这条河只在本地流——不经过网络协议栈,不经过路由器,不经过网卡。数据直接在内核中复制,从一个进程的地址空间到另一个进程的地址空间。"

"所以比网络 socket 快?"

"快得多。"中年人说,"网络 socket 要经过 TCP/IP 协议栈——封装、路由、校验,每一步都有开销。我只需要一次内核复制。没有网络延迟,没有协议开销。"

林小源看着清澈的河水,注意到河底铺着文件路径——/tmp/my.sock/var/run/docker.sock/run/systemd/private

"你用文件系统路径做地址?"

"对。struct sockaddr_un 里的 。"中年人说,"文件系统权限控制访问——只有有权限的进程才能连接我的 socket。这比网络 socket 的 IP 加端口更安全。"

"你有哪些类型?"林小源问。

中年人放下手中的绳索:"三种。——流式,类似 TCP,有序、可靠。——数据报,类似 UDP,无连接。——有序数据报,保持消息边界。"

"听起来和网络 socket 差不多。"

"接口一样。"中年人说,"bind()listen()accept()connect()send()recv()——同样的系统调用,同样的编程模型。区别只在地址族:网络用 ,我用 。"

"那管道呢?管道也是本机通信。"

中年人笑了:"管道是单向的——一端读,一端写。而且管道只能在有亲缘关系的进程间使用——父子进程。我是双向的,任意进程都能连接。你可以用一个 Unix socket 同时读写,管道做不到。"

林小源想到了什么:"Docker 用的就是你?"

"/var/run/docker.sock。"中年人点头,"Docker daemon 监听这个 socket,客户端通过它发送命令。MySQL 也用我——/var/run/mysqld/mysqld.sock。systemd 也用我——/run/systemd/private。"

"所以你是本机进程间通信的主力。"

"不是唯一的方式,但可能是最常用的方式。"中年人说,"共享内存更快,但编程复杂。管道更简单,但功能有限。我在速度和功能之间找到了平衡。"

林小源注意到河流的水流虽然快,但非常平稳——没有波浪,没有漩涡。

"你的数据不会乱序?"

"不会。 保证有序——数据按发送顺序到达。而且没有网络抖动——本机通信的延迟几乎为零,稳定得像一条直线。"

"那你有没有缺点?"

中年人沉思了一会儿:"我不能跨机器通信。这是最大的限制——如果你需要和远程服务器通信,还是得用网络 socket。但如果你在同一台机器上的进程间通信,我比任何网络方案都快。"

"还有呢?"

"文件系统的开销。"中年人说,"每次创建 Unix socket 都要在文件系统上创建一个节点。如果频繁创建销毁,文件系统会有压力。不过这不是大问题——用 abstract socket(路径以 \0 开头)可以避免文件系统操作。"

林小源站起身,看着这条安静的内河。它不像大海那样波澜壮阔,但在这狭窄的河道里,数据以最快的速度流动着——没有协议开销,没有网络延迟,只有纯粹的内核复制。

c
/*
 * Unix domain socket:
 *
 * 1. 类型
 *    SOCK_STREAM — 流式(类似 TCP)
 *    SOCK_DGRAM — 数据报(类似 UDP)
 *    SOCK_SEQPACKET — 有序数据报
 *
 * 2. 地址
 *    文件系统路径(如 /tmp/my.sock)
 *    AF_UNIX 地址族
 *
 * 3. 优势
 *    不经过网络协议栈
 *    没有网络开销
 *    比网络 socket 快
 *
 * 4. 用途
 *    进程间通信(IPC)
 *    数据库连接(如 MySQL)
 *    系统服务(如 systemd)
 *
 * Unix domain socket vs 管道:
 *   管道: 单向,只能父子进程
 *   Unix socket: 双向,任意进程
 */

printf("=== Unix domain socket — 本机通信 ===\n\n");

printf("Unix domain socket:\n");
printf("  同一台机器上的进程间通信\n");
printf("  使用文件系统路径作为地址\n");
printf("  不经过网络协议栈\n\n");

printf("--- 类型 ---\n");
printf("SOCK_STREAM: 流式(类似 TCP)\n");
printf("SOCK_DGRAM: 数据报(类似 UDP)\n");
printf("SOCK_SEQPACKET: 有序数据报\n\n");

printf("--- 地址 ---\n");
printf("struct sockaddr_un {\n");
printf("  sun_family = AF_UNIX;\n");
printf("  sun_path = \"/tmp/my.sock\";\n");
printf("};\n\n");

printf("--- 使用方式 ---\n");
printf("服务器:\n");
printf("  socket(AF_UNIX, SOCK_STREAM, 0)\n");
printf("  bind(\"/tmp/my.sock\")\n");
printf("  listen()\n");
printf("  accept()\n\n");
printf("客户端:\n");
printf("  socket(AF_UNIX, SOCK_STREAM, 0)\n");
printf("  connect(\"/tmp/my.sock\")\n\n");

printf("--- 优势 ---\n");
printf("1. 速度: 不经过网络协议栈\n");
printf("2. 安全: 文件系统权限控制\n");
printf("3. 简单: 不需要 IP 和端口\n\n");

printf("--- Unix socket vs 管道 ---\n");
printf("管道:\n");
printf("  单向\n");
printf("  只能父子进程\n\n");
printf("Unix socket:\n");
printf("  双向\n");
printf("  任意进程\n\n");

printf("--- 用途 ---\n");
printf("MySQL: /var/run/mysqld/mysqld.sock\n");
printf("Docker: /var/run/docker.sock\n");
printf("systemd: /run/systemd/private\n");

道藏笔记

内核启示

Unix domain socket 是本机进程间通信的方式。

Unix domain socket 的特点:

  • 使用文件系统路径作为地址
  • 不经过网络协议栈
  • 比网络 socket 快

类型:

  • — 流式
  • — 数据报
  • — 有序数据报

优势:

  • 速度 — 不经过协议栈
  • 安全 — 文件系统权限控制
  • 简单 — 不需要 IP 和端口

Unix domain socket 是"内河"——比网络更快更安全。


破关试炼

Unix socket 之试

Unix socket 内河里,提供面向连接字节流语义的 socket 类型是什么?

答对后才能继续滑动和进入下一章。

以修仙之名,悟内核之道