目录
- 前言
- 1. 进程间通信的方法
- 2. 管道的简单介绍
- 3. 匿名管道
- 4. 命名管道
- 5. 总结
前言
众所周知,进程运行是具有独立性的,想要进程间进行通信就要打破这种独立性,而进程间通信的本质其实是让不同的进程看见同一份资源!
本章重点:
本篇文章会介绍进程间通信中常见的几种方式,并且着重讲解匿名管道和命名管道的这两种通信手段的原理和代码的实现.
1. 进程间通信的方法
首先通信种类分为三大类:
- 管道
- system V
- POSIX
当然网络通信的本质其实也是进程间的通信,但是本篇文章的重点是利用管道通信!!!
2. 管道的简单介绍
首先要回答什么是管道,在学习Linux指令时我们使用过竖划线 | 也就是管道,把从一个进程连接到另一个进程的数据流称为“管道”
在管道通信中,管道的本质其实就是一个被系统打开的文件,然而用管道进行通信的本质就是让不同的进程看见相同的资源(文件).
3. 匿名管道
匿名管道的原理:
匿名管道的使用:
piped的底层就是open,参数是输出型参数,
不需要路径和文件名,所以叫做匿名管道.
对于匿名管道来说,通常用于父子,进程之间的通信,在父进程使用pipe创建好管道后,再使用fork创建子进程,此时子进程会将父进程的文件描述符表给拷贝过来,也就天然的拥有这两个管道文件了!
并且此时我们会面临两个问题:
1. 读取方将文件关闭了会发送什么?
2. 读取方在文件中没有数据时会干什么?
- 读端关闭后,写端进程会终止
- 当管道无数据时读端会阻塞
有了上面的经验后,现在可以编码验证一下:
int main()
{//创建管道int pipefd[2]={0}; //0下标表示读取端,1下标表示写入端int n = pipe(pipefd);assert(n!=-1);(void)n;
#ifdef DEBUG//条件编译cout<<"[0]: "<<pipefd[0]<<" "<<"[1]: "<<pipefd[1]<<endl;
#endif//创建子进程pid_t id = fork();assert(id!=-1);if(id==0)//子进程, 构建单向通信{close(pipefd[1]);char buffer[1024];while(1){ssize_t s = read(pipefd[0],buffer,sizeof(buffer)-1);if(s>0){buffer[s]=0;cout<<"father# "<<buffer<<endl;}else//read的返回值等于0代表父进程的管道文件已经close了{cout<<"写入结束,子进程退出";break;}}exit(0);}//父进程写入,子进程读取close(pipefd[0]);string str = "我在给子进程发信息";int count=0;char send_buffer[1024];while(count<=5){//构建一个变化的字符串snprintf(send_buffer, sizeof(send_buffer),"%s[%d]: %d",str.c_str(),getpid(),count++);//往缓冲区里写入数据//写入到管道中write(pipefd[1],send_buffer,sizeof(send_buffer));sleep(1);}close(pipefd[1]);pid_t ret = waitpid(id,NULL,0);assert(ret > 0);return 0;
}
总结管道的4种情况,5种特征:
4. 命名管道
匿名管道的一个限制就是必须是在有血缘关系的进程间才能通信,使用命名管道可以解决这个问题.
匿名管道和命名管道的区别:
- 匿名管道由pipe函数创建并打开。
- 命名管道由mkfifo函数创建,打开用open.
- FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义。
命名管道的打开规则:
命名管道简单通信的代码实现:
读端代码:
#define SIZE 1024
#define MODE 0666
string ipcpath = "./fifo.ipc";
static void getmessage(int fd)
{//编写通信代码char buffer[SIZE];while(1){memset(buffer,'\0',sizeof(buffer));ssize_t s = read(fd,buffer,sizeof(buffer)-1);if(s>0)//读取成功{cout<<"["<<getpid()<<"] "<<"client say: "<<buffer<<endl;}else if(s==0)//写端关闭,并且读到end{cerr<<"["<<getpid()<<"]"<<"read end"<<endl;break;}else//读取失败{perror("read");break;}}
}
int main()
{//创建管道文件int n = mkfifo(ipcpath.c_str(),MODE);if(n<0){perror("mkfifo");exit(1);}//文件操作int fd = open(ipcpath.c_str(),O_RDONLY);//这里必须等待client进程将此管道文件打开后才能执行下面的代码if(fd<0){perror("open");exit(2);}for(int i=0;i<4;i++){int id = fork();if(id==0){getmessage(fd);exit(0);}}close(fd);return 0;
}
写端代码:
#define SIZE 1024
#define MODE 0666
string ipcpath = "./fifo.ipc";
int main()
{//client不用自己创建管道文件,只需获取文件即可int fd = open(ipcpath.c_str(),O_WRONLY);if(fd<0){perror("open");exit(1);}//通信过程string buffer;while(1){cout<<"please Enter message: ";getline(cin,buffer);write(fd,buffer.c_str(),buffer.size());}close(fd);return 0;
}
5. 总结
虽然匿名管道和命名管道已经包含了对于有血缘关系和无血缘关系的进程的通信,但是毕竟调用read和write这些系统调用接口会不断的在用户态和内核态进行切换,比较消耗资源,所以管道不是最好的!