> 文章列表 > 进程间通信---管道

进程间通信---管道

进程间通信---管道

进程间通信


概念

进程间通信就是在不同进程之间传播或交换信息数据, 简称IPC(Interprocess communication).

意义
  • 数据传输, 资源共享
  • 事件通知, 进程控制
本质

让不同的进程看到同一份资源

管道


匿名管道

原理:

匿名管道仅限于本地父子进程之间的通信, 本质就是让两个父子进程先看到同一份被打开的文件资源, 然后父子进程就可以对该文件进行写入或是读取操作, 进而实现进程间通信.

pipe函数:

  • int pipe(int pipefd[2]); // #include <unistd.h>
  • pipefd: 输出型参数, 数组pipefd用于返回两个指向管道读端和写端的文件描述符. pipefd[0]是管道读端的文件描述符, pipefd[1]是管道写端的文件描述符.
  • return int: pipe函数调用成功时返回0, 调用失败时返回-1.
  • 注意: 管道的信道是半双工的.

使用(父进程读, 子进程写为例):

  1. 父进程通过调用pipe函数创建管道
  2. 父进程创建子进程
  3. 父进程关闭写端,子进程关闭读端
// 父进程读子进程写---父进程关闭写端,子进程关闭读端 
static void test_pipe1()
{// 1.父进程先调用pipe函数,创建匿名管道获取读写端的文件描述符int pipefd[2];memset(pipefd,0,sizeof(pipefd));int ret=pipe(pipefd);if(ret!=0){perror("pipe");exit(1);}// 2.父进程调用fork创建子进程, 在进程创建中子进程会继承父进程的struct file, // 所以父子进程的pipefd中文件描述符会标定同一个匿名管道pid_t id=fork();if(id<0){perror("fork");exit(2);}// 3.因为管道通信是半双工的, 所以需要根据需求关闭父子进程各一个fdif(0==id) {// child codeclose(pipefd[0]);int n=10;while(n--){char buffer[1024];sprintf(buffer,"hello father, I am child: %d",n);write(pipefd[1],buffer,strlen(buffer));sleep(1);}close(pipefd[1]);exit(0);}// father codeclose(pipefd[1]);while(true){char buffer[1024];memset(buffer,0,sizeof(buffer));ssize_t n=read(pipefd[0],buffer,sizeof(buffer)-1);if(n>0){buffer[n]='\\0';printf("chlid# %s\\n",buffer);}else if(0==n){printf("child exit");break;}else {perror("read");exit(3);}}close(pipefd[0]);exit(0);
}int main(int argc,char *argv[],char *env[])
{test_pipe1();return 0;
}

运行结果:

进程间通信---管道

四种特殊情况:

  1. 写端进程不写, 读端进程一直读, 那么此时会因为管道里面没有数据可读, 对应的读端进程会被挂起, 直到管道里面有数据后, 读端进程才会被唤醒.

  2. 读端进程不读, 写端进程一直写, 那么当管道被写满后, 对应的写端进程会被挂起, 直到管道当中的数据被读端进程读取后, 写端进程才会被唤醒.

  3. 写端进程将数据写完后将写端关闭, 那么读端进程将管道当中的数据读完后, 就会继续执行该进程之后的代码逻辑, 而不会被挂起.

  4. 读端进程将读端关闭, 而写端进程还在一直向管道写入数据, 那么操作系统会通过发送SIGPIPE信号给写端进程从而将写端进程杀掉.

验证第4种情况, 当写端进程一直往匿名管道写时, 读端进程把匿名管道对应的读取文件描述符关闭, 写端进程会收到什么信号(父进程写, 子进程读为例):

void sigpipe_handler(int signum)
{printf("收到信号: %d\\n",signum);exit(4);
}// 父进程写子进程读---父进程关闭读端,子进程关闭写端
static void test_pipe2()
{// 1.pipeint pipefd[2];int ret=pipe(pipefd);if(ret<0){perror("pipe");exit(1);}// 2.forkpid_t id=fork();if(id<0){perror("fork");exit(2);}if(0==id){// child codeclose(pipefd[1]); // 子进程关闭写端int n=3;while(n--){char buffer[1024];ssize_t n=read(pipefd[0],buffer,sizeof(buffer)-1);if(n>0){buffer[n]='\\0';printf("father# %s\\n",buffer);// sleep(1);}else if(0==n) {printf("father close\\n");exit(0);}else{perror("read");exit(3);}}close(pipefd[0]);exit(0);}// father codeclose(pipefd[0]); // 父进程关闭读端// 父进程对SIGPIPE信号进行捕获signal(SIGPIPE,sigpipe_handler);// 父进程一直往匿名管道中写入数据int count=0;while(true){char buffer[1024];sprintf(buffer,"I am father, pid: %d, count %d\\n",getpid(),count++);ssize_t n=write(pipefd[1],buffer,strlen(buffer));if(n>0){// printf("write n>0: n: %d\\n",n);usleep(5000); // 父进程写慢点}   else if(0==n){printf("write 0==n: n: %d\\n",n);break;}else{perror("write");exit(5);}}close(1);exit(0);
}

