非常好!Unix Socket
是在 Linux / Unix 系统中进行**本地进程间通信(IPC)**的一种非常高效和灵活的方式,被广泛用于:
- 系统内部通信(如 systemd 与各服务)
- 数据库通信(如 PostgreSQL)
- 复杂 IPC 系统(比如 Nginx 和 FastCGI)
🧠 一、什么是 Unix Socket?
Unix Socket 是一种基于文件系统路径(如 /tmp/mysock
)来标识通信端点的本地 socket 通信机制,工作原理与 TCP/UDP 非常相似,但它:
特性 | Unix Socket | TCP/IP Socket |
---|---|---|
通信对象 | 同一台机器的进程 | 可跨主机 |
地址类型 | 文件路径 (/tmp/sock ) | IP地址 + 端口 |
性能 | 更高(不走网络协议栈) | 相对较低 |
常用于 | IPC 本地通信 | 网络服务 |
它实际上使用 socket API,但地址族是 AF_UNIX
(也叫 AF_LOCAL
),不是 AF_INET
。
🧱 二、基本结构和 API 调用流程
和网络 socket 很相似:
服务端流程:
- 创建 socket (
socket
) - 绑定路径地址 (
bind
) - 开始监听 (
listen
) - 接受连接 (
accept
) - 通信(
read
/write
) - 关闭 socket (
close
)
客户端流程:
- 创建 socket (
socket
) - 连接服务端路径 (
connect
) - 通信
- 关闭
🔧 三、最小 Unix Socket 通信示例
🔌 服务端:
// unix_server.cpp
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <iostream>
#include <cstring>int main() {int server_fd = socket(AF_UNIX, SOCK_STREAM, 0);if (server_fd < 0) { perror("socket"); return 1; }sockaddr_un addr{};addr.sun_family = AF_UNIX;strcpy(addr.sun_path, "/tmp/mysocket");unlink("/tmp/mysocket"); // 清理旧的 socket 文件if (bind(server_fd, (sockaddr*)&addr, sizeof(addr)) == -1) {perror("bind");return 1;}listen(server_fd, 5);std::cout << "[Server] Waiting for connections...\n";int client_fd = accept(server_fd, nullptr, nullptr);char buf[128] = {0};read(client_fd, buf, sizeof(buf));std::cout << "[Server] Received: " << buf << std::endl;const char *reply = "Hello from server";write(client_fd, reply, strlen(reply));close(client_fd);close(server_fd);unlink("/tmp/mysocket"); // 删除 socket 文件
}
🔌 客户端:
// unix_client.cpp
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <cstring>
#include <iostream>int main() {int sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);if (sock_fd < 0) { perror("socket"); return 1; }sockaddr_un addr{};addr.sun_family = AF_UNIX;strcpy(addr.sun_path, "/tmp/mysocket");if (connect(sock_fd, (sockaddr*)&addr, sizeof(addr)) == -1) {perror("connect");return 1;}const char *msg = "Hello server!";write(sock_fd, msg, strlen(msg));char buf[128] = {0};read(sock_fd, buf, sizeof(buf));std::cout << "[Client] Received: " << buf << std::endl;close(sock_fd);
}
✅ 编译方式:
g++ unix_server.cpp -o server
g++ unix_client.cpp -o client
运行:
./server # 一个终端
./client # 另一个终端
📦 四、Unix Socket 类型
类型 | 用法 | 说明 |
---|---|---|
SOCK_STREAM | 面向连接(类似 TCP) | 常用,支持 read / write |
SOCK_DGRAM | 无连接(类似 UDP) | 用 sendto / recvfrom |
示例里用的是 SOCK_STREAM
,最常见。
🧠 五、Unix Socket 的优势
- 速度快:不经过网络协议栈(相比 TCP 快 2~3 倍)
- 文件系统集成:用文件路径表示 socket,便于权限管理
- 支持多种通信模型:阻塞/非阻塞、epoll 事件模型等
- 无需监听端口:安全性高,防外网攻击
🛡️ 六、权限控制与安全性
因为是普通文件路径,所以:
- 可以使用
chmod
控制权限 - 例如只允许某些用户连接
/tmp/mysocket
- 系统服务也常这么做(如
/run/docker.sock
)
🧩 七、进阶玩法
你可以结合:
epoll
实现高并发 Unix socket 服务器sendmsg
/recvmsg
传递文件描述符(高级功能)- 使用 SO_PASSCRED 传递 UID/GID 验证身份
- 用
socketpair()
创建双向通道(比如守护进程与客户端通信)
✅ 总结一句话:
Unix Socket 是最适合本机进程间高效、可靠、灵活通信的机制,几乎所有高级 IPC 系统都在用它。
如果你想继续深入:
✅ 想不想我基于这个再帮你写一个 epoll + Unix socket
的并发服务 demo?
✅ 或者讲讲 socketpair()
/ sendmsg()
传 FD 的用法?
你说,我就写。
好!我们来详细讲讲两个非常实用的高级 IPC 技巧:
🧠 一、socketpair()
与 sendmsg()
传文件描述符(FD)
这两者分别解决了两个问题:
功能 | 技术 | 用途 |
---|---|---|
双向通信 | socketpair() | 类似管道,但支持双向 |
传递 FD | sendmsg() / recvmsg() | 把一个进程中的 fd 发送给另一个进程使用 |
🧱 二、socketpair()
—— 创建一对连接的 socket
就像一根“管道”,但是双向的、基于 socket 的,比 pipe()
更灵活。
🔧 使用方式
int sv[2];
socketpair(AF_UNIX, SOCK_STREAM, 0, sv);
// sv[0] 和 sv[1] 是两端的 socket fd,可以双向通信
通常用于:
- 父子进程通信
- 多线程通信(适用于
epoll
) - 模拟 TCP 连接通道但无需网络
🔄 三、进程间传递 FD 的思路
✅ 为什么要传 FD?
比如你用主进程监听连接,但你要把 连接 fd 交给另一个子进程去处理,就得“传过去”。
✅ 怎样传?
必须用 sendmsg()
/ recvmsg()
,用带外带外数据结构 cmsghdr
传递 SCM_RIGHTS
📦 四、传 FD 的完整 demo 示例
我们写一个程序:
- 父进程建立
socketpair
- 父进程打开一个文件,将文件的 fd 发送给子进程
- 子进程收到 fd,读取内容
🔹父子进程传递文件描述符示例:
// send_fd.cpp
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
#include <fcntl.h>
#include <iostream>
#include <cstring>void send_fd(int sock, int fd_to_send) {struct msghdr msg = {};struct iovec iov;char dummy = '*';iov.iov_base = &dummy;iov.iov_len = 1;msg.msg_iov = &iov;msg.msg_iovlen = 1;// 用于携带控制信息(FD)char cmsgbuf[CMSG_SPACE(sizeof(int))];msg.msg_control = cmsgbuf;msg.msg_controllen = sizeof(cmsgbuf);struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);cmsg->cmsg_level = SOL_SOCKET;cmsg->cmsg_type = SCM_RIGHTS; // 表示发送文件描述符cmsg->cmsg_len = CMSG_LEN(sizeof(int));*((int *) CMSG_DATA(cmsg)) = fd_to_send;if (sendmsg(sock, &msg, 0) < 0)perror("sendmsg");
}int recv_fd(int sock) {struct msghdr msg = {};struct iovec iov;char dummy;iov.iov_base = &dummy;iov.iov_len = 1;msg.msg_iov = &iov;msg.msg_iovlen = 1;char cmsgbuf[CMSG_SPACE(sizeof(int))];msg.msg_control = cmsgbuf;msg.msg_controllen = sizeof(cmsgbuf);if (recvmsg(sock, &msg, 0) < 0)perror("recvmsg");struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);int fd = -1;if (cmsg && cmsg->cmsg_level == SOL_SOCKET &&cmsg->cmsg_type == SCM_RIGHTS) {fd = *((int *) CMSG_DATA(cmsg));}return fd;
}int main() {int sv[2];if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1) {perror("socketpair");return 1;}pid_t pid = fork();if (pid == 0) {// 子进程:接收文件描述符close(sv[0]);int fd = recv_fd(sv[1]);std::cout << "[Child] Got fd: " << fd << std::endl;char buf[100] = {0};lseek(fd, 0, SEEK_SET); // 确保读到开头read(fd, buf, sizeof(buf));std::cout << "[Child] File content: " << buf << std::endl;close(fd);close(sv[1]);} else {// 父进程:发送文件描述符close(sv[1]);int fd = open("test.txt", O_RDONLY);if (fd < 0) { perror("open"); return 1; }send_fd(sv[0], fd);close(fd);close(sv[0]);}return 0;
}
✅ 运行步骤
- 准备一个
test.txt
文件:
echo "Hello FD World!" > test.txt
- 编译运行:
g++ send_fd.cpp -o send_fd
./send_fd
你将看到:
[Child] Got fd: 4
[Child] File content: Hello FD World!
🔍 五、总结
技术 | 用途 | 特点 |
---|---|---|
socketpair() | 本地双向通信通道 | 快速、无需监听、适合父子进程 |
sendmsg() / recvmsg() | 跨进程传文件描述符 | 内核传递,不依赖全局变量 |
✅ 衍生场景
- 多进程网络服务器:主进程
accept()
后把连接 fd 分发给子进程 - 守护进程管理:通过 FD 实现权限隔离和统一资源管理
- 父进程传 pipe、socket、eventfd 等给子进程
如果你想我可以写:
- ✅ 多进程 echo server,使用
socketpair
分发任务 - ✅ 多进程 epoll + FD 传递高并发处理模型
你想继续往哪边深入?我可以马上写代码+讲解。
非常好,我们来系统讲解一下在 Linux 下如何通过 Unix Domain Socket(UNIX 套接字) 实现进程间通信(IPC)。
🧭 目标说明
你有两个进程:
- Server 进程:接收数据
- Client 进程:发送数据
它们在 同一台机器的同一操作系统内核空间下运行,通过一个“socket 文件”进行通信。这个 socket 文件存在于文件系统中,例如 /tmp/mysock
。
🧱 方式选择:Unix Socket 是 IPC 中的高效方式
通信方式 | 适合场景 | 优缺点 |
---|---|---|
Pipe | 父子进程通信 | 简单但不能跨非亲缘进程 |
FIFO | 任意进程通信 | 文件路径通信,半双工 |
Unix Socket | 任意进程、双工通信、支持 select/epoll | 🚀推荐 |
消息队列 / 共享内存 / 信号量 | 高性能 / 同步控制 | 复杂、调试麻烦 |
TCP/IP socket | 可跨主机,性能差一点 | 多用于网络 |
✅ Unix Socket 通信原理
- 使用
AF_UNIX
地址族 - 可以像 TCP socket 一样
bind()
、listen()
、accept()
、connect()
、send()
、recv()
- 通信是 面向流(SOCK_STREAM) 或 数据报(SOCK_DGRAM)
- 不经过网络栈,效率高
📦 实例演示(基于 SOCK_STREAM)
🚦 1. 创建 socket 服务器进程(接收端)
// server.cpp
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <iostream>
#include <cstring>#define SOCK_PATH "/tmp/mysock"int main() {int server_fd, client_fd;struct sockaddr_un addr;char buf[128];// 创建 socketserver_fd = socket(AF_UNIX, SOCK_STREAM, 0);if (server_fd < 0) {perror("socket");return 1;}// 绑定路径memset(&addr, 0, sizeof(addr));addr.sun_family = AF_UNIX;strcpy(addr.sun_path, SOCK_PATH);unlink(SOCK_PATH); // 如果之前有就删掉if (bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {perror("bind");return 2;}listen(server_fd, 5);std::cout << "[Server] Waiting for connection...\n";client_fd = accept(server_fd, NULL, NULL);std::cout << "[Server] Client connected!\n";int n = read(client_fd, buf, sizeof(buf) - 1);if (n > 0) {buf[n] = '\0';std::cout << "[Server] Received: " << buf << std::endl;}close(client_fd);close(server_fd);unlink(SOCK_PATH);return 0;
}
🚦 2. 创建 socket 客户端进程(发送端)
// client.cpp
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <iostream>
#include <cstring>#define SOCK_PATH "/tmp/mysock"int main() {int client_fd;struct sockaddr_un addr;client_fd = socket(AF_UNIX, SOCK_STREAM, 0);if (client_fd < 0) {perror("socket");return 1;}memset(&addr, 0, sizeof(addr));addr.sun_family = AF_UNIX;strcpy(addr.sun_path, SOCK_PATH);if (connect(client_fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {perror("connect");return 2;}const char *msg = "Hello from client!";write(client_fd, msg, strlen(msg));std::cout << "[Client] Sent message.\n";close(client_fd);return 0;
}
🛠 编译与运行
g++ server.cpp -o server
g++ client.cpp -o client
运行:
./server # 先启动服务端
./client # 启动客户端发送消息
输出:
[Server] Waiting for connection...
[Server] Client connected!
[Server] Received: Hello from client!
✅ 优势总结
- ✅ 可以用于任意两个进程通信(不需要是父子进程)
- ✅ 支持双向通信(
read
+write
) - ✅ 支持
select()
、epoll()
等异步模型 - ✅ 性能远高于 TCP socket(因为不经过网络协议栈)
📦 衍生用法
你还可以用 Unix Socket 实现:
- 🔄 RPC 通信(例如 gRPC + unix socket)
- 📺 服务进程间的信号通知(配合 select)
- 🔐 传送
fd
(file descriptor)文件描述符 - 📊 与 systemd 的 socket activation 通信
- 🚀 用于嵌入式设备中代替 DBus 进行本地模块间通信
📌 是否需要我为你扩展下面内容?
- 🔁 使用
epoll()
实现多个客户端接入? - 🔐 传递文件描述符(fd-passing)?
- 💡 把 client/server 封装成库?
- 🌟 更复杂的二进制结构体协议发送/接收?
你可以告诉我你的应用场景,我可以给你定制化。是否继续?