Unix Domain Sockets
这种方式是主从反应堆中,主反应堆和从反应堆两个进程通信方式的一种。
这种方式是一种在 Unix / Linux 操作系统上用于进程间通讯的机制。与传统的网络套接字(TCP/IP Sockets)不同,UDS 在同一台机器上进行通讯,不需要通过网络协议栈,因此具有更高的性能和更低的延迟。
特点
-
优点:速度快,延迟低,因为它们不需要网络栈。
-
缺点:只能在同一台机器上使用。
-
适用场景:适用于同一台机器上的进程间通讯,且对性能要求较高的场景。
代码流程以及示例
最主要的一点是如何实现文件描述符的传递。
c
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <vector>
#define UDS_PATH "/tmp/uds_socket"
#define MAX_EVENTS 10
// 子进程需要做的任务
void child_process(int uds_socket) {
// epoll的处理过程可以省略,我们主要看两个进程是如何实现文件描述符的传递的
int epoll_fd = epoll_create1(0);
if (epoll_fd == -1) {
perror("epoll_create1");
exit(EXIT_FAILURE);
}
struct epoll_event ev, events[MAX_EVENTS];
while (true) {
// 接收来自主进程的客户端套接字描述符
int client_socket;
struct msghdr msg = {0};
struct cmsghdr *cmsg;
char buf[CMSG_SPACE(sizeof(client_socket))];
char dummy;
struct iovec io = { .iov_base = &dummy, .iov_len = 1 };
msg.msg_iov = &io;
msg.msg_iovlen = 1;
msg.msg_control = buf;
msg.msg_controllen = sizeof(buf);
if (recvmsg(uds_socket, &msg, 0) < 0) {
perror("recvmsg");
exit(EXIT_FAILURE);
}
cmsg = CMSG_FIRSTHDR(&msg);
if (cmsg == NULL || cmsg->cmsg_len != CMSG_LEN(sizeof(client_socket))) {
std::cerr << "Invalid cmsg received\n";
exit(EXIT_FAILURE);
}
if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS) {
std::cerr << "Invalid cmsg_level or cmsg_type\n";
exit(EXIT_FAILURE);
}
client_socket = *((int *) CMSG_DATA(cmsg));
// 将客户端套接字描述符添加到 epoll 实例中
ev.events = EPOLLIN;
ev.data.fd = client_socket;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_socket, &ev) == -1) {
perror("epoll_ctl: client_socket");
exit(EXIT_FAILURE);
}
// 等待事件
int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
if (nfds == -1) {
perror("epoll_wait");
exit(EXIT_FAILURE);
}
for (int n = 0; n < nfds; ++n) {
if (events[n].events & EPOLLIN) {
char buffer[512];
ssize_t count = read(events[n].data.fd, buffer, sizeof(buffer));
if (count == -1) {
perror("read");
exit(EXIT_FAILURE);
} else if (count == 0) {
// 客户端关闭连接
close(events[n].data.fd);
} else {
// 处理读取到的数据
std::cout << "Received data: " << buffer << std::endl;
}
}
}
}
close(epoll_fd);
}
int main() {
// 创建 Unix Domain Socket
int uds_socket = socket(AF_UNIX, SOCK_STREAM, 0);
if (uds_socket == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, UDS_PATH);
// 绑定 Unix Domain Socket
if (bind(uds_socket, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
perror("bind");
exit(EXIT_FAILURE);
}
// 监听 Unix Domain Socket
if (listen(uds_socket, 5) == -1) {
perror("listen");
exit(EXIT_FAILURE);
}
// 创建子进程
pid_t pid = fork();
if (pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pid == 0) {
// 子进程
close(uds_socket);
child_process(uds_socket);
} else {
// 主进程
while (true) {
// 接收客户端连接
int client_socket = accept(uds_socket, NULL, NULL);
if (client_socket == -1) {
perror("accept");
exit(EXIT_FAILURE);
}
// 发送客户端套接字描述符到子进程
struct msghdr msg = {0};
struct cmsghdr *cmsg;
char buf[CMSG_SPACE(sizeof(client_socket))];
char dummy = '\0';
struct iovec io = { .iov_base = &dummy, .iov_len = 1 };
msg.msg_iov = &io;
msg.msg_iovlen = 1;
msg.msg_control = buf;
msg.msg_controllen = sizeof(buf);
cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof(client_socket));
*((int *) CMSG_DATA(cmsg)) = client_socket;
if (sendmsg(uds_socket, &msg, 0) < 0) {
perror("sendmsg");
exit(EXIT_FAILURE);
}
close(client_socket);
}
}
close(uds_socket);
unlink(UDS_PATH);
return 0;
}
其中的关键点如下:
-
Unix Domain Socket 的创建和绑定: 在主进程中创建 Unix Domain Socket,并绑定到指定路径,用于主从进程之间的通信。
-
描述符传递: 使用 sendmsg 和 recvmsg 进行描述符的传递。