2 管道
2.1 简介
查看管道命令:man 7 pipe
- 分类:
匿名管道(仅用于父子进程之间的通信) FIFO管道/命名管道(可用于父子或两不相关进程之间的通信)
2.2 匿名管道
2.2.1 单工管道
程序进程与Shell命令行进程单项通信。
1、打开管道FILE* popen (const char *command, const char *open_mode)
参数 | 含义 |
---|
command | 命令行字符串 | open_mode | "r" 只读"w" 只写 |
2、读取size_t fread ( void *buffer, size_t size, size_t count, FILE *stream)
参数 | 含义 |
---|
buffer | 用于接收数据的内存地址 | size | 读取每个数据项的字节数 | count | 数据项个数 | stream | 输入流 |
返回值 | 含义 |
---|
>count | 出错 | 正数 | 真实读取的数据项个数 |
3、写入size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream)
参数 | 含义 |
---|
buffer | 写入数据的内存地址 | size | 写入数据项的字节数 | count | 写入数据项的字节数 | stream | 目标文件指针 |
返回值 | 含义 |
---|
>count | 出错 | 正数 | 真实写入的数据项个数 |
4、 关闭管道int pclose(FILE *stream);
- 本质
启动shell 和命令两个进程,从命令进程中读/写文件流。 解决exec 和system 无法返回输出数据问题 - 特点
方便使用系统自带功能,并且可以执行比较复杂Shell 默认启动两个进程,效率较低。
操作 | 管道 | 文件 |
---|
打开 | popen() | fopen() | 关闭 | pclose() | fclose() |
实例1:send 和recv 实现通信 send.cpp
#include <iostream>
using namespace std;
int main(int argc,char* argv[]){
for(int i = 1;i < argc;i++){
cout << argv[i] << endl;
}
}
recv.cpp
#include <iostream>
using namespace std;
int main(){
string s;
string t;
while(cin >> s){
t+=s;
}
cout << "receive:" << t << endl;
}
执行./send hello world | ./rece
[root@localhost pipe]
receive:123abc
实力2:加入mypipe.cpp
// 执行:./a.out "./send ab 12" "./recv"
int main(int argc,char** argv){
const char* readcmd = argv[1];
const char* writecmd = argv[2];
char buffer[1024] = {0};
FILE* readfile = popen(readcmd,"r");
fread(buffer,1,1024,readfile);
pclose(readfile);
readfile = NULL;
FILE* writefile = popen(writecmd,"w");
fwrite(buffer,1,1024,writefile);
pclose(writefile);
writefile = NULL;
}
[root@localhost pipe]
receive:ab12
2.2.2 半双工管道
1、创建管道int pipe(int filedes[2])
参数 | 含义 |
---|
filedes[0] | 读 | filedes[1] | 写 |
2、读取ssize_t write(int fd, const void *buf, size_t nbyte)
参数 | 含义 |
---|
fd | 文件描述符 | buf | 写入数据的内存单元 | nbyte | 写入文件指定的字节数 |
3、写入ssize_t read(int fd, void *buf, size_t count)
4、控制int fcntl(int fd, int cmd, long arg) 如果管道是空的,read() 默认是阻塞
参数 | 含义 |
---|
fd | 文件描述符 | cmd | F_GETFL :获取文件描述符状态;F_SETFL :设置文件描述符状态; | arg | O_NONBLOCK :非阻塞;O_BLOCK :阻塞 |
把文件描述符改为非阻塞的fcntl(filedes,F_SETFL,O_NONBLOCK);
5、关闭管道close(filedes)
实例:父子进程采用半双工管道实现读写
#include <unistd.h>
#include <iostream>
#include <fcntl.h>
using namespace std;
int main(){
int fds[2];
pipe(fds);
if(fork()){
close(fds[0]);
cout << getpid() << " send msg:";
string msg;
cin >> msg;
write(fds[1],msg.c_str(),msg.size()+1);
close(fds[1]);
}else{
close(fds[1]);
char buffer[1024] = {0};
read(fds[0],buffer,1024);
cout << getpid() << " recv msg:" << buffer << endl;
close(fds[0]);
}
}
[root@localhost pipe]
22557 send msg:abc
22558 recv msg:abc
实力2:添加阻塞控制输入等的时间,如果不添加阻塞就会一直等
#include <unistd.h>
#include <iostream>
#include <fcntl.h>
using namespace std;
int main() {
int fds[2];
pipe(fds);
cout << fds[0] << '\t' <<fds[1] << endl;
if(fork()) {
close(fds[0]);
cout << getpid() << " send msg:";
string msg;
cin >> msg;
write(fds[1],msg.c_str(),msg.size()+1);
close(fds[1]);
} else {
fcntl(fds[0],F_SETFL,O_NONBLOCK);
close(fds[1]);
char buffer[1024] = {0};
int count = 5;
while(-1 == read(fds[0],buffer,1024)) {
sleep(1);
if(--count == 0) break;
}
if(count == 0) {
cout << "recv data timeout 5s" << endl;
} else {
cout << getpid() << " recv msg:" << buffer << endl;
}
close(fds[0]);
}
}
不做任何输入,超时自动结束。
[root@localhost pipe]
[root@localhost pipe]
3 4
22747 send msg:recv data timeout 5s
q
若在5s内输入,可以正常输出到终端
[root@localhost pipe]
3 4
22818 send msg:abc
[root@localhost pipe]
2.3 FIFO管道/命名管道
1、创建命名管道int mkfifo(pathname,mode)
参数 | 含义 |
---|
pathname | 文件路径,文件必须不存在 | mode | 模式 |
- 特点
可以是非亲缘进程之间 FIFO 首先会阻塞在open() ,等待读写文件的文件描述符都打开。接着阻塞在read()/write() 操作,读写操作需要同时执行。
实例1:通过FIFO管道实现读写 创建管道:createfifo.cpp
#include <sys/types.h>
#include <cstdio>
#include <sys/stat.h>
using namespace std;
int main(int argc,char** argv){
if(-1 == mkfifo(argv[1],0666)){
perror("fifo error");
}
}
写入终端:writefifo.cpp
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
using namespace std;
int main(int argc,char** argv){
int fd = open(argv[1],O_WRONLY);
string s;
cin >> s;
write(fd,s.c_str(),s.size()+1);
close(fd);
}
从终端读出:readfifo.cpp
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
using namespace std;
int main(int argc,char* argv[]){
int fd = open(argv[1],O_RDONLY);
char buffer[1024] = {0};
read(fd,buffer,sizeof(buffer));
cout << getpid() << " recv:" << buffer << endl;
close(fd);
}
执行过程: 执行 ./writefifo ./fifofile 另一shell 执行 ./readfifo ./fifofile
[root@localhost fifo]
hello
...
...
[root@localhost fifo]
3632 recv:hello
实例2:通过FIFO管道实现读写(加入阻塞)
- 写非阻塞
1、只需要在open() 添加O_NONBLOCK 。 2、写open() 非阻塞,读open() 阻塞的情况。读open() 需要先执行,否则,写open() 会出现No such device or address 。
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
using namespace std;
int main(int argc,char** argv){
int count = 5;
int fd = -1;
while(--count){
fd = open(argv[1],O_WRONLY|O_NONBLOCK);
if(-1 != fd) break;
sleep(1);
}
if(0 == count){
cout << "open fifo timeout 5s" << endl;
}else{
string s;
cin >> s;
write(fd,s.c_str(),s.size()+1);
}
close(fd);
}
- 读非阻塞
1、只需要在open() 添加O_NONBLOCK 。 2、写open() 阻塞,读open() 非阻塞的情况。读read() 需要处理写open() 未执行(read() 返回0)和读不到数据(写open() 打开但是没有写数据,read() 返回-1)的情况。
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
using namespace std;
int main(int argc,char* argv[]){
int fd = open(argv[1],O_RDONLY);
cout << fd << endl;
if(-1 == fd){
perror("open fifo error");
return 1;
}
char buffer[1024] = {0};
int count = 15;
int res = read(fd,buffer,sizeof(buffer));
while(0 == res){
sleep(1);
if(--count == 0) break;
res = read(fd,buffer,sizeof(buffer));
}
cout << res << endl;
if(count == 0){
cout << "recv timeout 15s" << endl;
}else{
cout << getpid() << " recv:" << buffer << endl;
}
close(fd);
}
执行步骤: 1、执行./createfifo fifo 创造一个名为fifo 的管道 2、./writefifo2 fifo 往管道里写 3、./readfifo2 fifo 从管道里读
写为非阻塞,读为阻塞。 注:读的程序若在后台执行,写入执行后会一直等待输入,否则等待5s不写入自动结束
(1)若读未打开,则写超时5s自动退出
[root@localhost fifo]# ./writefifo2 fifo
open fifo timeout 5s
(2)若写打开未输入退出,则读15s自动退出
[root@localhost fifo]# ./writefifo2 fifo
^C
...
....
[root@localhost fifo]#
3
0
recv timeout 15s
(3)写和读都打开,读一直等待写,写也不会退出,直到写入并读出。
[root@localhost fifo]# ./writefifo2 fifo
jkl
...
...
[root@localhost fifo]# ./readfifo2 fifo
3
4
5187 recv:jkl
实例3:父子进程通过两个管道完成双向读写
读写不能同时阻塞
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
using namespace std;
int main(int argc,char* argv[]) {
int wfd = -1,rfd = -1;
while(-1 == wfd) {
wfd = open(argv[2],O_WRONLY|O_NONBLOCK);
rfd = open(argv[1],O_RDONLY);
}
if(-1 == wfd || -1 == rfd) {
perror("open fifo error");
return 1;
}
if(fork()) {
for(;;) {
string s;
cin >> s;
write(wfd,s.c_str(),s.size()+1);
}
} else {
for(;;) {
char buffer[1024] = {0};
int res = read(rfd,buffer,sizeof(buffer));
cout << getpid() << " recv:" << buffer << endl;
}
}
close(wfd);
close(rfd);
}
步骤: (1)./createfifo a ./createfifo b 创造两个管道 (2)./talker a b 打开另一shell 执行./talker b a (3)实现双向通信
[root@localhost fifo]# ./talker b a
hello
5778 recv:world
[root@localhost fifo]# ./talker a b
5779 recv:hello
world
2.4 通信分类
只写单工 只读单工 半双工 全双工
类型 | 创建/打开 | 关闭 | 读 | 写 |
---|
单工 | popen() | pclose | fread() | fwrite() | 半双工 | pipe()/open() | close() | read() | write() | FIFO半双工 | mkfifo()/open | close()/unlink() | read() | write() | 全双工 | socketpair() | close() | read() | write() |
2.4.1 单进程管道
管道通常用于进程间通信
2.4.2 父子进程单向管道
1、概念图解
父子进程管道
父进程关闭fd[0] 子进程关闭fd[1]
父子进程单向管道 2、原理图解 3为读端,4为写端
2.4.3 父子进程双向管道
2.5 文件描述符
2.5.1 Linux文件读写与标准C的文件读写
文件描述符
| 文件描述符 | 文件流 |
---|
数据 | int 整数 | FILE 指针 | 标准 | POSIX | ANSIC | 打开 | open | fopen | 关闭 | close | fclose | 读 | read | fread | 写 | write | fwrite | 定位 | lseek | fseek |
文件流是文件描述符之上的封装。文件流通过增加缓冲区减少读写系统调用次数来提高读写效率。在进程的用户空间封装的FILE 结构,以提高可移植性和效率。
2.5.2 文件描述符复制
内核为每个进程创建的文件描述符。
分类 | 文件描述符 | 文件号 |
---|
标准输入 | 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));
}
[root@localhost pipe]# ./a.out
hello
hello
world
world
2.5.3 文件描述符原理
Linux内核使用三个关联的数据结构,表示打开的文件。 打开不同的文件 打开相同的文件 父子进程共享文件
2.5.4 lsof 命令
列名 | 含义 |
---|
COMMAND | 进程的名称 | PID | 进程标识符 | USER | 进程所有者 | FD | 文件描述符,应用程序通过文件描述符识别该文件。如cwd、txt等 | TYPE | 文件类型,如PIPE、DIR、REG等 | DEVICE | 指定磁盘的名称 | SIZE | 文件的大小 | NODE | 索引节点(文件在磁盘上的标识) | NAME | 打开文件的确切名称 |
命令 | 作用 |
---|
lsof 文件名 | 查看文件打开信息 | lsof -d 文件描述符 | 查看文件描述符信息 | lsof -p PID | 查看进程PID打开的文件信息 |
实例1:fork_pipe.cpp
#include <unistd.h>
#include <iostream>
#include <fcntl.h>
using namespace std;
int main(){
int fds[2];
pipe(fds);
if(fork()){
close(fds[0]);
cout << getpid() << " send msg:";
string msg;
cin >> msg;
write(fds[1],msg.c_str(),msg.size()+1);
close(fds[1]);
}else{
close(fds[1]);
char buffer[1024] = {0};
read(fds[0],buffer,1024);
cout << getpid() << " recv msg:" << buffer << endl;
close(fds[0]);
}
}
执行ps -ef 查看进程,可以看到两个a.out,即父子进程 执行lsof -p 6513 和lsof -p 6514 查看父子进程的文件描述符信息
root 6513 3032 0 18:44 pts/0 00:00:00 ./a.out
root 6514 6513 0 18:44 pts/0 00:00:00 ./a.out
root 6515 3422 0 18:44 pts/1 00:00:00 ps -ef
[root@localhost fifo]# lsof -p 6513
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
a.out 6513 root cwd DIR 8,3 198 83106692 /root/Desktop/2022/internet/2/pipe
a.out 6513 root rtd DIR 8,3 247 128 /
a.out 6513 root txt REG 8,3 17056 83106698 /root/Desktop/2022/internet/2/pipe/a.out
a.out 6513 root mem REG 8,3 3201344 100727036 /usr/lib64/libc-2.28.so
a.out 6513 root mem REG 8,3 104248 100663439 /usr/lib64/libgcc_s-8-20180905.so.1
a.out 6513 root mem REG 8,3 2303696 100733760 /usr/lib64/libm-2.28.so
a.out 6513 root mem REG 8,3 1661496 100735443 /usr/lib64/libstdc++.so.6.0.25
a.out 6513 root mem REG 8,3 243520 100727029 /usr/lib64/ld-2.28.so
a.out 6513 root 0u CHR 136,0 0t0 3 /dev/pts/0
a.out 6513 root 1u CHR 136,0 0t0 3 /dev/pts/0
a.out 6513 root 2u CHR 136,0 0t0 3 /dev/pts/0
a.out 6513 root 4w FIFO 0,12 0t0 75877 pipe
[root@localhost fifo]# lsof -p 6514
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
a.out 6514 root cwd DIR 8,3 198 83106692 /root/Desktop/2022/internet/2/pipe
a.out 6514 root rtd DIR 8,3 247 128 /
a.out 6514 root txt REG 8,3 17056 83106698 /root/Desktop/2022/internet/2/pipe/a.out
a.out 6514 root mem REG 8,3 3201344 100727036 /usr/lib64/libc-2.28.so
a.out 6514 root mem REG 8,3 104248 100663439 /usr/lib64/libgcc_s-8-20180905.so.1
a.out 6514 root mem REG 8,3 2303696 100733760 /usr/lib64/libm-2.28.so
a.out 6514 root mem REG 8,3 1661496 100735443 /usr/lib64/libstdc++.so.6.0.25
a.out 6514 root mem REG 8,3 243520 100727029 /usr/lib64/ld-2.28.so
a.out 6514 root 0u CHR 136,0 0t0 3 /dev/pts/0
a.out 6514 root 1u CHR 136,0 0t0 3 /dev/pts/0
a.out 6514 root 2u CHR 136,0 0t0 3 /dev/pts/0
a.out 6514 root 3r FIFO 0,12 0t0 75877 pipe
3为r(读),4为w(写),对应2.4.2节中的原理分析
|