栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

二、Linux系统编程:管道

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

二、Linux系统编程:管道

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"只写
  • 返回值
返回值 含义
NULL 文件描述符
非NULL 打开失败

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 
using namespace std;
int main(int argc,char* argv[]){
    for(int i = 1;i < argc;i++){
    	cout << argv[i] << endl;
    }
}

recv.cpp

//receive与send交互:./send hello world | ./rece
#include 
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]# ./send 123 abc | ./recv 
receive:123abc

实力2:加入mypipe.cpp

// 执行:./a.out "./send ab 12" "./recv"
#include 
#include 
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]# ./a.out "./send ab 12" "./recv"
receive:ab12
2.2.2 半双工管道

1、创建管道int pipe(int filedes[2])

  • 参数
参数 含义
filedes[0] 读
filedes[1] 写
  • 返回值
返回值 含义
-1 失败
0 成功

2、读取ssize_t write(int fd, const void *buf, size_t nbyte)

  • 参数
参数 含义
fd 文件描述符
buf 写入数据的内存单元
nbyte 写入文件指定的字节数
  • 返回值
返回值 含义
-1 出错
正数 写入的字节数

3、写入ssize_t read(int fd, void *buf, size_t count)

  • 参数
参数 含义
fd 文件描述符
buf 读取数据的内存单元
  • 返回值
返回值 含义
-1 出错
0 无数据
正数 读取的字节数

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 
#include 
#include 
using namespace std;
// fds为半双工
int main(){
    int fds[2]; //fds[0](读) fds[1](写)
    /// file fds[0] = open(file,"r");
    /// file fds[1] = open(file,"w");
    pipe(fds);
    if(fork()){
		close(fds[0]); //不用的先关掉
		cout << getpid() << "  send msg:";
    	string msg;
		cin >> msg; // block阻塞
		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};
		read(fds[0],buffer,1024); // block阻塞,往终端写入数据后才执行从终端读的操作
		cout << getpid() << "  recv msg:" << buffer << endl;
		close(fds[0]);
    }
}
[root@localhost pipe]# ./a.out 
22557  send msg:abc
22558  recv msg:abc

实力2:添加阻塞控制输入等的时间,如果不添加阻塞就会一直等

#include 
#include 
#include 
using namespace std;
// fds为半双工
int main() {
    int fds[2]; //fds[0](读) fds[1](写)
    /// file fds[0] = open(file,"r");
    /// file fds[1] = open(file,"w");
    pipe(fds);
    cout << fds[0] << 't' <> msg; // block阻塞
        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]# g++ fork_pipe2.cpp
[root@localhost pipe]# ./a.out 
3	4
22747  send msg:recv data timeout 5s
q

若在5s内输入,可以正常输出到终端

[root@localhost pipe]# ./a.out 
3	4
22818  send msg:abc
[root@localhost pipe]# 22819  recv msg:abc
2.3 FIFO管道/命名管道

1、创建命名管道int mkfifo(pathname,mode)

  • 参数
参数 含义
pathname 文件路径,文件必须不存在
mode 模式
  • 返回值
返回 含义
0 成功
非零 失败
  • 特点
    可以是非亲缘进程之间
    FIFO首先会阻塞在open(),等待读写文件的文件描述符都打开。接着阻塞在read()/write()操作,读写操作需要同时执行。

实例1:通过FIFO管道实现读写
创建管道:createfifo.cpp

#include 
#include 
#include 
using namespace std;
int main(int argc,char** argv){
    if(-1 == mkfifo(argv[1],0666)){
    	 perror("fifo error");
    }
}

写入终端:writefifo.cpp

#include 
#include 
#include 
#include 
#include 
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 
#include 
#include 
#include 
#include 
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]# ./writefifo ./createfifo
hello
...
...
[root@localhost fifo]# ./readfifo ./createfifo
3632  recv:hello

实例2:通过FIFO管道实现读写(加入阻塞)

  • 写非阻塞
    1、只需要在open()添加O_NONBLOCK。
    2、写open()非阻塞,读open()阻塞的情况。读open()需要先执行,否则,写open()会出现No such device or address。
#include 
#include 
#include 
#include 
#include 
using namespace std;
//读的程序若在后台执行,写入执行后会一直等待输入,否则等待5s不写入自动结束
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 
#include 
#include 
#include 
#include 
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 
#include 
#include 
#include 
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 
#include 
#include 
int main(){
    char str[1024];
    scanf("%s",str);
    printf("%sn",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 
#include 
#include 
using namespace std;
// fds为半双工
int main(){
    int fds[2]; //fds[0](读) fds[1](写)
    // file fds[0] = open(file,"r");
    // file fds[1] = open(file,"w");
    pipe(fds);
    if(fork()){
        close(fds[0]); //不用的先关掉
        cout << getpid() << "  send msg:";
        string msg;
        cin >> msg; // block阻塞
        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};
        read(fds[0],buffer,1024); // block阻塞,往终端写入数据后才执行从终端读的操作
        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节中的原理分析

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/925893.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号