运行结果:

进程间通信---管道

测试管道的大小:

// 测试管道的大小,父进程一直不读,子进程一直写
static void test_pipe_size()
{// 1.pipeint pipefd[2];int ret = pipe(pipefd);if (ret < 0){perror("pipe");exit(1);}// 2.forkpid_t id = fork();if (id < 0){perror("fork");exit(2);}if (0 == id){// child codeclose(pipefd[0]); // 子进程关闭写端// 子进程一直往匿名管道中写int count = 0; // 记录写入了多少个字节数while (true){char one_byte = '$';ssize_t n = write(pipefd[1], &one_byte, 1);if (n > 0){printf("count: %d bytes\\n", ++count);}}close(pipefd[1]);exit(0);}// father codeclose(pipefd[1]); // 父进程关闭读端// 父进程一直不从匿名管道中读for(;;){}close(pipefd[0]);exit(0);
}

结论:我当前Linux版本(Linux VM-12-12-centos 3.10.0-1160.62.1.el7.x86_64 #1 SMP Tue Apr 5 16:57:59 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux)中管道的最大容量是65536字节. 当管道写满时, 写端进程继续写则会进入阻塞状态.

进程间通信---管道

命名管道

原理:

想要实现本地任意两个进程之间的通信, 可以通过命名管道来实现. 命名管道是一种特殊类型的文件(管道文件), 两个进程通过命名管道的文件路径打开同一个管道文件, 此时这两个进程也就看到了同一份资源,进而就可以进行进程间通信了。

mkfifo函数:

int mkfifo(const char *pathname, mode_t mode);

pathname: 根据传入pathname所指定的目录下创建命名管道文件, 默认在当前进程的工作目录下创建.

  1. /home/yx/code/ipc_fifo/fifofile: 表示在/home/yx/code/ipc_fifo目录下创建fifofile文件
  2. fifofile: 表示在默认目录下创建fifofile文件

mode: 表示创建命名管道文件的默认权限, 受usmask权限掩码影响, 实际权限=mode&(~umask);

return int: 创建成功返回0, 创建失败返回-1.

使用:

comm.h

#ifndef __COMM_H__
#define __COMM_H__#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>const char * FIFOFILEPATH="fifofile";
const int DEFAULTMODE=0666;
const int BUFFERSIZE=1024;#endif

fifoServer.c

#include "comm.h"static void server()
{// 1.调用mkfifo创建管道文件int ret=mkfifo(FIFOFILEPATH,DEFAULTMODE);if(ret<0) // 成功返回0,失败返回-1{perror("mkfifo");exit(1);}// 2.调用open以读的方式打开管道文件int fifo_fd=open(FIFOFILEPATH,O_RDONLY);if(fifo_fd<0){perror("open");exit(2);}// 3.调用read从管道文件中读取数据char buffer[BUFFERSIZE];while(true){ssize_t n=read(fifo_fd,buffer,sizeof(buffer)-1);if(n>0){buffer[n]='\\0';printf("%s\\n",buffer);} else if(n==0){printf("client exit\\n");break;}else{perror("read");close(fifo_fd); // 关闭fifo_fd文件描述符unlink(FIFOFILEPATH); // 删除管道文件exit(4);}}close(fifo_fd);unlink(FIFOFILEPATH);exit(0);
}int main()
{server();return 0;
}

fifoClient.c

#include "comm.h"static void client()
{// 1.调用open以写的方式打开管道文件int fifo_fd=open(FIFOFILEPATH,O_WRONLY);if(fifo_fd<0){perror("open");exit(1);}// 2.调用write往管道文件中写入数据char buffer[BUFFERSIZE];while(true){// 从0号文件描述符读取数据zprintf("client# ");fflush(stdout);ssize_t n=read(0,buffer,sizeof(buffer)-1);buffer[n]='\\0';// 将读取到的数据往管道文件中写入n=write(fifo_fd,buffer,strlen(buffer));if(n>0){printf("write success n: %d\\n",n);}else if(n==0){// 读端关闭exit(2);}else{// 写入错误exit(3);}}}int main()
{client();return 0;
}

测试:

服务端先启动, 紧接着启动客户端. 客户端发送hello server, 可以看到服务端可以收到, 也即通过命名管道完成进程间通信. 客户端(写端)退出, 服务端(读端)也退出.
进程间通信---管道