IPC——基于STREAMS的管道
1. 概述
基于 STREAMS 的管道有三个特点:
- 全双工
- 可以传递文件描述符
- 可以用文件命名
2. socketpair
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socketpair(int domain, int type, int protocol, int sv[2]);
创建 互相连接 的 unix 套接字。
由于unix套接字只复制数据,没有报文封装等操作,所以高效,且可以用 套接字相关api。
3. 基于unix域套接字的服务端客户端
unix套接字编程和tcp套接字编程类似,只是bind的是文件名
服务端
#include <stdio.h>
#include <stddef.h>
#include <string.h>
#include <unistd.h>
#include <sys/un.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int
main ()
{
int fd, cnt, cfd;
size_t size;
char buf[200];
struct sockaddr_un peer, un;
fd = socket(AF_UNIX, SOCK_STREAM, 0);
un.sun_family = AF_UNIX;
strcpy(un.sun_path, "hello.socket");
unlink(un.sun_path); // 若已存在同名文件,则先删除
// bind 成功后会创建 socket 文件
if (bind(fd, (struct sockaddr *)&un, sizeof(un)) < 0) {
perror("bind");
return -1;
}
// 不能删除socket文件,因为客户端需要利用文件系统连接套接字
// 将套接字转成 监听套接字
if (listen(fd, 10) < 0) {
perror("listen");
return -1;
}
while ((cfd = accept(fd, (struct sockaddr *)&peer, &size))) {
if ((cnt = read(cfd, buf, sizeof(buf))) < 0) {
perror("read");
return -1;
}
buf[cnt] = 0;
printf("read : %s\n", buf);
write(cfd, "world", 5);
close(cfd);
}
close(fd);
return 0;
}
客户端
#include <stdio.h>
#include <stddef.h>
#include <string.h>
#include <unistd.h>
#include <sys/un.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int
main ()
{
int fd, cnt;
struct sockaddr_un un;
char buf[200];
fd = socket(AF_UNIX, SOCK_STREAM, 0);
un.sun_family = AF_UNIX;
strcpy(un.sun_path, "hello.socket");
if (connect(fd, (struct sockaddr *)&un, sizeof(un)) < 0) {
perror("connect");
return -1;
}
if ((cnt = write(fd, "hello", 5)) < 0) {
perror("write");
return -1;
}
if ((cnt = read(fd, buf, sizeof(buf))) < 0) {
perror("read");
return -1;
}
buf[cnt] = 0;
printf("child read %s\n", buf);
close(fd);
return 0;
}
4. 进程间传递文件描述符
#include <stdio.h>
#include <stdlib.h>
#include <alloca.h>
#include <sys/ioctl.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
void parent();
void child();
int p[2];
int main()
{
// 1. 进程之间用 unix域 套接字传递文件描述符
socketpair(AF_UNIX, SOCK_STREAM, 0, p);
if (fork()) {
parent();
}
else {
child();
}
return 0;
}
void send_fd(int sockfd, int fd_to_send)
{
struct msghdr msg;
struct cmsghdr *cmsg;
#define CONTRLEN CMSG_LEN(sizeof(int))
char buf[1];
struct iovec iov;
iov.iov_base = buf;
iov.iov_len = 1;
buf[0] = 'a';
msg.msg_iov = &iov; // 聚合写,不能为空
msg.msg_iovlen = 1;
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_flags = 0;
// 控制信息
cmsg = alloca(CONTRLEN);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS; // 用于指明传输访问权限
cmsg->cmsg_len = CONTRLEN;
*(int *)CMSG_DATA(cmsg) = fd_to_send; // 控制信息最后域追加 需要传递的文件描述符
msg.msg_control = cmsg;
msg.msg_controllen = CONTRLEN;
if (sendmsg(sockfd, &msg, 0) < 0) {
perror("sendmsg");
}
}
void recv_fd(int sockfd, int *fd_to_recv)
{
struct msghdr msg = {0};
struct cmsghdr *cmsg;
struct iovec iov;
char buf[2] = {0};
iov.iov_base = buf;
iov.iov_len = 2;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
cmsg = alloca(CONTRLEN);
msg.msg_control = cmsg;
msg.msg_controllen = CONTRLEN;
if (recvmsg(sockfd, &msg, 0) < 0) {
perror("recvmsg");
}
cmsg = msg.msg_control;
*fd_to_recv = *(int *)CMSG_DATA(cmsg);
}
void parent()
{
int fd;
close(p[0]);
fd = open("./tmp", O_CREAT | O_RDWR, 0644);
send_fd(p[1], fd);
}
void child()
{
int fd;
close(p[1]);
recv_fd(p[0], &fd);
if (write(fd, "hello world", strlen("hello world")) < 0) {
perror("write");
}
}
unix传输文件描述符,使用 sendmsg, recvmsg,这两个函数都有一个msghdr指针
struct msghdr {
void *msg_name; /* optional address */
socklen_t msg_namelen; /* size of address */
struct iovec *msg_iov; /* scatter/gather array */
size_t msg_iovlen; /* # elements in msg_iov */
void *msg_control; /* ancillary data, see below */
socklen_t msg_controllen; /* ancillary data buffer len */
int msg_flags; /* flags on received message */
};
msg_name 和 msg_namelen 通常用于网络连接发送报文时,指定目的地址
msg_iov 和 msg_iovlen 用于 分散读/聚合写 和 readv writev一样
msg_flags 指定 非阻塞等特性
msg_control 和 msg_controllen 处理控制信息的接受和发送,msg_control 指向 cmsghdr(控制信息头部)结构,
struct cmsghdr {
socklen_t cmsg_len; /* 整个 cmsghdr 的长度,字节为单位 */
int cmsg_level;
int cmsg_type;
/* 紧接着是真实的控制信息数据 */
};
使用下面宏访问cmsghdr
// 返回关联控制信息数据的指针
unsigned char *CMSG_DATA(struct cmsghdr *cp);
// 若 控制信息数据的大小为 nbytes, 则返回对应 整个 cmsghdr 的大小
unsigned int CMSG_LEN(unsigned int nbytes);
5. ngx 实现的 channel
通常 soketpair + fork 能得到如下结构
问题是 老的子进程 没有 新子进程 通信的 struct file * ,所以需要传递由 父进程将新打开的 struct file * 传递给 老子进程。
以形成如下结构:
如此 所有进程互相通信的基础有了,
只需要 加上协议。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?