pipe()创建的管道只能单向通信,因其底层是内核环形缓冲区,数据严格从fd[1](写端)流入、fd[0](读端)流出,不支持反向或双向读写;需两个pipe实现双向通信。

pipe() 创建的管道为什么只能单向通信?
因为 pipe() 系统调用返回一对文件描述符:fd[0](读端)和 fd[1](写端),底层是内核维护的一个环形缓冲区,数据只能从写端流入、从读端流出。它不是双向通道,也不能在父子进程间自动“翻转”方向。
常见错误是试图在同一个进程中对 fd[0] 写、fd[1] 读——这会阻塞或失败。必须由一个进程写、另一个进程读,典型模式是 fork 后父子分工:
- 父进程关闭
fd[0],只用fd[1]写;子进程关闭fd[1],只用fd[0]读 - 反过来也行,但不能混用;若需双向通信,得建两个 pipe(即两对 fd)
-
pipe()返回 -1 时记得检查errno,常见原因是打开文件数超限(EMFILE)或系统资源不足(ENOMEM)
fork() 后文件描述符怎么继承?哪些要显式 close()?
子进程会完整复制父进程的文件描述符表,所以 pipe() 创建的两个 fd 在 fork 后父子双方都可见。但如果不关掉不用的端,会导致读端永远不 EOF、写端无法感知对方关闭——这是最常踩的坑。
关键原则:每个进程只保留自己需要的那一端,其余立即 close():
- 父进程写 → 关闭
fd[0];子进程读 → 关闭fd[1] - 子进程读完后也要
close(fd[0]),否则父进程write()可能成功但数据滞留,且read()不会返回 0 - 如果忘了关某端,
read()会一直阻塞,直到所有写端都被关闭
如何安全地用 write()/read() 传输字符串或结构体?
write() 和 read() 是字节流接口,不带消息边界。传字符串时建议带长度前缀或以 '
write() 和 read() 是字节流接口,不带消息边界。传字符串时建议带长度前缀或以 '\0' 结尾并用 strlen()+1;传结构体必须确保两端内存布局一致(如用 #pragma pack(1) 避免对齐差异)。
strlen()+1;传结构体必须确保两端内存布局一致(如用 #pragma pack(1) 避免对齐差异)。实操要点:
- 不要直接
write(fd, str, strlen(str)),漏掉 '\0' 会导致接收方无法判断结尾;推荐write(fd, str, strlen(str)+1) - 接收方用
read()时,需循环调用直到读满预期字节数,或约定以 '\0' 为终止符(但注意 '\0' 可能在中间出现) - 结构体传输前确认
sizeof(MyStruct)两端一致,字段顺序、padding、整数字节序(通常同机通信不用处理) - 避免
write(fd, &obj, sizeof(obj))直接传含指针/虚函数表的对象——那是未定义行为
为什么 read() 返回 0 就代表对方关闭了写端?
这是 POSIX 对管道的明确定义:当所有指向管道写端的 fd 都被关闭后,后续对读端的 read() 调用返回 0,表示 EOF。这不是错误,而是正常结束信号。
容易混淆的点:
-
read()返回 -1 且errno == EAGAIN或EWOULDBLOCK表示当前无数据(非阻塞模式下) -
read()返回 0 ≠ 缓冲区为空,而是写端已彻底关闭 - 如果发送方只
close(fd[1])但没exit()或继续运行,接收方仍可能读到残留数据,之后才收到 0 - 别用返回值是否为 0 来判断“数据是否收完”,而应结合协议(如长度头)或明确关闭时机
管道简单可靠,但仅限亲缘进程;跨用户或跨主机必须换 socket 或 message queue。真正麻烦的是错误处理路径里的 close 顺序和 errno 检查——写一次就要跑三遍不同场景验证。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











