目录
系统中的进程都是独立的个体,拥有属于自己的用户空间,所以每个进程之间的数据是不共享的。在前面学习fork时,了解到父、子进程可以共享fork之前打开的文件描述符。那么现在我们思考一个问题:父进程现在想给子进程发送一个“hello world"字符串。可以采取哪种方式,我想大多数人会想到 借助文件传输这种办法:
- # include
- # include
- # include
- # include
- # include
-
- int main()
- {
- int fd=open("a.txt",O_RDWR);//读写打开
- assert(fd!=-1);
- char buff[128]={0};
- pid_t pid=fork();
-
- if(pid==0)
- {
- sleep(1);
- lseek(fd,0,SEEK_SET);
- int n=read(fd,buff,20);
- printf("buff:%s\n",buff);
- }
- else
- {
- int n= write(fd,"Hello,world!",20);
- }
- close(fd);
- exit(0);
- }
-
同时我们会发现这样存在一定的问题:
为了解决上述的问题,达到进程间任一两个进程进行数据交互通讯的目的,科学家们研究出了进程间通讯(IPC)的几种方式
同一主机下
无名管道
有名管道 信号 消息队列 信号量 共享内存 存储映射不同主机下
socket套接字
每个进程的空间地址是独立的,因此进程与进程之间是不能相互访问的,要进行进程间通讯,必须通过内核,内核会开辟一段特殊的内存空间,进程可以在这块内存空间进行数据的交换。
管道是一个重要的通信机制,思想是:在内存中创建一个共享文件,从而使通信双方利用这个共享文件来传递信息,由于这种方式具有单向传递数据的特点(),所以称为管道,即在某一时刻只能一端读数据一端写数据。

根据使用方式和通信对象将它分为无名管道,有名管道(命名管道)两类
管道正常情况下默认为无名管道(匿名管道)
无名管道是一种特殊类型的文件,在应用层体现打开的两个文件描述符。
无名管道的使用存在限制,它只能用于父,子进程之间,但是它却最常用,原因很简单:现在的项目都是由父进程创建子进程,替换为新代码来实现不同的操作。这样可以只创建一个进程,通过不断fork,execl进行不同功能的实现,所以无名管道最常用。
因为是父子之间,所以不用单独创建管道文件和打开文件,创建打开是一起的,故不用open操作,打开后就可以进行write,read等操作了。
无名管道没有文件名,存储在内核体和的一段内存中,通过借助这段内存完成进程间的通信,进程结束,管道也随之结束。
半双工,数据在同一时刻只能在一个方向上流动
数据只能从管道一端写入,从另一端读出
写入管道中的数据遵循先入先出的规则
管道所传送的数据是无格式的,这要求管道的读出方与写入方必须事先约定好数据的格式,如多少字节算一个消息
管道不是普通的文件,不属于某个文件系统,其只存在于内存中
管道在内存中对应一个缓冲区。不同的系统其大小不一定相同
从管道读取数据是一次性操作,数据一旦被读取走,它就从管道中被抛弃,释放空间,以便写入更多的数据
管道没有名字,只能在具有公共祖先的进程(父子,兄弟,具有亲缘关系)之间使用
存在阻塞方式
- #include
//头文件 -
- int pipe(int pipefd[2]); //函数
一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可以对管道进行操作,我们来具体看一下过程:
首先父进程pipe创建和打开管道,内核开辟一段内存,称为管道,用于通信,它有一个读端,一个写端,通过fd参数传给用户两个文件描述符,fd[0]指向管道的读端,fd[1]指向管道的写端。此时管道的两端通过进程相互连接,fd[1]写端指向fd[0]读端,数据通过内核开辟的管道流动:

当父进程fork之后。子进程复制父进程的所有文件描述符,父进程有fd[1]->fd[0],子进程也有fd[1]->fds[0],都通过管道进行连接:

因为管道是半双工通信,所以只能一端读,一端写。那么根据需求,将多余的连接关闭,如果我们让父进程写,子进程读,那么需要父进程关闭fd[0]读端,子进程关闭fd[1]写端:

所以注意:在使用无名管道时,必须事先确定谁发谁收的问题.
默认管道的读写两端都为阻塞模式。
阻塞模式下有两个特征:
当读管道时,如果管道中没有数据,则会阻塞,直到管道另一端写入数据。
当写管道时,如果管道中已经满了,则会阻塞,直到管道另一端读出数据(读出的数据会从管道中清除)。
设置非阻塞模式:



查看管道的缓冲区函数:

因为是用于父子进程,所以一个.c文件即可。我们实现:父子进程的数据交互,父进程给子进程发送信息,即在父进程种关闭读端,向管道中写入数据,子进程关闭写端,从管道中读取数据。
- # include
- # include
- # include
- # include
- # include
- # include
-
- int main(int argc,char const *argv[])
- {
- //创建一无名管道
- int fd[2];
- pipe(fd);
- //创建子进程
- //父进程发,子进程收
- pid_t pid = fork();
- assert(pid!=-1);
- if (pid == 0)//子进程
- {
- close(fd[1]);//关闭写端
- while(1)
- {
- char buff[128]={0};
- int accept=read(fd[0],buff,127);
- if(accept<=0)
- {
- break;
- }
- printf("son:%d success read %s\n",getpid(),buff);
- }
- //通信结束,关闭读端
- close (fd[0]);
- }
- else //父进程
- {
- //父进程读端无意义(可以关闭)
- close(fd[0]);
- //写端写入数据
- while(1)
- {
- char buff[128]={0};
- printf("father:%d write data:",getpid());
- fgets(buff,127,stdin);
- write(fd[1],buff,strlen(buff)-1);
- if(strncmp(buff,"over",4)==0)
- {
- break;
- }
- }
- //通信结束,关闭写端
- close (fd[1]);
- //等待子进程退出
- wait(NULL);
- }
- return 0;
- }

