一、简介
1.1 分类:
1.2 查看管道命令
二、匿名管道
2.1 单工管道
程序进程与shell命令行进程单项通信。
1. 打开管道
FILE* popen(const char *command(命令行字符串),const char* open_mode(“r”只读、“w”只写));
2. 读取
size_t fread(void* buffer(接收数据的内存地址), size_t size(读取的每个数据项的字节数), size_t count(数据项个数), FILE* stream(输入流));
3. 写入
size_t fwrite(const void* buffer(写入数据的内存地址),size_t size(写入数据项的字节数,size_t count(写入数据项的个数),FILE* stream(目标文件指针));
4. 关闭管道
int pclose(FILE* stream(文件描述符));
示例!!!
#include<iostream>
#include<unistd.h>
using namespace std;
int main(){
#ifdef WPIPE
FILE* f=popen("wc","w");
if(NULL==f){
perror("popen error");
return EXIT_FAILURE;
}
string line;
getline(cin,line);
int n=fwrite(line.c_str(),sizeof(char),line.size(),f);
if(n>line.size(){
perror("fwrite error");
return EXIT_FAILURE;
}
pclose(f);
#endif
FILE* f = popen("pstree","r");
if(NULL==f){
perror("popen error");
return EXIT_FAILURE;
}
char buffer[1024]={0};
fread(buffer,sizeof(char),1024,f);
cout<<buffer<<endl;
pclose(f);
return EXIT_SUCCESS;
}
本质
- 启动shell和命令两个进程,从命令进程中读/写文件流。
- 解决exec和system无法返回输出数据问题
特点
- 方便使用系统自带功能,并且可以执行比较复杂Shell
- 默认启动两个进程,效率较低。
管道和文件读写对比
操作 | 管道 | 文件 |
---|
打开 | popen () | fopen () |
关闭 | pclose () | fclose () |
2.2 半双工管道
1. 创建管道int pipe(int filedes[2]); 其中filedes[0]为读,filedes[1]为写
2. 读取ssize_t read(int fd文件描述符, const void* buf读取数据的内存单元, size_t count)
3. 写入ssize_t write(int fd文件描述符, void* buf写入数据的内存单元, size_t count写入文件指定的字节数)
4.控制int fcntl(int fd文件描述符, int cmd( F_GETFL:获取文件描述符状态;F_SETFL:设置文件描述符状态; ), long arg ( O_NONBLOCK:非阻塞; O_BLOCK:阻塞))
把文件描述符改为非阻塞的fcntl(filedes,F_SETFL,O_NONBLOCK);
5. 关闭管道close(filedes)
示例!!!
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
using namespace std;
#define BUFSIZE 4
int main(){
int fds[2];
pipe(fds);
fcntl(fds[0],F_SETFL,O_NONBLOCK);
if(0!=fork()){
for(;;){
cout << "parent [" << getpid() << "] send:";
string str;
cin >> str;
write(fds[1],str.c_str(),str.size());
usleep(100000);
}
}else{
for(;;){
char buffer[BUFSIZE] = {0};
int n = read(fds[0],buffer,BUFSIZE);
if(-1==n){
usleep(10000);
}else{
cout << "child [" << getpid() << "] recv:" << buffer << endl;
}
}
}
close(fds[0]);
close(fds[1]);
}
#include <iostream>
#include <algorithm>
#include <cstring>
#include <sstream>
#include <unistd.h>
#include <fcntl.h>
using namespace std;
#define BUFSIZE 4
int main(){
int fds[2];
int fds2[2];
pipe(fds);
pipe(fds2);
fcntl(fds[0],F_SETFL,O_NONBLOCK);
fcntl(fds2[0],F_SETFL,O_NONBLOCK);
if(0!=fork()){
close(fds[0]);
close(fds2[1]);
for(;;){
cout << "parent [" << getpid() << "] send:";
string str;
getline(cin,str);
write(fds[1],str.c_str(),str.size()+1);
int n;
ostringstream oss;
do{
char buffer[BUFSIZE] = {0};
n = read(fds2[0],buffer,BUFSIZE);
oss << buffer;
}while(-1!=n || oss.str().empty());
cout << "parent [" << getpid() << "] recv:" << oss.str() << endl;
}
}else{
close(fds[1]);
close(fds2[0]);
for(;;){
ostringstream oss;
int n;
do{
char buffer[BUFSIZE] = {0};
n = read(fds[0],buffer,BUFSIZE);
if(-1 != n) oss << buffer;
}while(-1!=n || oss.str().empty());
string str = oss.str();
cout << "child [" << getpid() << "] recv:" << str << endl;
reverse(str.begin(),str.end());
write(fds2[1],str.c_str(),str.size()+1);
}
}
close(fds[0]);
close(fds[1]);
close(fds2[0]);
close(fds2[1]);
}
三、 FIFO管道/命名管道
1. 创建命名管道int mkfifo(pathname文件路径,文件必须不存在, mode模式(如0644))
注:
- 管道文件通常在
/tmp
目录下创建。 - 管道文件大小通常是
0
示例!!!
#include <stdio.h>
#include <unistd.h>
int main(){
if(-1 == mkfifo("/tmp/test",0644)){
perror("mkfifo error");
return 1;
}
}
2. 打开FIFO文件 int open(const char* path文件路径, int mode模式);
O_RDONLY
阻塞只读O_RDONLY
| O_NONBLOCK
非阻塞只读O_WRONLY
阻塞只写O_WRONLY
| O_NONBLOCK
非阻塞只写
返回值
示例!!!
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
using namespace std;
int main(){
int fd = open("/tmp/fifo",O_RDONLY);
char buffer[BUFSIZ] = {0};
read(fd,buffer,BUFSIZ);
cout << buffer << endl;
close(fd);
}
#include <iostream>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
using namespace std;
int main(){
string str;
getline(cin,str);
int fd = open("/tmp/fifo",O_WRONLY);
write(fd,str.c_str(),str.size()+1);
close(fd);
}
- 只需要在open()添加O_NONBLOCK
- 写open()非阻塞,读open()阻塞的情况。读open()需要先执行,否则,写open()会出现No such device or address。
#include <iostream>
#include <fcntl.h>
#include <unistd.h>
using namespace std;
int main(){
int fd = open("/tmp/fifo",O_WRONLY|O_NONBLOCK);
if(-1 == fd){
perror("open error");
return EXIT_FAILURE;
}else{
cout << "filo open ok" << endl;
}
string str;
getline(cin,str);
write(fd,str.c_str(),str.size()+1);
close(fd);
return EXIT_SUCCESS;
}
注:
1.只需要在open()
添加O_NONBLOCK
。
2.写open()
阻塞,读open()
非阻塞的情况。读read()
需要处理写open()
未执行(read()
返回0)和读不到数据(写open()
打开但是没有写数据,read()
返回-1)
的情况。
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
using namespace std;
#define BUFSIZE 258
int main(){
int fd = open("/tmp/fifo",O_RDONLY|O_NONBLOCK);
if(-1 == fd){
perror("open error");
return EXIT_FAILURE;
}else{
cout << "filo open ok" << endl;
}
char buffer[BUFSIZE] = {0};
int n = -1;
while(n<=0){
n=read(fd,buffer,BUFSIZE);
}
cout << buffer << endl;
close(fd);
return EXIT_SUCCESS;
}
特点
- 可以是非亲缘进程之间
- FIFO首先会阻塞在open(),等待读写文件的文件描述符都打开。接着阻塞在read()/write()操作,读写操作需要同时执行。
案例
四、通信分类
- 只写单工:

- 只读单工:



类型 | 创建、打开 | 关闭 | 读 | 写 |
---|
单工 | popen() | pclose() | fread() | fwrite() |
半双工 | pipe() /open() | close() | read() | write() |
FIFO半双工 | mkfifo() /open() | close() /unlink() | read() | write() |
全双工 | socketpair() | close() | read() | write() |
4.1 单进程管道

管道通常用于进程间通信
4.2 父子进程单向管道
4.2.1 概念图解

父子进程管道
父进程关闭fd[0] 子进程关闭fd[1]

父子进程单向管道
### 4.2.2 原理图解



4.3 父子进程双向管道

五、文件描述符
5.1 Linux文件读写与标准C的文件读写
文件描述符
| 文件描述符 | 文件流 |
---|
数据 | int整数 | FILE指针 |
标准 | POSIX | ANSI C |
打开 | open | fopen |
关闭 | close | fclose |
读 | read | fread |
写 | write | fwrite |
定位 | lseek | fseek |
- 文件流是文件描述符之上的封装。文件流通过增加缓冲区减少读写系统调用次数来提高读写效率。在进程的用户空间封装的
FILE
结构,以提高可移植性和效率
5.2 文件描述符原理
Linux内核使用三个关联的数据结构,表示打开的文件。

5.3 命令lsof
lsof
(list open files):列出当前系统打开文件
列名 | 含义 |
---|
COMMAND | 进程的名称 |
PID | 进程标识符 |
USER | 进程所有者 |
FD | 文件描述符,应用程序通过文件描述符识别该文件。如cwd、txt等 |
TYPE | 文件类型,如PIPE、DIR、REG等 |
DEVICE | 指定磁盘的名称 |
SIZE | 文件的大小 |
NODE | 索引节点(文件在磁盘上的标识) |
NAME | 打开文件的确切名称 |
命令 | 作用 |
---|
sof 文件名 | 查看文件打开信息 |
lsof -d 文件描述符 | 查看文件描述符信息 |
lsof -p PID | 查看进程PID打开的文件信息 |
5.3 文件描述符复制
分类 | 文件描述符 | 文件号 |
---|
标准输入 | STDIN_FILENO | 0 |
标准输出 | STDOUT_FILENO | 1 |
标准出错信息 | STDERR_FILENO | 2 |
内核为每个进程创建的文件描述符。 | | |
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main(){
char str[1024];
scanf("%s",str);
printf("%s\n",str);
read(STDIN_FILENO,str,sizeof(str));
write(STDOUT_FILENO,str,strlen(str));
}