Nothing is ever a setback. If anghing, it just motivate you for what is next.--------没有挫折这回事,他若具有任何意义,那只是激励你再下一个挑战中表现更好.
文章目录
1,前景回顾
进程间的通信肯定是与进程相关,小黑在之前写过相关的博客,有兴趣的或者对这方面这知识有点生疏的同学可以移步:
https://blog.csdn.net/weixin_46027505/article/details/104812719
https://blog.csdn.net/weixin_46027505/article/details/105141592
2,管道真的就是管道
- 在介绍管道前有必要了解一下3种通信方式。
1,
单工通信:传输方向只有一个方向,单工通信只有一根数据线,它也只在一个方向上进行,如打印机、电视机等。
比如:电视机,广播
2,半双工通信:可以双向通信,但只能轮流传输,也只有一根数据线,不同于单工通信的是这根数据线即可作为发送又可作为接收,虽数据可在两个方向上传送,但通信双方不能同时收发数据。
比如:对讲机
3,全双工通信:可以同时双向传输数据,数据的发送和接收用两根不同的数据线,通信双方在同一时刻都能进行发送和接收,发送和接收同时进行,没有延迟。
比如视频聊天
2.1 管道通俗理解
- 为什么这种通信机制叫做管道呢? 因为它的功能真的就是生活中管道(水道)的作用。
我们把供水商比作一个进程空间,我们自己家是另外一个进程空间,管道就是用来送水的,水就是我们进程间通信传输的数据,往水管里注水就相当于向文件写入数据
这里需要记住,管道是单向的、先进先出的,这个容易理解,就像供水商给你送水时,你不能向这个水管注水。
2.2 为什么管道是半双工通信?
由于Linux一个命令只能完成一个功能,所以一个复杂点的任务需要好几个进程协同完成,第一个进程处理结果需交给第二个进程,然后一次交给第三个,等等,像流水线完成某个商品的生产一样,这个过程只需要数据单向往下传输,所以设计的时候做成了半双工。
当然也可以使用管道实现双工通信,需要两个管道。
2.3 管道特点
管道是,它把一个进程的输出和另一个进程的输入连接在一起。
1, 一个进程(写进程)在管道的尾部写入数据,另一个进程(读进程)从管道的头部读出数据。
管道是固定读端和写端的。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。
2, 数据被一个进程读出后,将被从管道中删除,其它读进程将不能再读到这些数据。
3, 管道提供了简单的流控制机制,进程试图读空管道时,进程将阻塞。同样,管道已经满时,进程再试图向管道写入数据,进程将阻塞
4,管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中。
5, 向管道中写入数据时,linux将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不读走管道缓冲区中的数据,那么写操作将一直阻塞。
6, 只有在管道的读端存在时,向管道中写入数据才有意义。否则,向管道中写入数据的进程将收到内核传来的SIFPIPE信号,应用程序可以处理该信号,也可以忽略(默认动作则是应用程序终止)。
- 当然管道有两种类型。
1,无名管道:这个只能用于父子进程,这个你可以理解成,你和你爸爸分家了,这个管道就是用来你们两家送水用的。因为这个管道你和爸爸
2,命名管道:可以用于同一系统中,任何进程间通信。
后面马上会介绍到。
3,无名管道(有时会直接叫做管道,不要混淆)
管道是UNIX系统IPC的最古老的形式,所有的UNIX系统都提供此种通信机制。管道的实质是一个内核缓冲区,进程以先进 先出(FIFO, First In First Out)的方式从缓冲区存取数据:管道一端的进程顺序地将进程数据写入缓冲区,另一端的进程则顺 序地读取数据,该缓冲区可以看做一个循环队列,读和写的位置都是自动增加的,一个数据只能被读一次,读出以后再缓冲区都 不复存在了。当缓冲区读空或者写满时,有一定的规则控制相应的读进程或写进程是否进入等待队列,当空的缓冲区有新数据写 入或慢的缓冲区有数据读出时,就唤醒等待队列中的进程继续读写。
3.1 局限性
(1)半双工,数据只能在一个方向流动,现在有些系统可以支持全双工管道,但是为了最佳的可移植性,应认为系统 不支持全双工管道;
(2)无名管道只能用于具有父子进程关系通信;
#3 3.2 无名管道的创建
管道可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不 属于其他任何文件系统,并且只存在于内存中。
管道是通过调用pipe函数创建的。
#include <unistd.h>
int pipe(int fd[2]);
- 返回值:若成功,返回0,若出错,返回-1.
经由参数fd返回的两个文件描述符:
fd[0]只为读而打开(用来在管道里取水)
fd[1]只为写而打开 (用来在管道里注水)
fd[1]的输出是fd[0]的输入
4,用无名管道实现单进程通信
其实管道就是文件,下面的示例代码就是自己给自己发送信息然后读出来。
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#define MSG "my name is xuxiaohei"
int main(int argc, char **argv)
{
int pipe_fd[1];
int rv;
char buf[512];
if (pipe(pipe_fd) < 0)
{
printf("Create pipe failure: %s\n", strerror(errno));
return -1;
}
memset(buf, 0, sizeof(buf));
if (write(pipe_fd[1], MSG, strlen(MSG)) < 0)
{
printf(" write data to pipe failure: %s\n", strerror(errno));
return -2;
}
rv = read(pipe_fd[0], buf, sizeof(buf));
if (rv < 0)
{
printf(" read from pipe failure: %s\n", strerror(errno));
return -3;
}
printf("process read %d bytes data from pipe: \"%s\"\n", rv, buf);
return 0;
}
5,用无名管道实现父子进程通信
下面编写一个例程,用于父进程给子进程方向发送数据。首先父进程创建管道之后fork(),这时子进程会继承父进程所有打开的 文件描述符(包括管道),这时对于一个管道就有4个读写端(父子进程各有一对管道读写端),如果需要父进程往子进程里写 数据,则需要在父进程中关闭读端,在子进程中关闭写端;而如果需要子进程往父进程中写数据,则可以在父进程关闭写端,然 后子进程中关闭读端。
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#define MSG_STR "This message is from father: Hello, my son!"
int main(int argc, char **argv)
{
int pipe_fd[1];
int rv;
int pid;
char buf[512];
int wstatus;
printf("before creat pipe\n");
if (pipe(pipe_fd) < 0)
{
printf("Create pipe failure: %s\n", strerror(errno));
return -1;
}
printf("before fork\n");
if ((pid = fork()) < 0)
{
printf("Create child process failure: %s\n", strerror(errno));
return -2;
}
else if (pid == 0)
{
printf("son start read message from father\n");
close(pipe_fd[1]);
memset(buf, 0, sizeof(buf));
rv = read(pipe_fd[0], buf, sizeof(buf));
if (rv < 0)
{
printf("Child process read from pipe failure: %s\n", strerror(errno));
return -3;
}
printf("Child process read %d bytes data from pipe: \"%s\"\n", rv, buf);
return 0;
}
close(pipe_fd[0]);
if (write(pipe_fd[1], MSG_STR, strlen(MSG_STR)) < 0)
{
printf("Parent process write data to pipe failure: %s\n", strerror(errno));
return -3;
}
printf("father finish write and wait son read \n");
wait(&wstatus);
return 0;
}
关闭管道的一端
(1)当读一个写端被关闭的管道时,在所有数据都被读取后,read返回0,表示文件结束;
(2)当写一个读端被关闭的管道时,则产生信号SIGPIPE,如果忽略该信号或者捕捉该信号并从其处理程序返回,则 wirte返回-1.
如果想要子进程给父进程发信息
则
6,有名管道FIFO(或者叫做命名管道)
当然,有名管道同样是半双工的。
前面讲到的未命名的管道只能在两个具有亲缘关系的进程之间通信,
通过命名管道(Named PiPe)FIFO,不相关的进程也能 交换数据。
它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信(能够访问该路径的进程以及FIFO的创建进程之间),它在磁盘上有对应的节点,但 没有数据块——换言之,只是拥有一个名字和相应的访问权限,通过mknode()系统调用或者mkfifo()函数来建立的。一旦建 立,任何进程都可以通过文件名将其打开和进行读写,而不局限于父子进程,当然前提是进程对FIFO有适当的访问权。当不再被 进程使用时,FIFO在内存中释放,但磁盘节点仍然存在。
因此,通过FIFO不相关的进程也能交换数据。值得注意的是,FIFO严格遵循先进先出(first in first out),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。它们不支持诸如lseek()等文件定位操作。
7,无名管道和有名管道的区别
两者之间最大的区别就是:
有名在任意两个进程间可以通信
无名只能在父子进程间通信
关于命名管道小黑另外写一篇博客实现一个小应用。
https://blog.csdn.net/weixin_46027505/article/details/105157072