一、进程通信简介
01 进程通信概念
02 Linux进程间通信的方式
二、管道
03 匿名管道
04 管道的特点
- 管道通信数据是一次性的
- 匿名管道只能用于有关系的进程
05 为什么可以使用管道进行进程间的通信
因为共享文件描述符
06 管道的数据结构
环形队列,逻辑上是
07 匿名管道的使用
- 创建匿名管道
#include<unistd.h> int pipe(int pipefd[2]);
查看管道缓冲大小命令
ulimit -a
查看管道缓冲大小函数
#include<unistd.h> long fpathconf(int fd, int name);
例:
/* #include <unistd.h> int pipe(int pipefd[2]); 功能:创建一个匿名管道,用来进程间通信。 参数:int pipefd[2] 这个数组是一个传出参数。 pipefd[0] 对应的是管道的读端 pipefd[1] 对应的是管道的写端 返回值: 成功 0 失败 -1 管道默认是阻塞的:如果管道中没有数据,read阻塞,如果管道满了,write阻塞 注意:匿名管道只能用于具有关系的进程之间的通信(父子进程,兄弟进程) */ #include <unistd.h> #include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <string.h> // 子进程发送数据给父进程,父进程读取到数据输出 int main() { // 在fork之前创建管道 int pipefd[2]; //0是读端,1是写端 int ret = pipe(pipefd); //创建管道,ret为对应的文件描述符 if(ret == -1) { //创建失败的操作 perror("pipe"); exit(0); } // 创建子进程 pid_t pid = fork(); if(pid > 0) { // 父进程从管道的读取端读取数据 char buf[1024] = {0}; int len = read(pipefd[0], buf, sizeof(buf)); printf("parent recv : %s, pid : %d\n", buf, getpid()); } else if(pid == 0){// 子进程 char buf[1024] = {0}; // 子进程向管道中写入数据 char * str = "hello,i am child"; write(pipefd[1], str, strlen(str)); } return 0; }
例:匿名管道实现双向读写数据
/* #include <unistd.h> int pipe(int pipefd[2]); 功能:创建一个匿名管道,用来进程间通信。 参数:int pipefd[2] 这个数组是一个传出参数。 pipefd[0] 对应的是管道的读端 pipefd[1] 对应的是管道的写端 返回值: 成功 0 失败 -1 管道默认是阻塞的:如果管道中没有数据,read阻塞,如果管道满了,write阻塞 注意:匿名管道只能用于具有关系的进程之间的通信(父子进程,兄弟进程) */ #include <unistd.h> #include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <string.h> int main() { // 在fork之前创建管道 int pipefd[2]; int ret = pipe(pipefd); if(ret == -1) { perror("pipe"); exit(0); } // 创建子进程 pid_t pid = fork(); if(pid > 0) { // 父进程 printf("i am parent process, pid : %d\n", getpid()); // 从管道的读取端读取数据 char buf[1024] = {0}; while(1) { //连续读取数据 没有读就阻塞 所以不需要sleep int len = read(pipefd[0], buf, sizeof(buf)); printf("parent recv : %s, pid : %d\n", buf, getpid()); // 向管道中写入数据 char * str = "hello,i am parent"; write(pipefd[1], str, strlen(str)); sleep(1); } } else if(pid == 0){ // 子进程 printf("i am child process, pid : %d\n", getpid()); char buf[1024] = {0}; while(1) { //连续发送数据 // 向管道中写入数据 char * str = "hello,i am child"; write(pipefd[1], str, strlen(str)); sleep(1); //读数据 int len = read(pipefd[0], buf, sizeof(buf)); printf("child recv : %s, pid : %d\n", buf, getpid()); } } return 0; }
例:通过fpathconf
函数查看管道大小
#include <unistd.h> #include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <string.h> int main() { int pipefd[2]; int ret = pipe(pipefd); // 获取管道的大小 long size = fpathconf(pipefd[0], _PC_PIPE_BUF); printf("pipe size : %ld\n", size); return 0; }
例:匿名管道是半双工,所以只能一方读一方写
/* #include <unistd.h> int pipe(int pipefd[2]); 功能:创建一个匿名管道,用来进程间通信。 参数:int pipefd[2] 这个数组是一个传出参数。 pipefd[0] 对应的是管道的读端 pipefd[1] 对应的是管道的写端 返回值: 成功 0 失败 -1 管道默认是阻塞的:如果管道中没有数据,read阻塞,如果管道满了,write阻塞 注意:匿名管道只能用于具有关系的进程之间的通信(父子进程,兄弟进程) */ // 子进程发送数据给父进程,父进程读取到数据输出 #include <unistd.h> #include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <string.h> int main() { // 在fork之前创建管道 int pipefd[2]; int ret = pipe(pipefd); if(ret == -1) { perror("pipe"); exit(0); } // 创建子进程 pid_t pid = fork(); if(pid > 0) { // 父进程读数据,关闭写端 printf("i am parent process, pid : %d\n", getpid()); // 关闭写端 close(pipefd[1]); // 从管道的读取端读取数据 char buf[1024] = {0}; while(1) { int len = read(pipefd[0], buf, sizeof(buf)); printf("parent recv : %s, pid : %d\n", buf, getpid()); // 向管道中写入数据 //char * str = "hello,i am parent"; //write(pipefd[1], str, strlen(str)); //sleep(1); } } else if(pid == 0){ // 子进程写数据,关闭读端 printf("i am child process, pid : %d\n", getpid()); // 关闭读端 close(pipefd[0]); char buf[1024] = {0}; while(1) { // 向管道中写入数据 char * str = "hello,i am child"; write(pipefd[1], str, strlen(str)); //sleep(1); // int len = read(pipefd[0], buf, sizeof(buf)); // printf("child recv : %s, pid : %d\n", buf, getpid()); // bzero(buf, 1024); } } return 0; }
/* 实现 ps aux | grep xxx 父子进程间通信 子进程: ps aux, 子进程结束后,将数据发送给父进程 父进程:获取到数据,过滤 pipe() execlp() 子进程将标准输出 stdout_fileno 重定向到管道的写端。 dup2 */ #include <unistd.h> #include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <wait.h> int main() { // 创建一个管道 int fd[2]; int ret = pipe(fd); if(ret == -1) { perror("pipe"); exit(0); } // 创建子进程 pid_t pid = fork(); if(pid > 0) { // 父进程 // 关闭写端 close(fd[1]); // 从管道中读取 char buf[1024] = {0}; int len = -1; while((len = read(fd[0], buf, sizeof(buf) - 1)) > 0) { //判断buf是否有数据 // 过滤数据输出 printf("%s", buf); memset(buf, 0, 1024); //清空buf } wait(NULL); } else if(pid == 0) { // 子进程 // 关闭读端 close(fd[0]); // 文件描述符的重定向 stdout_fileno -> fd[1] dup2(fd[1], STDOUT_FILENO); // 执行 ps aux execlp("ps", "ps", "aux", NULL); perror("execlp"); exit(0); } else { perror("fork"); exit(0); } return 0; }
设置管道非阻塞:
#include <unistd.h> #include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> //设置flag的 /* 设置管道非阻塞 int flags = fcntl(fd[0], F_GETFL); // 获取原来的flag flags |= O_NONBLOCK; // 修改flag的值 fcntl(fd[0], F_SETFL, flags); // 设置新的flag */ int main() { // 在fork之前创建管道 int pipefd[2]; int ret = pipe(pipefd); if(ret == -1) { perror("pipe"); exit(0); } // 创建子进程 pid_t pid = fork(); if(pid > 0) { // 父进程 printf("i am parent process, pid : %d\n", getpid()); // 关闭写端 close(pipefd[1]); // 从管道的读取端读取数据 char buf[1024] = {0}; int flags = fcntl(pipefd[0], F_GETFL); // 获取原来的flag flags |= O_NONBLOCK; // 修改flag的值 fcntl(pipefd[0], F_SETFL, flags); // 设置新的flag 设置非阻塞 while(1) { int len = read(pipefd[0], buf, sizeof(buf)); printf("len : %d\n", len); printf("parent recv : %s, pid : %d\n", buf, getpid()); memset(buf, 0, 1024); //清空buf sleep(1); } } else if(pid == 0){ // 子进程 printf("i am child process, pid : %d\n", getpid()); // 关闭读端 close(pipefd[0]); char buf[1024] = {0}; while(1) { // 向管道中写入数据 char * str = "hello,i am child"; write(pipefd[1], str, strlen(str)); sleep(5); } } return 0; }
管道的读写特点: 使用管道时,需要注意以下几种特殊的情况(假设都是阻塞I/O操作) 1.所有的指向管道写端的文件描述符都关闭了(管道写端引用计数为0),有进程从管道的读端 读数据,那么管道中剩余的数据被读取以后,再次read会返回0,就像读到文件末尾一样。 2.如果有指向管道写端的文件描述符没有关闭(管道的写端引用计数大于0),而持有管道写端的进程 也没有往管道中写数据,这个时候有进程从管道中读取数据,那么管道中剩余的数据被读取后, 再次read会阻塞,直到管道中有数据可以读了才读取数据并返回。 3.如果所有指向管道读端的文件描述符都关闭了(管道的读端引用计数为0),这个时候有进程 向管道中写数据,那么该进程会收到一个信号SIGPIPE, 通常会导致进程异常终止。 4.如果有指向管道读端的文件描述符没有关闭(管道的读端引用计数大于0),而持有管道读端的进程 也没有从管道中读数据,这时有进程向管道中写数据,那么在管道被写满的时候再次write会阻塞, 直到管道中有空位置才能再次写入数据并返回。 总结: 读管道: 管道中有数据,read返回实际读到的字节数。 管道中无数据: 写端被全部关闭,read返回0(相当于读到文件的末尾) 写端没有完全关闭,read阻塞等待 写管道: 管道读端全部被关闭,进程异常终止(进程收到SIGPIPE信号) 管道读端没有全部关闭: 管道已满,write阻塞 管道没有满,write将数据写入,并返回实际写入的字节数
08 有名管道
09 有名管道的使用
mkfifo.c
/* 创建fifo文件 1.通过命令: mkfifo 名字 2.通过函数:int mkfifo(const char *pathname, mode_t mode); #include <sys/types.h> #include <sys/stat.h> int mkfifo(const char *pathname, mode_t mode); 参数: - pathname: 管道名称的路径 - mode: 文件的权限 和 open 的 mode 是一样的 是一个八进制的数 返回值:成功返回0,失败返回-1,并设置错误号 */ #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <stdlib.h> #include <unistd.h> int main() { // 判断文件是否存在 int ret = access("fifo1", F_OK); //查看文件是否存在 if(ret == -1) { printf("管道不存在,创建管道\n"); ret = mkfifo("fifo1", 0664); //第一个参数为路径 第二个参数为权限 if(ret == -1) { perror("mkfifo"); exit(0); } } return 0; }
read.c
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> // 从管道中读取数据 int main() { // 1.打开管道文件 int fd = open("test", O_RDONLY); if(fd == -1) { perror("open"); exit(0); } // 读数据 while(1) { char buf[1024] = {0}; int len = read(fd, buf, sizeof(buf)); if(len == 0) { //-1是调用失败 0是读完了,结束循环 printf("写端断开连接了...\n"); break; } printf("recv buf : %s\n", buf); } close(fd); return 0; }
write.c
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <string.h> // 向管道中写数据 /* 有名管道的注意事项: 1.一个为只读而打开一个管道的进程会阻塞,直到另外一个进程为只写打开管道 2.一个为只写而打开一个管道的进程会阻塞,直到另外一个进程为只读打开管道 读管道: 管道中有数据,read返回实际读到的字节数 管道中无数据: 管道写端被全部关闭,read返回0,(相当于读到文件末尾) 写端没有全部被关闭,read阻塞等待 写管道: 管道读端被全部关闭,进行异常终止(收到一个SIGPIPE信号) 管道读端没有全部关闭: 管道已经满了,write会阻塞 管道没有满,write将数据写入,并返回实际写入的字节数。 */ int main() { // 1.判断文件是否存在 int ret = access("test", F_OK); if(ret == -1) { printf("管道不存在,创建管道\n"); // 2.创建管道文件 ret = mkfifo("test", 0664); if(ret == -1) { perror("mkfifo"); exit(0); } } // 3.以只写的方式打开管道 int fd = open("test", O_WRONLY); if(fd == -1) { perror("open"); exit(0); } // 写数据 for(int i = 0; i < 100; i++) { char buf[1024]; sprintf(buf, "hello, %d\n", i); //流中写入hello printf("write data : %s\n", buf); write(fd, buf, strlen(buf)); sleep(1); } close(fd); return 0; }
实现聊天
chatA.c,
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <stdlib.h> #include <fcntl.h> #include <string.h> int main() { // 1.判断有名管道文件是否存在 int ret = access("fifo1", F_OK); if(ret == -1) { // 文件不存在 printf("管道不存在,创建对应的有名管道\n"); ret = mkfifo("fifo1", 0664); if(ret == -1) { perror("mkfifo"); exit(0); } } ret = access("fifo2", F_OK); if(ret == -1) { // 文件不存在 printf("管道不存在,创建对应的有名管道\n"); ret = mkfifo("fifo2", 0664); if(ret == -1) { perror("mkfifo"); exit(0); } } // 2.以只写的方式打开管道fifo1 int fdw = open("fifo1", O_WRONLY); if(fdw == -1) { perror("open"); exit(0); } printf("打开管道fifo1成功,等待写入...\n"); // 3.以只读的方式打开管道fifo2 int fdr = open("fifo2", O_RDONLY); if(fdr == -1) { perror("open"); exit(0); } printf("打开管道fifo2成功,等待读取...\n"); char buf[128]; // 4.循环的写读数据 while(1) { memset(buf, 0, 128); // 获取标准输入的数据 fgets(buf, 128, stdin); // 写数据 ret = write(fdw, buf, strlen(buf)); if(ret == -1) { perror("write"); exit(0); } // 5.读管道数据 memset(buf, 0, 128); ret = read(fdr, buf, 128); if(ret <= 0) { perror("read"); break; } printf("buf: %s\n", buf); } // 6.关闭文件描述符 close(fdr); close(fdw); return 0; }
chatB.c类似