进程间的通信—管道
管道
- 进程间的通信(IPC-Inter-Process Communication)有多种方式,管道是其中最基本的方式。
- 管道是
半双工
的,即是单向
的。- 管道是FIFO(先进先出)的。
- 在实际的多进程间通信时,可以理解为有一条管道,而每个进程都有两个可以使用管道的"端口",分别负责进行数据的读取与发送。
- 注意:
单进程中的管道无实际用处
,管道用于多进程间通信
。
管道的创建
- 函数原型: int pipe(int pipefd[2]);
- 返回值:
- 成功:返回0。
- 失败:返回-1。
管道的使用
实例1: 单进程使用管道进行通信
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
int fd[2];
int ret;
char buff1[1024];
char buff2[1024];
ret = pipe(fd);
if (ret !=0) {
printf("create pipe Failed!\n");
exit(1);
}
strcpy(buff1, "Hello!");
write(fd[1], buff1, strlen(buff1)); //写进去一个hello
printf("send information:%s\n", buff1);
bzero(buff2, sizeof(buff2));
read(fd[0], buff2, sizeof(buff2));//读出来hello
printf("received information:%s\n", buff2);
return 0;
}
实例2: 多进程使用管道进行通信
#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));//read在没收到数据时会阻塞
printf("process(%d) received information:%s,buff2's address:%p\n", getpid(), buff2,buff2);
sleep(5);
strcpy(buff1, "Hello Dad!");
write(fd[1], buff1, strlen(buff1));
} else {
//父进程先写再读
strcpy(buff1, "Hello Kid");
write(fd[1], buff1, strlen(buff1));
sleep(5);
bzero(buff2, sizeof(buff2));
read(fd[0], buff2, sizeof(buff2));
printf("process(%d) received information:%s,buff2's address:%p\n", getpid(), buff2,buff2);
}
if (pd > 0) {
wait();
}
return 0;
}
实例3: 子进程使用execl启动新程序时管道的使用
- execl()函数原型
int execl(const char *path, const char *arg, ...);
- 函数说明—— Linux下execl函数学习
当进程调用一种exec函数时,该进程完全由新程序代换,而新程序则从其main函数开始执行。因为调用exec并不创建新进程,所以前后的进程ID并未改变。exec只是用另一个新程序替换了当前进程的正文、数据、堆和栈段。
- 示例:
main3.c
#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("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;
}
实例4: 关闭管道的读端/写端
小示例1:主进程关闭写进程后,无法给子进程使用管道发送数据,此时子进程使用read函数进行数据的读取,如果 没有数据可读,则会进行阻塞,代码&结果如下所示:
- 解释:主进程循环5次,给子进程发送数据。5次之后之后,子进程便无法收到来自于主进程的数据,read()开始阻塞。
#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) {
for(;;){
bzero(buff2, sizeof(buff2));
sleep(3);
read(fd[0], buff2, sizeof(buff2));
printf("process(%d) received information:%s\n", getpid(), buff2);
}
} else {
for(int i = 0;i<5;i++){
strcpy(buff1, "Hello!");
write(fd[1], buff1, strlen(buff1));
sleep(3);
printf("process(%d) send information:%s\n", getpid(), buff1);
}
}
if (pd > 0) {
wait();
}
return 0;
}
小示例2:管道间是"
共享的
",个人理解。注意,实际上,并不是同一个内存地址
。在
读取数据
时,管道读端的数据会越读越少
,而在写入数据
时,写入的数据会累加
,添加到尾部。如下所示,
#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) {
for(;;){
bzero(buff2, sizeof(buff2));
sleep(3);
strcpy(buff1, "Dad!");
//子进程写数据
write(fd[1], buff1, strlen(buff1));
}
} else {
for(int i = 0;i<5;i++){
bzero(buff2, sizeof(buff2));
strcpy(buff1, "Hello!");
//父进程写数据
write(fd[1], buff1, strlen(buff1));
sleep(10);
//父进程读数据
read(fd[0], buff2, sizeof(buff2));
printf("dad process(%d) received information:%s\n", getpid(), buff2);
sleep(3);
}
}
if (pd > 0) {
wait();
}
return 0;
}
实例5: 把管道作为标准输入和标准输出
把管道作为标准输入和标准输出的优点:
实现流程:
补充:
- dup函数
- 功能:使用dup函数复制一份原来的文件描述符所指向的内容,并且使用当前系统(进程)可使用的最小文件描述符。
- 示例:先关闭标准输入文件描述符,然后就使用dup复制当前某一文件描述符,再关闭原来的文件描述符,即可完成文件描述符的替换。
- 函数原型:
int dup(int oldfd);
- 返回值:
- 成功:返回新的文件描述符。
- 失败:返回-1,并设置errno。
- execlp函数
- 功能:用exec函数可以把当前进程替换为一个新进程,且新进程与原进程有相同的PID。
- 相关参考——linux系统编程之进程(五):exec系列函数(execl,execlp,execle,execv,execvp)使用
main5.c
#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]);
close(fd[1]);
close(0);//关闭标准输入文件描述符
dup(fd[0]);//复制 fd[0] ,并且使用可用的最小的文件描述符作为此文件描述符
//即,此子进程使用管道的读端替换标准输入文件描述符
close(fd[0]);//关闭原来的读端
execlp("./od.exe", "./od.exe", "-c", 0);
//如果execlp执行成功,则下面不会执行
printf("execl error!\n");
exit(1);
} else {
close(fd[0]);//关闭读端
//写
strcpy(buff1, "Hello!");
write(fd[1], buff1, strlen(buff1));
printf("send...\n");
close(fd[1]);//关闭写端
}
return 0;
}
od.c
#include <stdio.h>
#include <stdlib.h>
int main(void){
int ret = 0;
char buff[80] = {0,};
//scanf从标准输入读——在本实例中,实际上从管道从来的
ret = scanf("%s", buff);
printf("[ret: %d]buff=%s\n", ret, buff);
ret = scanf("%s", buff);
printf("[ret: %d]buff=%s\n", ret, buff);//第二次scanf失败,返回-1
return 0;
}
使用popen/pclose
实例1:读取外部程序的输出
#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);//fread是从文件指针中读取
if (cnt > 0) {
buff[cnt] = '\0';
printf("%s", buff);
}
pclose(file);//关闭
return 0;
}
实例2:把输出写到外部程序
main7.c
#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 'am 123456789testtest!!!");
cnt = fwrite(buff, sizeof(char), strlen(buff), file);
pclose(file);
return 0;
}
p2.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc,char* argv[]){
int fd;
char buff[1024] = {'\0'};
int cnt = read(0,buff,sizeof(buff));
if(cnt > 0)buff[cnt] = '\0';
printf("receive: %s\n",buff);
return 0;
}
popen的原理
popen的优缺点
原文地址:https://www.jb51.cc/wenti/3287921.html
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。