linux系统编程——IPC——管道
1. 使用管道需要注意
-
管道提供单向数据流,fd[0]读,fd[1]写
创建全双工IPC管道的方法是 socketpair(). -
写管道数据大小小于 PIPE_BUF 才保证原子性
-
管道和 FIFO支持 O_NONBLOCK ,使用 fcntl 设置
-
写一个没有读打开的管道,内核发送 SIGPIPE,若忽略该信号,write返回错误 EPIPE
-
读一个没有写打开的管道,返回0
-
管道是 字节流,会粘包,可用 分隔符或消息解决
-
不要向管道写大量数据,因为管道容量有限,当空间不足时,会导致写阻塞
2. 阻塞状态下的使用
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
/**********************
* 基于pipe回射服务器
* 注意管道特点:
* 1)单工
* 2)写管道数据小于PIPE_BUF 才保住原子性
* 3)写一个没有读打开的管道,内核发送SIGPIPE,若忽略并继续写,返回EPIPE
* 4)读一个没有写打开的管道,返回0
*/
void srv(int rfd, int wfd);
void cli(int rfd, int wfd);
int main()
{
pid_t pid;
int pfd[4];
pipe(pfd);
pipe(pfd + 2);
switch ((pid = fork())) {
case -1:
break;
case 0:
// 必须关闭不用的文件描述符,
// 这样对方关闭文件描述符后 struct file 才会真正释放
close(pfd[2]);
close(pfd[1]);
srv(pfd[0], pfd[3]);
break;
default:
close(pfd[0]);
close(pfd[3]);
cli(pfd[2], pfd[1]);
waitpid(-1, NULL, 0);
break;
}
return 0;
}
#define MAXLINE 256
void srv(int rfd, int wfd)
{
char buf[MAXLINE + 1];
int cnt;
signal(SIGPIPE, SIG_IGN);
// 对端关闭,返回0
// 错误,返回-1
if ((cnt = read(rfd, buf, MAXLINE)) <= 0)
return ;
buf[cnt] = 0;
int fd;
if ((fd = open(buf, O_RDONLY)) < 0) {
strcpy(buf, "can't open file\n");
// 写管道注意:
// 对端关闭,触发SIGPIPE,忽略信号,返回EPIPE
write(wfd, buf, strlen(buf));
return ;
}
while ((cnt = read(fd, buf, sizeof(buf)))) {
write (wfd, buf, cnt);
}
}
void cli(int rfd, int wfd)
{
int cnt, len;
char buf[MAXLINE + 1];
signal(SIGPIPE, SIG_IGN);
if (fgets(buf, MAXLINE, stdin) <= 0) {
exit(0);
}
len = strlen(buf);
if (buf[len - 1] == '\n')
len--;
if (write(wfd, buf, len) < 0) {
if (errno == EPIPE) {
printf("srv pipe is closed\n");
return;
}
}
while ((cnt = read(rfd, buf, sizeof(buf)))) {
write (STDOUT_FILENO, buf, cnt);
}
return;
}
3. 非阻塞状态下的使用
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
void delay_write(int fd);
void echo_msg(int fd);
int main(int argc, const char *argv[])
{
pid_t pid;
int flag, p[2];
pipe(p);
flag = fcntl(p[0], F_GETFL, 0);
flag |= O_NONBLOCK;
fcntl(p[0], F_SETFL, flag);
flag = fcntl(p[1], F_GETFL, 0);
flag |= O_NONBLOCK;
fcntl(p[1], F_SETFL, flag);
if ((pid = fork()) == 0) {
close(p[0]);
delay_write(p[1]);
exit(0);
}
close(p[1]);
echo_msg(p[0]);
waitpid(-1, NULL, 0);
return 0;
}
void delay_write(int fd)
{
sleep(2);
exit(0);
// O_NONBLOCK 对写端通常没有影响,除非管道满了
write(fd, "hello", 5);
}
#define MAXLINE 255
void echo_msg(int fd)
{
char buf[MAXLINE + 1];
int cnt;
__again__:
cnt = read(fd, buf, MAXLINE);
if (cnt == 0) {
// 若写管道关闭,则返回 0
printf("peer is closed\n");
return;
}
if (cnt < 0) {
// 若写管道没有关闭,且没有数据,则返回 EAGAIN
if (errno == EAGAIN) {
printf("read again\n");
goto __again__;
}
perror("read");
return;
}
buf[cnt] = 0;
printf("%s\n", buf);
}
4. 粘包问题
由于管道是字符流,所以需要用 分隔符 或 格式化的 方式 避免粘包
为了方便读写,封装如下函数
typedef struct msg_hdr {
int len;
} msg_hdr_t;
int msg_read(int fd, char *buf, int buf_size)
{
msg_hdr_t msg_hdr;
int nbytes;
nbytes = read(fd, &msg_hdr, sizeof(msg_hdr_t));
if (nbytes < 0) {
perror("read");
return -1;
}
// 管道写端全关闭,说明对方已经停止会话
if (nbytes == 0) {
return -2;
}
// 一次 单向通信 结束
if (msg_hdr.len == 0) {
return 0;
}
nbytes = read(fd, buf, msg_hdr.len);
if (nbytes < 0) {
return -1;
}
if (nbytes != msg_hdr.len) {
return -1;
}
return msg_hdr.len;
}
int msg_send(int fd, char *buf, int n)
{
char *p;
int len, max;
msg_hdr_t msg;
// pipe只保证 数据大小小于 PIPE_BUF 的原子性,
// 为了保证 数据报的完整,必须 小于等于 PIPE_BUF
max = PIPE_BUF - sizeof(msg_hdr_t);
p = buf;
if (n == 0 || buf == NULL) {
msg.len = 0;
if (write(fd, &msg, sizeof(msg)) < 0) {
perror("write");
return -1;
}
return 0;
}
while (n > 0) {
len = n > max ? max : n;
msg.len = len;
struct iovec iov[2];
iov[0].iov_base = &msg;
iov[0].iov_len= sizeof(msg);
iov[1].iov_base = p;
iov[1].iov_len = msg.len;
if (writev(fd, iov, sizeof(iov)/sizeof(*iov)) < 0) {
perror("writev");
return -1;
}
n -= len;
p += len;
}
return 0;
}
5. 从内核看管道
管道本质是页缓存和管道文件关联,管道文件属于 特殊的文件系统 pipefs.
5.1 错误的读写管道
管道的数据流向是单向的,反向操作导致报错。
5.2 写读端关闭的管道
内核向写进程发送 SIGPIPE,并返回 EPIPE
5.3 读写关闭的管道
读出管道内剩余数据,最后读出EOF(0字节)
5.4 管道的大小
管道大小有限,默认是 65536,可以修改
由于管道大小有限,所以不适合大量数据传输。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?