1 简介
Linux进程通信机制:
- 管道
- 信号量
- 消息队列
- 共享内存
- socket通信
2 管道
管道其实质是由内核管理的一个缓冲区
形象地认为管道的两端连接着两个进程:
- 一个进程进行信息输出,将数据写入管道;
- 另一个进程进行信息输入,从管道中读取信息。
管道分为:
- 匿名管道:只能用于有亲缘关系的进程间通信,进程退出后管道会被销毁。
- 命名管道:命名管道与进程的联系较弱,相当于一个读写内存的接口,进程退出后,命名管道依然存在。
2.1 匿名管道
匿名管道的使用流程如下:
①在进程中创建匿名管道,pipe函数;
②关闭进程中不使用的管道端口,close函数;
③在待通信的进程中分别对管道的读、写端口进行操作,read/write函数;
④关闭管道,close函数。
2.1.1 pipe函数
#include <unistd.h>
int pipe(int pipefd[2]);
功能:创建匿名管道
参数说明:
- pipefd:传入参数,一个文件描述符数组;Linux将管道抽象为一个特殊文件。
返回值说明:
- 成功:返回0.
- 不成功:返回-1。
【案例1】使用pipe()实现父子进程间通信,父进程作为读端,子进程作为写端。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(){int tempFd[2];//定义文件描述符数组int tempRet=pipe(tempFd);//创建管道if(tempRet == -1){ perror("pipe");exit(1);} pid_t tempPid=fork();if(tempPid > 0){//父进程—读close(tempFd[1]);//关闭写端char tempBuf[64]={0};tempRet = read(tempFd[0], tempBuf, sizeof(tempBuf));//读数据close(tempFd[0]);write(STDOUT_FILENO, tempBuf, tempRet);//将读到的数据写到标准输出wait(NULL);} else if(tempPid == 0){//子进程—写close(tempFd[0]);//关闭读端char *tempStr="hello,pipe\n";write(tempFd[1], tempStr, strlen(tempStr)+1);//写数据close(tempFd[1]);}//of ifreturn 0;
}//of main
分析如下:
pipe()创建管道后读端的文件描述符为fd[0],写端的文件描述符为fd[1];
调用fork后父子进程共享文件描述符,文件描述符与管道的关系如图所示:
父进程进行读操作,子进程进行写操作;使用close()函数关闭父进程的写端与子进程的读端。
2.1.2 dup2函数
【案例2】使用管道实现兄弟进程间通信,兄弟进程实现命令“ls | wc –l”的功能。
- 在实现本案例时会用到重定向函数dup2:
#include <unistd.h>
int dup2(int oldfd, int newfd);
- 其功能是将参数oldfd的文件描述符复制给newfd
- 若函数调用成功则返回newfd
- 否则返回-1,并设置errno。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(){int tempFd[2];int tempRet = pipe(tempFd);if(tempRet == -1){perror("pipe err");exit(1);}//of if int i;pid_t tempPid, tempWpid;for(i=0; i<2; i++){//2个子if((tempPid = fork()) == 0){break;}//of if}//of ifif(2 == i){//父进程,回收子进程close(tempFd[0]); //关闭读close(tempFd[1]); //关闭写tempWpid = wait(NULL);printf("wait child 1 success,pid=%d\n", tempWpid );tempPid = wait(NULL);printf("wait child 2 success,pid=%d\n", tempPid);} else if(0 == i){//子进程1—写close(tempFd[0]);dup2(tempFd[1], STDOUT_FILENO);//定向到标准输出execlp("ls", "ls", NULL);} else if(1 == i){//子进程2—读close(tempFd[1]);dup2(tempFd[0], STDIN_FILENO);execlp("wc", "wc", "-l", NULL);}//of ifreturn 0;
}//of main
注意:
int execlp(const char *file, const char *arg, ...);
execlp是一个可变参数函数。它需要2个const char *。其余的参数(如果有的话)为转移到运行程序的附加参数,也是char * ,最后一个参数必须是NULL指针。
所以,文件参数是要执行的可执行文件的路径名。 arg是我们想在可执行文件中显示为argv [0]的字符串。按照惯例,argv [0]只是可执行文件的文件名,通常它被设置为与文件相同。
兄弟进程间通信,进程文件描述符与管道的关系如图所示:
实线所示的箭头为编程中需要保留的文件描述符
2.1.3 popen/pclose函数
Linux标准I/O库两个函数popen()和pclose(),可完成管道通信的流程。
#include <stdio.h>
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
功能:
popen函数的功能是:
- 调用pipe()函数创建管道;
- 调用fork()函数创建子进程;
- 之后在子进程中通过execve()函数调用shell命令执行相应功能。
pclose函数的功能:
- 关闭popen()打开的I/O流;
- 通过调用wait()函数等待子进程命令执行结束;
- 返回shell的终止状态,防止产生僵尸进程。
参数说明:
popen函数的参数:
- command:命令;
- type:指定命令类型(输入w/输出r);
pclose函数的参数:
- stream:I/O流。
返回值说明:
popen函数的返回值:
- 成功:管道文件的描述符;
- 不成功:返回-1。
pclose函数的返回值:
- 成功:0;
- 不成功:-1。
【案例3】使用popen与pclose函数实现管道通信。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(){FILE *tempRfp,*tempWfp;char temBuf[100];tempRfp = popen("ls","r"); //读取命令执行结果tempWfp = popen("wc -l","w"); //将管道中的数据传递给进程while(fgets(temBuf, sizeof(temBuf), tempRfp)!=NULL){fputs(temBuf, tempWfp);}//of while pclose(tempRfp);pclose(tempWfp);return 0;
}//of main
2.2 命名管道
#include <sys/type.h>
#include <sys/stat.h>int mkfifo(const char *pathname, mode_t mode);
功能:创建命名管道(FIFO文件),命名管道与系统中的一个路径名关联,以文件的形式存在于文件系统中,通过FIFO的路径名访问FIFO文件,实现进程间通信。
参数说明:
- pathname:管道文件的路径名,通过FIFO路径名访问FIFO文件;
- mode:指定FIFO的权限;
返回值说明:
- 成功:0;
- 不成功:-1,并设置errno。
【案例4】使用FIFO实现没有亲缘关系进程间的通信。没有亲缘关系的进程间通信,需要两段程序来实现:
- fifo_write.c实现FIFO的写操作;
- fifo_read.c实现FIFO的读操作。
fifo_write.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int paraArgc,char *paraArgv[]){if(paraArgc < 2){ //判断是否传入文件名printf("./a.out fifoname\n");exit(1);}//of ifint tempRet = access(paraArgv[1], F_OK); //判断fifo文件是否存在if(tempRet == -1){ //若fifo不存在就创建fifo int tempFIFO = mkfifo(paraArgv[1], 0664);if(tempFIFO == -1){ //判断文件是否创建成功perror("mkfifo");exit(1);} else{printf("fifo creat success!\n");}//of if}//of ifint tempFd = open(paraArgv[1], O_WRONLY); //读写方式打开while(1){ //循环写入数据char *tempStrp="hello,world!";write(tempFd, tempStrp, strlen(tempStrp)+1);sleep(1);}//of whileclose(tempFd);return 0;
}//of mainfifo_read.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int paraArgc,char *paraArgv[]){if(paraArgc < 2){ //判断是否传入文件名printf("./a.out fifoname\n");exit(1);}//of ifint tempRet = access(paraArgv[1], F_OK); //判断fifo文件是否存在if(tempRet == -1){ //若fifo不存在就创建fifo int tempFIFO = mkfifo(paraArgv[1], 0664);if(tempFIFO == -1){ //判断文件是否创建成功perror("mkfifo");exit(1);} else{printf("fifo creat success!\n");}//of if}//of ifint tempFd = open(paraArgv[1], O_RDONLY); //只读方式打开if(tempFd == -1){perror("open");exit(1);}//of ifwhile(1){ //不断读取fifo中的数据并打印char temBuf[1024]={0};read(tempFd, temBuf, sizeof(temBuf));printf("buf=%s\n", temBuf);}//of whileclose(tempFd); //关闭文件return 0;
}//of main
FIFO文件和普通文件的区别:
- FIFO文件是对内存进行操作;
- 普通文件是存储在硬盘;
- 对内存的的读取会比硬盘的读写要快很多;
- 两个进程通过普通文件通信当然也是可以的。