非常好!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];
// 创建 socket
server_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 封装成库?
- 🌟 更复杂的二进制结构体协议发送/接收?
你可以告诉我你的应用场景,我可以给你定制化。是否继续?