根据运行过程可以看到,在子进程输出之前会打出father pid input: ,原因是它们是两个独立的进程,都在并发运行,即父进程运行自己的,子进程也在运行自己的,子进程是buff中有数据打印,比父进程慢一点,所以每次都会输出father pid input.
read,write一样会阻塞,父进程不输入,子进程就不会输出。
C++网络通信中write和read的为什么会阻塞_
主要用于不相关的进程间通信
有名管道(FIFO)不同于无名管道之处在于它提供了一个路径名与之相关,以FIFO的文件形式存在于文件系统中,这样即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通过FIFO不相关的进程也能交换数据。
通过命令创建有名管道
通过函数创建有名管道
- #include
- #include
-
- int mkfifo(const char*pathname,mode_t mode);
-
- pathname -> 普通的路径名,也就是创建后FIFO的名字
- mode ->文件权限
- 与打开普通文件的open()函数中的mode参数相同。(0666)
-
- 返回值:
- 成功 :0
- 失败: 如果文件已存在,则会出错且返回-1
一旦用mkfifo创建一个FIFO管道文件,就可以用open打开它,用read,write等可以对文件进行操作。
我们来看一下有名管道是如何实现进程间的通信,假设现在又A,B两个进程,A进程向管道文件写数据,B进程从管道文件读数据,那么就有下面这张图:

我们需要清楚以下这几个点:
使用一下管道进行两个进程间的通讯:有write.c,read.c两个进程:
那么我们代码如下:
write.c
- # include
- # include
- # include
- # include
- # include
- #include
- #include
- int main(int argc,char *argv[])
- {
- //创建有名管道(保存两个进程,识别相同目录)
- mkfifo("my_fifo",0666);
- //open以写的方式打开有名管道(阻塞 到 对方 以读的方式打开)
- int fd=open("my_fifo",O_WRONLY);
- assert(fd!=-1);
- if(fd<0)
- {
- perror("open");
- return 0;
- }
- printf("write.c open success\n");
- //循环写入数据
- while(1)
- {
- //获取键盘输入
- char buff[128]=" ";
- printf("input data:");
- fgets(buff,127,stdin);
- //发送数据
- write(fd,buff,strlen(buff)-1);
- //退出循环
- if(strncmp(buff,"over",4)==0)
- {
- break;
- }
- }
- close(fd);
- return 0;
- }
read.c
- # include
- # include
- # include
- # include
- # include
- #include
- #include
- int main(int argc,char *argv[])
- {
- //创建有名管道(保存两个进程,识别相同目录)
- mkfifo("my_fifo",0666);
- //open以读的方式打开有名管道
- int fd=open("my_fifo",O_RDONLY);
- assert(fd!=-1);
- if(fd<0)
- {
- perror("open");
- return 0;
- }
- printf("read.c open success\n");
- //循环读取数据
- while(1)
- {
- char buff[128]=" ";
- //接收数据
- int n=read(fd,buff,strlen(buff)-1);
- if(n==0)
- {
- break;
- }
- printf("read:%s\n",buff);
- }
-
- close(fd);
- return 0;
- }
我们对代码进行下面几种方法的测试,分析出现的情况:
可以看到A进程阻塞,不能输出write进程的提示信息,这就表示open函数阻塞,因为我们是以只写的方式打开管道,read进程不运行表示没有进程来读取管道数据,管道只写不读,就没有意义,所以会一直阻塞。
2.先运行read进程,write进程不运行,会出现的情况:
和(1)的情况是一样的,会阻塞,直到write进程运行才解除阻塞。

通过这两个测试,我们直到任何一个进程先运行都不能正常运行,会出现阻塞,但我们要搞清楚一点,阻塞并不是说open函数会阻塞,而是操作的对象会阻塞,因为操作的是管道文件,只读/只写会导致无意义,阻塞,如果换成普通文件就不会阻塞。
3.同时运行两个进程,出现的情况:

可以看到两个进程运行,一个对管道写,一个从管道读,可以立即输出提示信息,open不会阻塞,输入一个数据,读出一个数据,不会存在B进程read空读的问题,它会一直阻塞着,直到A进程往管道里面写入一个数据,它才读一个数据,而A进程的write操作,也不会一直的让你写,而是你读一个我写一个。所以read,write会阻塞,这样就解决了读写混乱的问题。close关闭管道文件不会阻塞。
注意:
我们可以总结出有名管道需要注意的点:
了解一个命令:
ulimit -a可以显示当前的各种用户进程限制,包括块大小,创建进程数等。
越努力越幸运!