帮助文档
专业提供香港服务器、香港云服务器、香港高防服务器租用、香港云主机、台湾服务器、美国服务器、美国云服务器vps租用、韩国高防服务器租用、新加坡服务器、日本服务器租用 一站式全球网络解决方案提供商!专业运营维护IDC数据中心,提供高质量的服务器托管,服务器机房租用,服务器机柜租用,IDC机房机柜租用等服务,稳定、安全、高性能的云端计算服务,实时满足您的多样性业务需求。 香港大带宽稳定可靠,高级工程师提供基于服务器硬件、操作系统、网络、应用环境、安全的免费技术支持。
服务器资讯 / 香港服务器租用 / 香港VPS租用 / 香港云服务器 / 美国服务器租用 / 台湾服务器租用 / 日本服务器租用 / 官方公告 / 帮助文档
【Linux】进程间通信——管道
发布时间:2024-03-10 12:31:49   分类:帮助文档
【Linux】进程间通信——管道 文章目录 进程间通信1.1进程间通信介绍1.2进程间通信目的1.3进程间通信分类 管道2.1管道介绍2.2匿名管道pipe读写特征管道特征 2.3命名管道mkfifo创建管道文件删除管道文件通信 总结 进程间通信 1.1进程间通信介绍 什么是进程间通信? 答:进程具有独立性,每个进程都有自己的PCB,所以进程间需要通信,并且通信的成本一定不低(通信的本质:OS需要直接或者间接给通信双方的进程提供“内存空间”,并且要通信的进程,必须看到一份公共的资源) 而我们所说的不同通信种类本质就是:上面所说的资源,是OS中的哪一个模块提供的。如文件系统提供的叫管道通信;OS对应的System V模块提供的… 📝ps:成本不低是因为我们需要让不同的进程看到同一份资源 1.2进程间通信目的 进程间通信的目的在于: 数据传输:一个进程需要将它的数据发送给另一个进程 资源共享:多个进程之间共享同样的资源 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程) 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程) 为什么要有进程间通信? 答:有时候我们需要多进程协同的,完成某种业务内容。比如管道。 1.3进程间通信分类 如何去通信? 答:1.采用标准的做法:System V进程间通信(聚焦在本地通信,如共享内存)、POSIX进程间通信(让通信过程可以跨主机)。 2.采用文件的做法:管道-基于文件系统(匿名管道、命名管道) 而本篇博客主要介绍管道,接着往下看把👇 管道 2.1管道介绍 管道是Unix中最古老的进程间通信的形式。我们把从一个进程连接到另一个进程的一个数据流称为一个"管道" 任何一个文件包括两套资源:1.file的操作方法 2.有属于自己的内核缓冲区,所以父进程和子进程有一份公共的资源:文件系统提供的内核缓冲区,父进程可以向对应的文件的文件缓冲区写入,子进程可以通过文件缓冲区读取,此时就完成了进程间通信,这种方式提供的文件称为管道文件。管道文件本质就是内存级文件,不需要IO。 两个进程如何看到同一个管道文件:fork创建子进程完成 管道创建时分别以读和写方式打开同一个文件(如果只读或者只写,子进程也只会继承只读或只写,父子双方打开文件的方式一样,无法完成单向通信);父进程创建子进程,父进程以读写打开,子进程也是以读写打开(一般而言,管道只用来进行单向数据通信);关闭父子进程不需要的文件描述符,完成通信: 管道分为匿名管道和命名管道 2.2匿名管道 我们通过文件名区分文件,但是如果当前进程的文件没有名字,这样的内存级文件称为匿名管道。让两个进程看到同一个文件,通过父进程创建子进程,子进程继承文件地址的方式,看到同一个内存级文件,此时内存级文件没有名称就是匿名管道了。匿名管道能用来父进程和子进程之间进行进程间通信。 pipe pipe:创建一个管道只需要调用pipe,注意头文件,返回值,以及函数的参数 头文件为#include ,调用成功返回0,调用失败返回-1。参数是输出型参数 SYNOPSIS #include int pipe(int pipefd[2]); DESCRIPTION pipe() creates a pipe,pipefd[0] refers to the read end of the pipe. pipefd[1] refers to the write end of the pipe. RETURN VALUE On success, zero is returned. On error, -1 is returned, and errno is set appropriately. 创建管道文件,打开读写端: #include #include #include using namespace std; int main() { int fds[2]; int n = pipe(fds); assert(n == 0); //0,1,2->3,4 //[0]:读取 [1]:写入 cout<<"fds[0]:"< #include #include #include #include using namespace std; int main() { int fds[2]; int n = pipe(fds); assert(n == 0); //fork pid_t id = fork(); assert(id>=0); if(id==0) { //子进程通信 exit(0); } //父进程通信 n = waitpid(id,nullptr,0); assert(n==id); return 0; } 关闭父子进程不需要的文件描述符,完成通信: 子进程写入,父进程读取: #include #include #include #include #include #include #include #include #include using namespace std; int main() { int fds[2]; int n = pipe(fds); assert(n == 0); //fork pid_t id = fork(); assert(id>=0); if(id==0) { //子进程通信:子进程进行写入,关闭读 close(fds[0]); //通信 const char*s = "这是子进程,正在进行通信"; int cnt = 0; while(true) { cnt++; char buffer[1024]; snprintf(buffer,sizeof buffer,"child->parent say:%s[%d][%d]",s,cnt,getpid()); //写端写满的时候,在写会阻塞,等对方进行读取 write(fds[1],buffer,strlen(buffer));//系统接口 sleep(1);//一秒写一次 } //退出前关闭子进程 close(fds[1]); exit(0); } //父进程通信:父进程进行读取,关闭写 close(fds[1]); //通信 while(true) { char buffer[1024]; //管道中如果没有数据,读端在读,默认会直接阻塞当前正在读取的进程 ssize_t s = read(fds[0],buffer,sizeof(buffer)-1); if(s>0) buffer[s] = 0; cout<<"Get Message# "< #include int mkfifo(const char *pathname, mode_t mode); RETURN VALUE On success mkfifo() returns 0. In the case of an error, -1 is returned (in which case, errno is set appropriately). 在当前路径下直接创建命名管道: mkfifo named_pipe 往管道文件写东西: 两个进程打开同一个文件:站在内核的角度,第二个文件不需要继续创建struct file对象,因为OS会识别到打开的文件被打开了。在内核中,此时就看到了同一份资源,有着操作方法和缓冲区,不需要把数据刷新到磁盘上去,不需要IO。所以无论是匿名还是命名,本质都是管道。 匿名管道通过继承的方式看到同一份资源。命名管道:通过让不同的进程打开指定名称(路径+文件名,具备唯一性)的同一个文件看到同一份资源。所以命名管道是通过文件的文件名来标定唯一性的。而匿名管道是通过继承的方式来标定的。 创建管道文件 准备工作 分为三个文件:comm.hpp:公共文件(同一份资源),server.cc:读取端,clinet.cc:写入端 在目录tmp下创建文件: server.cc: #include "comm.hpp" int main() { bool ret = createFifo(NAMED_PIPE); assert(ret); (void)ret; return 0; } comm.hpp: #pragma once #include #include #include #include #include #include #include #define NAMED_PIPE "/tmp/mypipe.name" bool createFifo(const std::string &path) { umask(0); int n = mkfifo(path.c_str(),0666); if(n==0) return true; else { std::cout<<"errno:"< int unlink(const char *path); RETURN VALUE Upon successful completion, 0 shall be returned. Otherwise, -1 shall be returned and errno set to indicate the error. If -1 is returned, the named file shall not be changed. 在comm.hpp中封装好删除的函数: void removeFifo(const std::string &path) { int n = unlink(path.c_str()); assert(n==0); (void)n;//防止n没使用而警告 } 在server.cc中进行调用: #include "comm.hpp" int main() { bool ret = createFifo(NAMED_PIPE); assert(ret); (void)ret; removeFifo(NAMED_PIPE); return 0; } 至此,创建和删除管道文件的操作我们实现完毕。下面进入通信环节 通信 其实在了解完了匿名管道之后,对于命名管道我们能够更好的理解: client.cc(写端): #include "comm.hpp" int main() { int wfd = open(NAMED_PIPE,O_WRONLY); if(wfd<0) exit(1); //write char buffer[1024]; while(true) { std::cout<<"Please Say:"; fgets(buffer,sizeof(buffer),stdin); //if(strlen(buffer)>0) buffer[strlen(buffer)-1] = 0; ssize_t n = write(wfd,buffer,strlen(buffer)); assert(n==strlen(buffer)); (void)n; } close(wfd); return 0; } server.cc(读端): #include "comm.hpp" int main() { bool ret = createFifo(NAMED_PIPE); assert(ret); (void)ret; int rfd = open(NAMED_PIPE,O_RDONLY); if(rfd<0) exit(1); //read char buffer[1024]; while(true) { ssize_t s = read(rfd,buffer,sizeof(buffer)-1); if(s>0) { buffer[s] = 0; std::cout<<"client->server" <