(一)进程间通讯方式----管道

管道是第一个广泛应用的IPC(进程间通信)手段。

管道特点:

  • 管道以文件作为交互的媒介
  • 管道分为有名管道无名管道
  • 管道都是半双工通讯
  • 管道按照队列的先进先出读取数据
  • 管道中的read/write函数都是阻塞的,与普通文件不同

 有名管道

  • 适用于任意进程之间的通讯
  • 要创建一个管道文件作为交互的媒介
  • FIFO(管道文件)在磁盘上没有数据块,仅用inode来标识内核中的一条通道,与普通文件不同。文件属性类型标识为p表示FIFO,文件大小为0。各进程可以打开这个文件进行read/write,实际上是在读写内核通道(根本原因在于这个file结构体所指向的read、write函数和常规文件不一样),这样就实现了进程间通信。
  • 该管道可以通过路径名来指出,并且在文件系统中是可见的.在建立了管道之后,两个进程就可以把它当作普通文件一样进行读写操作,使用非常方便.
  • FIFO严格地遵循先进先出规则,对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾,它们不支持如lseek()等文件定位操作
  • FIFO提供了一个路径名与之关联,以FIFO的文件形式存在于文件系统中.这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能彼此通过FIFO相互通信(能够访问该路径的进程以及FIFO的创建进程之间),因此,通过FIFO,不相关的进程也能交换数据.
  • FIFO可以一个读端,多个写端;也可以一个写端,多个读端
  • FIFO文件数据一旦被读,就“不存在”了

有名管道的创建

//可以通过mkfifo命令创建一个管道文件用于操作
//也可以通过mkfifo()创建有名管道,可以指定管道的路径和打开的模式.mkfifo()函数语法如下:
#include<sys/types.h>
#include<sys/stat.h>

int mkfifo(const char *filename,mode_t mode);   //pathname管道文件的路径名,mkfifo()会依参数pathname建立特殊的FIFO文件,该文件必须不存在,而参数mode为该文件的权限位数字,若成功则返回0,否则返回-1,错误原因存于errno中。

ps:mkfifo函数前要使用unlink(filename);
eg:unlink("FIFO");
    mkfifo("FIFO",0777);

 注意:

下面我们来一个有名管道的例子:

 执行代码之前先用命令在当前的文件夹创建命名管道文件(也可以自己调用函数实现,在这里我用命令创建。)

write.c

int main()
{
	int fd=open("./FIFO",O_WRONLY);  //打开当前目录下的FIFO管道文件
	assert(fd!=-1);
	printf("OPEN FIFO SUCCESS\n");

	write(fd,"HELLO",6);
	printf("WRITE OVER\n");

	close(fd);
	return 0;
}

read.c

int main()
{
    int fd=open("./FIFO",O_RDONLY);
    assert(fd!=-1);
    printf("open success\n");

    char buff[128]={0};
    read(fd,buff,127);
    printf("%s",buff);
    close(fd);
    return 0;
    
}

无名管道

  • 只适用于父子进程之间的通讯
  • 并没有管道文件作为交互的媒介,而是在内核中开辟一块缓冲区(ubantu系统上管道大小是64K)并且也是用环形队列来实现,写端是入队,读端是出队操作,因此操作遵循队列原则总是按照先进先出的原则工作。
  • 因为其也是一个半双工通讯,所以具有固定的读端和写端
  • 管道也可以看成是一种特殊的文件,对于它的读写也可以使用普通的read(),write()等函数.但是它不是普通的文件,并不属于其他任何文件系统
  • 流式服务。发送和接收大小不受特定格式的限制。

  • 管道的生命周期和进程有关。

  • 同步与互斥原则

无名管道的创建 

管道是基于文件描述符的通信方式,当一个无名管道建立时,它会创建两个文件描述符fds[0]和fds[1],其中fds[0]固定用于读管道,而fd[1]固定用于写管道,这样就构成了一个半双工的通道.管道关闭时只需将这两个文件描述符关闭即可,可使用普通的close()函数逐个关闭各个文件描述符.

//创建管道可以通过调用pipe()来实现
#include<unistd.h>
int pipe(int fds[2]);//fds[2]是管道的两个文件描述符,之后可以直接操作这两个文件描述符,实际上pipe包含创建与打开一体

两个进程通过一个pipe管道只能实现单向通信,所以我们就需要在进行读操作的进程中关闭其写操作的文件描述符,在进行写操作的进程中关闭读操作的文件描述符。举例:子进程读父进程写,就要关闭父进程的读操作文件描述符fds[0],关闭子进程的写操作文件描述符fds[1]。但如果有时候也需要子进程写父进程读,就必须另开一个管道。

如果只开一个管道,但是父进程不关闭读端,子进程也不关闭写端,双方都有读端和写端,为什么不能实现双向通信?
因为俩进程同时读/写,会造成数据混乱,因为读写指针只有一个,但我们并不能保证读写的顺序。

示例代码:

int main()
{
	int fds[2];
	pipe(fds);  //创建并打开管道
	pid_t pid=fork();
	if(pid==0)  //我们这里举例子进程读,父进程写
	{
		close(fds[1]);   //子进程关闭写端
		char buff[128]={0};
  		int n=read(fds[0],buff,127);
		printf("%s\n",buff);
		close(fds[0]);
	}
	else
	{
		close(fds[0]);  //父进程关闭读端
		write(fds[1],"hello",5);
		wait(NULL);   //等待子进程结束,以免出现僵尸进程
		close(fds[1]);
	}
	exit(0);
}

无名管道的集中特殊情况:

1:当读一个写端被关闭的管道时,在所有数据被读取完成后,read返回0,以指示达到了文件结束处。

2:当读一个写段未被关闭的管道,但写段也并没有数据写入时,读进程从管道中将数据读完后并不会返回0,而是read阻塞,直到管道中有数据了读取后才会返回

3:当写一个读端被关闭的管道时,则写段进程会收到信号SIGPIPE,通常会导致进程异常终止,如果忽略该信号或者捕捉该信号并从其处理程序返回,则write返回-1。

4:当写一个读端未被关闭的管道,且读端并不会读取管道中的数据,那么写端就一直往管道中写,直到管道写满,write就会阻塞,直到管道中有空位才写入数据并返回。

猜你喜欢

转载自blog.csdn.net/Eunice_fan1207/article/details/83041016