文章目录
管道
- 管道是“半双工”的,即是单向的。
单进程中的管道:
int fd[2]
- 使用文件描述符fd[1], 向管道写数据
- 使用文件描述符fd[0], 从管道读数据
注:单进程中的管道无实际用处,管道用于多进程间通信。
实例1:单进程使用管道进行通信
main1.c
一个进程实现管道通信(管道主要用于多进程通信)
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
int fd[2];
int ret;
char buff1[1024];
char buff2[1024];
// fd为长度为2类型为int的数组,用于保存读管道和写管道两个文件描述符
ret = pipe(fd);
if (ret !=0) {
printf("create pipe failed!\n");
exit(1);
}
// 往管道中写入数据
strcpy(buff1, "Hello!");
write(fd[1], buff1, strlen(buff1));
printf("send information:%s\n", buff1);
// 往管道中读出数据
bzero(buff2, sizeof(buff2));
read(fd[0], buff2, sizeof(buff2));
printf("received information:%s\n", buff2);
return 0;
}
实例2:多进程使用管道进行通信
父子进程通过管道通信步骤
- 父进程调用 pipe 函数创建管道,得到两个文件描述符 fd[0]、fd[1]指向管道的读端和写端。
- 父进程调用 fork 创建子进程,那么子进程也有两个文件描述符指向同一管道
如图
main2.c
这里的父子进程可以实现全双工
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
int fd[2];
int ret;
char buff1[1024];
char buff2[1024];
pid_t pd;
ret = pipe(fd);
if (ret !=0) {
printf("create pipe failed!\n");
exit(1);
}
pd = fork();
if (pd == -1) {
printf("fork error!\n");
exit(1);
} else if (pd == 0) {
bzero(buff2, sizeof(buff2));
read(fd[0], buff2, sizeof(buff2));
printf("process(%d) received information:%s\n", getpid(), buff2);
} else {
strcpy(buff1, "Hello!");
write(fd[1], buff1, strlen(buff1));
printf("process(%d) send information:%s\n", getpid(), buff1);
}
if (pd > 0) {
wait();
}
return 0;
}
实例3:父子进程管道半双工
问题:
- 对管道进行read时,如果管道中已经没有数据了,此时读操作将被“阻塞”。
- 父子进程读写都指向同一个管道,则管道有两个写操作.
- 如果有多个写端口,而只关闭了一个写端,那么无数据时读操作仍将被阻塞。
解决方案:
- 如果不准备再向管道写入数据,则把该管道的所有写端都关闭,
- 则,此时再对该管道read时,就会返回0,而不再阻塞该读操作。(管道的特性)
实际实现方式:
父子进程各有一个管道的读端和写端;
- 把父进程的读端(或写端)关闭;
- 把子进程的写端(或读端)关闭;
- 使这个“4端口”管道变成单向的“2端口”管道,全双工变成半双工
如图:
代码:main4.c (改写main2.c)
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
int fd[2];
int ret;
char buff1[1024];
char buff2[1024];
pid_t pd;
ret = pipe(fd);
if (ret !=0) {
printf("create pipe failed!\n");
exit(1);
}
pd = fork();
if (pd == -1) {
printf("fork error!\n");
exit(1);
} else if (pd == 0) {
close(fd[1]);// 子进程把写段关闭
bzero(buff2, sizeof(buff2));
read(fd[0], buff2, sizeof(buff2));
printf("process(%d) received information:%s\n", getpid(), buff2);
} else {
strcpy(buff1, "Hello!");
close (fd[0]);
write(fd[1], buff1, strlen(buff1));
printf("process(%d) send information:%s\n", getpid(), buff1);
close (fd[1]);
}
if (pd > 0) {
wait();
}
return 0;
}
实例4:子进程使用execl启动新程序时管道的使用
解决方案:
- 把子进程中的管道文件描述符,用exec的参数传递给新进程。
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
int fd[2];
int ret;
char buff1[1024];
char buff2[1024];
pid_t pd;
ret = pipe(fd);
if (ret != 0) {
printf("create pipe failed!\n");
exit(1);
}
pd = fork();
if (pd == -1) {
printf("fork error!\n");
exit(1);
}
else if (pd == 0) {
//bzero(buff2, sizeof(buff2));
sprintf(buff2, "%d", fd[0]);
/*前两个都是要执行的程序名
execl()其中后缀"l"代表list也就是参数列表的意思,第一参数path字符指针
所指向要执行的文件路径, 接下来的参数代表执行该文件时传递的参数列表:
argv[0],argv[1]... 最后一个参数须用空指针NULL作结束*/
execl("main3_2", "main3_2", buff2, 0);// 子进程执行main3_2
printf("execl error!\n");
exit(1);
}
else {
strcpy(buff1, "Hello!");
write(fd[1], buff1, strlen(buff1));
printf("process(%d) send information:%s\n", getpid(), buff1);
}
if (pd > 0) {
wait();
}
return 0;
}
main3_2.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char* argv[])
{
int fd;
char buff[1024] = {0};
sscanf(argv[1], "%d", &fd);// 获取管道读端句柄
read(fd, buff, sizeof(buff));
printf("Process(%d) received information:%s\n", getpid(), buff);
return 0;
}
使用popen/pclose
popen的作用:
用来在两个程序之间传递数据:
在程序A中使用popen调用程序B时,有两种用法:
- 程序A读取程序B的输出(使用fread读取)
- 程序A发送数据给程序B,以作为程序B的标准输入。(使用fwrite写入)
// 返回值:成功,返回FILE* 失败, 返回空
FILE * popen( const char * command,const char * type);
实例1: 读取外部程序的输出
读取 ls -l
的结果
main7.c
#include <stdio.h>
#include <stdlib.h>
#define BUFF_SIZE 1024
int main(void)
{
FILE * file;
char buff[BUFF_SIZE+1];
int cnt;
// system("ls -l > result.txt");
file = popen("ls -l", "r"); // 读出ls -l 输出的结果
if (!file) {
printf("fopen failed!\n");
exit(1);
}
cnt = fread(buff, sizeof(char), BUFF_SIZE, file);
if (cnt > 0) {
buff[cnt] = '\0';
printf("%s", buff);
}
pclose(file);
return 0;
}
实例2:把输出写到外部程序
输出一个字符串,到p2程序中
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFF_SIZE 1024
int main(void)
{
FILE * file;
char buff[BUFF_SIZE+1];
int cnt;
file = popen("./p2", "w");
if (!file) {
printf("fopen failed!\n");
exit(1);
}
strcpy(buff, "hello world! I`m martin");
cnt = fwrite(buff, sizeof(char), strlen(buff), file);
pclose(file);
return 0;
}
p2.c
结果:
popen的原理:
- 先使用fork创建一个子进程,
- 然后在子进程中使用exec执行指定外部程序,并返回一个文件指针FILE*给父进程。
- 当使用”r”时,该FILE指向外部程序的标准输出
- 当使用”w”时,该FILE指向外部程序的标准输入。
popen的优缺点:
- 优点:可以使用shell扩展(比如命令中可以使用通配符)使用方便。
- 缺点:每调用一次popen, 将要启动两个进程(shell和被指定的程序), 资源消耗大。
如果所有管道写端对应的文件描述符被关闭,则read返回0
如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE