尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

【Linux】两个毫无关系的进程怎么通信?命名管道 FIFO 从原理到 Server/Client 实战

【Linux】两个毫无关系的进程怎么通信?命名管道 FIFO 从原理到 Server/Client 实战 个人主页爱和冰阔乐专栏传送门《数据结构与算法》 、C学习方向C方向学习爱好者⭐人生格言得知坦然 失之淡然博主简介文章目录前言一、匿名管道缺的不是“管道”而是一个双方都认识的入口二、先用命令把 FIFO 的行为跑出来2.1 mkfifo2.2 为什么 echo fifo 会卡住三、FIFO 的 open() 规则比 read() 更早开始同步四、系统调用 mkfifo()五、用命名管道写一个最小 Server / Client5.1 Server5.2 Client六、Client 一退出Server 为什么会读到 0七、匿名管道和命名管道到底差在哪八、几个容易理解错的地方1. “命名管道是普通磁盘文件”2. “只要文件路径相同就一定是同一个打开文件对象”3. Server 卡住一定是 read()4. read() 读出来就是字符串总结前言前面学匿名管道时有一个很明显的限制父进程先 pipe()↓fork()↓子进程继承父进程的 fd↓父子进程看到同一条管道这套方法很好用但前提是两个进程之间能通过fork()建立文件描述符的继承关系。如果现在有两个完全独立启动的程序./server ./client它们没有父子关系也没有谁能把自己已经打开的匿名管道 fd “继承”给另一方。那怎么让它们找到同一份内核通信资源命名管道解决的正是“两个无亲缘关系进程怎样找到同一份通信资源”这个问题。一、匿名管道缺的不是“管道”而是一个双方都认识的入口匿名管道没有可供无关进程重新打开的路径。intpipefd[2];pipe(pipefd);pipe()返回的pipefd[0]、pipefd[1]只对当前进程以及后来继承这些 fd 的进程有意义。两个毫无关系的进程不可能提前知道对方进程里的某个 fd 是多少。命名管道换了一种思路不再靠 fd 继承找到同一条管道而是让两个进程约定同一个 pathname。例如/tmp/myfifoServer 和 Client 都去open(/tmp/myfifo, ...)内核就能把它们连接到同一个 FIFO 对象上。这里有个地方很容易理解偏。命名管道确实在文件系统中拥有路径、目录项和文件类型执行ls -l能看到它但真正传输的数据并不会像普通文件一样写进磁盘文件内容。这里记住路径存在于文件系统中 ↓ 用于让不同进程找到同一个 FIFO ↓ 通信数据仍然在内核管道机制中流动所以“有文件名”不等于“通信数据会落盘”。二、先用命令把 FIFO 的行为跑出来2.1mkfifo创建mkfifofifo查看ls-lfifo命名管道的文件类型通常会显示为p删除时直接rmfifo或者unlink fifo没必要写rm-rffifofifo不是目录-r在这里没有意义。2.2 为什么echo fifo会卡住先执行echohello fifofifo如果此时没有任何进程以读方式打开 FIFOShell 会停在那里。另开一个终端catfifo此时两边都会继续运行cat能读到hello fifo这一步最值得观察的是写端 open(O_WRONLY) │ │ 没有读端时等待 ▼ 读端 open(O_RDONLY) │ ▼ 双方建立通信关系三、FIFO 的open()规则比read()更早开始同步默认阻塞模式下打开方式另一端不存在时open(..., O_RDONLY)等待某个写端打开open(..., O_WRONLY)等待某个读端打开也就是说阻塞甚至可能发生在open()代码还没有走到read()或write()。如果使用O_NONBLOCK规则会变化O_RDONLY | O_NONBLOCK可以立即打开。而O_WRONLY | O_NONBLOCK如果一个读端都不存在会失败并得到ENXIO。排查 FIFO “程序一启动就卡住”时先看open()有没有返回。很多时候不是read()卡住而是open(FIFO_FILE,O_RDONLY);根本还没有返回。四、系统调用mkfifo()程序中创建 FIFO#includesys/types.h#includesys/stat.hintmkfifo(constchar*pathname,mode_t mode);成功返回0失败返回-1例如#includecerrno#includecstdio#includesys/stat.hconstchar*FIFO_FILE./myfifo;intmain(){umask(0);if(mkfifo(FIFO_FILE,0666)0){if(errno!EEXIST){perror(mkfifo);return1;}}return0;}Server 可能被重复启动所以遇到EEXIST时不要先认定程序出错。如果路径已经存在还应该确认它是不是我们期望的 FIFO而不是同名普通文件。五、用命名管道写一个最小 Server / ClientServer读端 Client写端5.1 Server#includecerrno#includecstdio#includecstdlib#includefcntl.h#includeiostream#includesys/stat.h#includeunistd.hconstchar*FIFO_FILE./myfifo;intmain(){umask(0);if(mkfifo(FIFO_FILE,0666)0errno!EEXIST){perror(mkfifo);return1;}std::cout等待 client 打开 FIFO...std::endl;intfdopen(FIFO_FILE,O_RDONLY);if(fd0){perror(open);unlink(FIFO_FILE);return2;}charbuffer[1024];while(true){ssize_t nread(fd,buffer,sizeof(buffer)-1);if(n0){buffer[n]\0;std::coutclient say# bufferstd::endl;}elseif(n0){std::cout所有写端已经关闭server 结束读取std::endl;break;}else{perror(read);break;}}close(fd);unlink(FIFO_FILE);return0;}这里顺手修正原稿中的一个细节。原来写的是buffer[n-1]0;改成buffer[n]\0;read()返回的n就是本次实际读到的有效字节数。如果你的目的只是给 C 字符串补结束符应该放在有效数据后一个位置。只有你明确知道最后一个字节一定是换行符并且想把换行删掉时才考虑改buffer[n - 1]。5.2 Client#includecstdio#includefcntl.h#includeiostream#includestring#includeunistd.hconstchar*FIFO_FILE./myfifo;intmain(){intfdopen(FIFO_FILE,O_WRONLY);if(fd0){perror(open);return1;}intcount1;pid_t pidgetpid();while(true){std::coutPlease Enter# ;std::string message;if(!std::getline(std::cin,message)){break;}message, message number std::to_string(count) [pidstd::to_string(pid)];ssize_t nwrite(fd,message.c_str(),message.size());if(n0){perror(write);break;}}close(fd);return0;}运行./server再打开另一个终端./client六、Client 一退出Server 为什么会读到0假设 Client 正常退出close(fd);如果系统中已经不存在任何写端那么 Server 继续read(fd,buffer,sizeof(buffer));当 FIFO 中剩余数据已经全部读完后read()返回0这就是 EOF。这里和匿名管道是一套规则有没有路径只决定双方怎么“找到”管道 读写端、EOF、SIGPIPE、字节流规则仍然属于管道语义七、匿名管道和命名管道到底差在哪匿名管道和命名管道的区别不用背很多条抓住“双方怎样找到同一条管道”最重要。对比匿名管道命名管道 FIFO创建方式pipe()mkfifo()是否有路径没有有常见使用关系父子等有亲缘关系进程无亲缘关系进程也可以数据是否落盘否否数据形式字节流字节流阻塞、EOF、SIGPIPE有有核心区别匿名管道通常靠fork()继承 fd命名管道靠双方打开同一个 pathname 建立联系。八、几个容易理解错的地方1. “命名管道是普通磁盘文件”不对。它在文件系统中有名字和类型但 FIFO 中传递的字节并不是普通文件内容。2. “只要文件路径相同就一定是同一个打开文件对象”也不能这么说。多个open()会形成各自的打开实例和 fd底层文件系统对象存在共享关系但不要简单写成“两个进程的struct file完全是同一个”。3. Server 卡住一定是read()不一定。读端open(path,O_RDONLY);在没有写端时就可能已经阻塞。4.read()读出来就是字符串不是。read()只认识字节不认识 C 字符串结尾的\0。所以buffer[n]\0;是用户代码自己补的。总结命名管道没有改变管道传输数据的本质它只是解决了匿名管道最麻烦的那个问题两个没有亲缘关系的进程 到底怎样找到同一条管道答案就是 pathname。Server 和 Client 约定./myfifo各自调用open()就能连接到同一条 FIFO。最后把这篇真正要记住的内容收一下mkfifo()创建的是 FIFO 特殊文件FIFO 数据不会作为普通文件内容刷到磁盘阻塞可能从open()就开始read()返回0表示所有写端关闭并且剩余数据已经读完命名管道和匿名管道的主要区别是“双方怎样找到同一资源”。下一篇继续沿着“让不同进程看到同一份资源”往下走不过不再借助文件描述符而是直接把同一批物理页映射进多个进程的地址空间——System V 共享内存。资源分享【Linux】从匿名管道到进程池任务派发、fd 继承 Bug 与完整实现【Linux】进程为什么不能直接通信匿名管道原理、fd 继承与读写规则【Linux】没有公网 IP 也能 SSH 回内网反向 SSH、ProxyJump 与 systemd 保活实战
返回列表