- 写入端关闭,则读取到0(管道末尾)读取端关闭
文件test.c
1 #include<stdio.h> 2 #include<unistd.h> 3 #include<assert.h> 4 #include<sys/types.h> 5 #include<sys/stat.h> 6 #include<fcntl.h> 7 #include<sys/wait.h> 8 #include<string.h> 9 #include<stdlib.h> 10 int main() 11 { 12 int fds[2]; 13 int n = pipe(fds); 14 assert(n == 0); 15 pid_t id = fork(); 16 assert(id >= 0); 17 if(id == 0)//子进程 18 { 19 //子进程通信,关闭子进程的读取端,即子进程进行写入 20 close(fds[0]); 21 const char* s = "你好,我是子进程,正在进行通信"; 22 int cnt = 0; 23 while(1) 24 { 25 cnt++; 26 char buffer[1024]; 27 snprintf(buffer, sizeof buffer, "child -> parent say:%s [%d], [%d]",s,cnt,getpid()); 28 write(fds[1], buffer, strlen(buffer)); 29 printf("count: %d\n",cnt); 30 break; 31 } 32 close(fds[1]);//退出子进程前关闭文件写入端 33 exit(0); 34 } 35 //父进程 36 close(fds[1]);//父进程关闭写入端,即父进程进行读取 37 while(1) 38 { 39 sleep(2);//父进程不读 40 char buffer[1024]; 41 ssize_t s = read(fds[0], buffer, sizeof(buffer) - 1); 42 if(s > 0) 43 { 44 buffer[s] = 0; 45 printf("Get Message : %s | mypid = %d\n", buffer, getpid()); 46 } 47 else if(s == 0)//写入端关闭,读到文件末尾了 48 { 49 printf("read: %d\n", s); 50 break;//关闭读取端 51 } 52 } 53 n = waitpid(id, NULL, 0); 54 assert(n == id); 55 close(fds[0]);//退出程序前,关闭读取端 56 return 0; 57 }
4. 读取端关闭,写入端直接关闭
关闭读取端后,写入端就没有意义了,因此OS会给写入的进程发送信号,终止该进程。
1 #include<stdio.h> 2 #include<unistd.h> 3 #include<assert.h> 4 #include<sys/types.h> 5 #include<sys/stat.h> 6 #include<fcntl.h> 7 #include<sys/wait.h> 8 #include<string.h> 9 #include<stdlib.h> 10 int main() 11 { 12 int fds[2]; 13 int n = pipe(fds); 14 assert(n == 0); 15 pid_t id = fork(); 16 assert(id >= 0); 17 if(id == 0)//子进程 18 { 19 //子进程通信,关闭子进程的读取端,即子进程进行写入 20 close(fds[0]); 21 const char* s = "你好,我是子进程,正在进行通信"; 22 int cnt = 0; 23 while(1) 24 { 25 cnt++; 26 char buffer[1024]; 27 snprintf(buffer, sizeof buffer, "child -> parent say:%s [%d], [%d]",s,cnt,getpid()); 28 write(fds[1], buffer, strlen(buffer)); 29 printf("count: %d\n",cnt); 30 } 31 close(fds[1]);//退出子进程前关闭文件写入端 32 printf("子进程关闭写入端\n"); 33 exit(0); 34 } 35 //父进程 36 close(fds[1]);//父进程关闭写入端,即父进程进行读取 37 while(1) 38 { 39 sleep(2);//父进程不读 40 char buffer[1024]; 41 ssize_t s = read(fds[0], buffer, sizeof(buffer) - 1); 42 if(s > 0) 43 { 44 buffer[s] = 0; 45 printf("Get Message : %s | mypid = %d\n", buffer, getpid()); 46 } 47 break;//关闭读取端 48 } 49 close(fds[0]);//退出程序前,关闭读取端 50 printf("父进程关闭读取端\n"); 51 n = waitpid(id, NULL, 0); 52 assert(n == id); 53 return 0; 54 }
管道特征
- 管道的生命周期是根据进程的,进程退出,则管道释放;
- 管道可以用来进行具有血缘关系的进程间通信(常用于父子进程);
- 管道是面向字节流的;
- 半双工——单向管道(特殊);
- 互斥与同步机制——对共享资源进行保护的方案。
2.命名管道
匿名管道的限制就是只能在具有血缘关系的进程间通信,那么如果是两个毫不相干的进程间通信交互呢?
如果我们想要在两个不相关的进程之间进行通信,我们可以使用FIFO文件,它被称为命名管道。(命名管道是一种特殊类型的文件)
mkfifo
在当前路径下创建命名管道:
mkfifo named_pipe
创建管道文件
comm.hpp文件(同一份资源)
1 #pragma once 2 #include<iostream> 3 #include<sys/types.h> 4 #include<sys/stat.h> 5 #include<fcntl.h> 6 #include<cassert> 7 #include<cstring> 8 #include<cerrno> 9 using namespace std; 10 #define NAMED_PIPO "/tmp/mypipe.name" 11 #include<string> 12 bool createFIFO(const string &path) 13 { 14 umask(0); 15 int n = mkfifo(path.c_str(), 0666); 16 if(n ==0) return true; 17 else 18 { 19 cout<<"errno:"<<errno<<"err string:"<<strerror(errno)<<endl; 20 } 21 return false; 22 }
server.cc文件(读取端)
1 #include"comm.hpp" 2 int main() 3 { 4 bool ret = createFIFO(NAMED_PIPO); 5 assert(ret == true); 6 (void)ret; 7 return 0; 8 }
删除管道文件
unlink
头文件:unistd.h
;参数为const char* path;返回值:如果删除成功返回0,如果删除失败返回-1
使用:
在comm.hpp中封装删除函数
23 void removeFIFO(const string &path) 24 { 25 int n = unlink(path.c_str()); 26 assert(n == 0); 27 (void)n;//避免因为没有使用n导致爆警告 28 }
在文件server中调用删除函数
1 #include"comm.hpp" 2 int main() 3 { 4 bool ret = createFIFO(NAMED_PIPO); 5 assert(ret == true); 6 (void)ret; 7 8 removeFIFO(NAMED_PIPO); 9 return 0; 10 }
到此管道文件的创建和删除就完成了,接下来我们进入通信阶段:
通信
clinet.cc文件(写入端)
1 #include"comm.hpp" 2 int main() 3 { 4 printf("HHHHHH\n"); 5 int wfd = open(NAMED_PIPO, O_WRONLY); 6 if(wfd < 0) exit(1); 7 char buffer[1024]; 8 while(1) 9 { 10 cout<<"Please say:"; 11 fgets(buffer, sizeof buffer, stdin); //if(strlen(buffer) > 0) buffer[strlen(buffer) - 1] = 0; 12 ssize_t s = write(wfd, buffer, strlen(buffer)); 13 assert(s == strlen(buffer)); 14 (void)s; 15 } 16 close(wfd); 17 return 0; 18 }
server.cc文件(读取端)
1 #include"comm.hpp" 2 int main() 3 { 4 bool ret = createFIFO(NAMED_PIPO); 5 assert(ret); 6 (void)ret; 7 int rfd = open(NAMED_PIPO, O_RDONLY); 8 if(rfd < 0) exit(1); 9 10 char buffer[1024]; 11 while(1) 12 { 13 ssize_t s = read(rfd, buffer, sizeof(buffer) - 1); 14 if(s > 0) 15 { 16 buffer[s] = 0; 17 cout<<"client -> server"<< buffer<<endl; 18 } 19 else if(s == 0) 20 { 21 cout<<"client quit. me, too"<<endl; 22 break; 23 } 24 else 25 { 26 cout<<"err string:"<<strerror(errno)<<endl; 27 break; 28 } 29 } 30 removeFIFO(NAMED_PIPO); 31 return 0; 32 }
comm.hpp文件(同一份资源)
1 #pragma once 2 #include<iostream> 3 #include<sys/types.h> 4 #include<sys/stat.h> 5 #include<fcntl.h> 6 #include<cassert> 7 #include<cstring> 8 #include<cerrno> 9 #include<unistd.h> 10 #include<stdlib.h> 11 #include<stdio.h> 12 using namespace std; 13 #define NAMED_PIPO "/tmp/mypipe.name" 14 #include<string> 15 bool createFIFO(const string &path) 16 { 17 umask(0); 18 int n = mkfifo(path.c_str(), 0666); 19 if(n ==0) return true; 20 else 21 { 22 cout<<"errno:"<<errno<<"err string:"<<strerror(errno)<<endl; 23 } 24 return false; 25 } 26 void removeFIFO(const string &path) 27 { 28 int n = unlink(path.c_str()); 29 assert(n == 0); 30 (void)n;//避免因为没有使用n导致爆警告 31 }
运行:
我们发现读端每次会多打印一行空白,实际上是将写入端最后的Enter也作为数据传送了,因此我们将写端缓冲区的最后一个位置内容改为0即可。
if(strlen(buffer) > 0) buffer[strlen(buffer) - 1] = 0;
三、匿名管道和命名管道的区别和联系
它们都是往管道文件里写东西,
两个进程打开同一个文件:站在内核的角度,第二个文件不需要继续创建struct file对象,因为OS会识别到需要打开的文件已经被打开了。在内核中,两个进程此时就看到了同一份资源,有对应文件的操作方法和缓冲区,不需要将数据刷新到磁盘上(不需要IO),所以无论是匿名管道还是命名管道,本质都是内存级文件。
匿名管道是通过继承的方式让两个进程看到一个文件(资源),命名管道是通过让不同的进程打开指定名称(路径+文件名)的文件,来看待同一份资源。所以命名管道是通过文件的文件名来唯一标定资源的唯一性,而匿名管道是通过继承的方式来标定的。
总结
以上就是今天要讲的内容,本文介绍了进程间通信——管道的相关概念。
本文作者目前也是正在学习Linux相关的知识,如果文章中的内有错误或者不严谨的部分,欢迎大家在评论区指出,也欢迎大家在评论区提问、交流。
最后,如果本篇文章对你有所启发的话,希望可以多多支持作者,谢谢大家!