Linux学习记录--命名管道通信

命名管道通信

什么是命名管道

一个主要的限制是,它是匿名管道的应用还没有名字,因此,只有它可以用于进程间通信的方式与亲缘关系。在命名管道(named pipeFIFO)提出后,该限制得到了克服。FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。

这样,即使与FIFO的创建进程不存在亲缘关系的进程,仅仅要可以訪问该路径,就行彼此通过FIFO相互通信

 

有名管道创建

int mkfifo(const char * pathname, mode_t mode)

 和普通文件创建一样pathname为文件名,mode为权限

有名管道通信规则

管道关闭规则

intclose (int __fd);

1.当最后一个读进程管道关闭时。写进程不管是堵塞还是非堵塞,都会将管道写满(假设能写满)并退出

2.当最后一个写进程管道关闭时,向管道写入一个结束标识,当读进程从管道读到这个结束标识时。假设是堵塞读进程将结束堵塞返回读入数据个数为0.(对于未堵塞读进程假设管道内没有数据则返回-1,假设读到结束标识则返回读入数据个数为0

 

规则分析1

写进程:

#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<fcntl.h>
#include<limits.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<errno.h>

#define FIFO_NAME "/tmp/my_fifo"
#define BUF_SIZE 80000
intmain(int argc,char *argv[])
{
	unlink(FIFO_NAME);
int pipe_fd;
int res;
char buf[BUF_SIZE];
memset(buf,3,BUF_SIZE);
if(access(FIFO_NAME,F_OK)==-1)
    {
        res=mkfifo(FIFO_NAME,0766);
if(res!=0)
        {
fprintf(stderr,"不能创建管道文件 %s
",FIFO_NAME);
exit(1);
        }
    }

printf("进程PID %d 打开管道 O_WRONLY
",getpid());
    pipe_fd=open(FIFO_NAME,O_WRONLY);

if(pipe_fd!=-1)
    {
        res=write(pipe_fd,buf,sizeof(buf));
printf("写入数据的大小是%d 
",res);
close(pipe_fd);
sleep(1000);
    }
else
exit(1);
exit(1);
}

读进程

#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<fcntl.h>
#include<limits.h>
#include<sys/types.h>
#include<sys/stat.h>

#define FIFO_NAME "/tmp/my_fifo"
#define BUF_SIZE 20

intmain(int argc, char *argv[]) {
	char buf[BUF_SIZE];
	memset(buf, 0, BUF_SIZE);
	int pipe_fd;
	int res;
	int bytes_read = 0;

	printf("进程PID %d 打开管道 O_RDONLY
", getpid());
	pipe_fd = open(FIFO_NAME, O_RDONLY);

	if (pipe_fd != -1) {
		bytes_read = read(pipe_fd, buf, sizeof(buf));
		printf("读入数据的大小是%d 
", bytes_read);
		sleep(10);
		close(pipe_fd);
	} else
		exit(1);
	exit(1);
}

控制台信息

 

读进程:

进程PID 10930打开管道 O_RDONLY

读入数据的大小是20

 

写进程:

进程PID 10918打开管道 O_WRONLY

10S后输出…..

写入数据的大小是65536

 

分析:当读进程运行到close(pipe_fd);时,写进程一次性将数据写满缓冲区(65536)并退出。

 

规则分析2

写进程

#define FIFO_NAME "/tmp/my_fifo"
#define BUF_SIZE 80000
int main(int argc,char *argv[])
{
	unlink(FIFO_NAME);
int pipe_fd;
int res;
char buf[BUF_SIZE];
memset(buf,3,BUF_SIZE);
if(access(FIFO_NAME,F_OK)==-1)
    {
        res=mkfifo(FIFO_NAME,0766);
if(res!=0)
        {
fprintf(stderr,"不能创建管道文件 %s
",FIFO_NAME);
exit(1);
        }
    }

printf("进程PID %d 打开管道 O_WRONLY
",getpid());
    pipe_fd=open(FIFO_NAME,O_WRONLY);

if(pipe_fd!=-1)
    {
        res=write(pipe_fd,buf,sizeof(buf));
printf("写入数据的大小是%d 
",res);
sleep(10);
close(pipe_fd);

    }
else
exit(1);
exit(1);
}

 

读进程

#define FIFO_NAME "/tmp/my_fifo"
#define BUF_SIZE 4000

int main(int argc, char *argv[]) {
	char buf[BUF_SIZE];
	memset(buf, 0, BUF_SIZE);
	int pipe_fd;

	int bytes_read = 0;

	printf("进程PID %d 打开管道 O_RDONLY
", getpid());
	pipe_fd = open(FIFO_NAME, O_RDONLY);

	if (pipe_fd != -1) {

		do {
			bytes_read = read(pipe_fd, buf, sizeof(buf));
			printf("读入数据的大小是%d 
", bytes_read);

		} while (bytes_read != 0);

		close(pipe_fd);
	} else
		exit(1);
	exit(1);
}

控制台输出:

 

读进程

进程PID 12240打开管道 O_RDONLY

读入数据的大小是4000.

……

(10S)

读入数据的大小是0

 

写进程

进程PID 12227打开管道 O_WRONLY

写入数据的大小是80000

 

分析:

假设读进程为堵塞的,当写进程关闭管道时。读进程收到写进程发来的结束符,读进程结束堵塞(此时bytes_read =0

假设读进程为非堵塞的,首先将全部数据读取出来,然后在读进程未收到写进程发来的结束符时。由于管道没有数据读进程不会堵塞且返回-1,由于此例WHILE退出条件是bytes_read =0。因此在未读到结束符之前返回值一直是-1,直到读取到结束符才返回0

管道写端规则

 

对于设置了堵塞标志的写操作:

1.       当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。假设此时管道空暇缓冲区不足以容纳要写入的字节数。则进入睡眠。直到当缓冲区中可以容纳要写入的字节数时,才開始进行一次性写操作。

2.       当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。FIFO缓冲区一有空暇区域,写进程就会试图向管道写入数据,写操作在写全然部请求写的数据后返回。

 

对于没有设置堵塞标志的写操作:

3.       当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。

在写满全部FIFO空暇缓冲区后,写操作返回。

4.       当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。

假设当前FIFO空暇缓冲区可以容纳请求写入的字节数,写完后成功返回;假设当前FIFO空暇缓冲区不可以容纳请求写入的字节数,则返回EAGAIN错误,提醒以后再写

 

管道读端规则

对于设置了堵塞标志的写操作:

1.       假设有进程写打开FIFO。且当前FIFO内没有数据,将一直堵塞。

 

对于没有设置堵塞标志的写操作:

2.       假设有进程写打开FIFO,且当前FIFO内没有数据。则返回-1,当前errno值为EAGAIN,提醒以后再试。

 

管道读写规则代码举例

写进程

#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<fcntl.h>
#include<limits.h>
#include<sys/types.h>
#include<sys/stat.h>
#include <errno.h>

#define FIFO_NAME "/tmp/my_fifo"
#define BUF_SIZE 88888
int main(int argc,char *argv[])
{
    int pipe_fd;
    int res;
    char buf[BUF_SIZE];
    memset(buf,3,BUF_SIZE);
    if(access(FIFO_NAME,F_OK)==-1)
    {
        res=mkfifo(FIFO_NAME,0766);
        if(res!=0)
        {
            fprintf(stderr,"不能创建管道文件 %s
",FIFO_NAME);
            exit(1);
        }
    }

    printf("进程PID %d 打开管道 O_WRONLY
",getpid());
    pipe_fd=open(FIFO_NAME,O_WRONLY|O_TRUNC);//1
   // pipe_fd=open(FIFO_NAME,O_WRONLY|O_TRUNC|O_NONBLOCK);//2
    if(pipe_fd!=-1)
    {
        res=write(pipe_fd,buf,sizeof(buf));
        printf("写入数据的长度是%d 
",res);
        close(pipe_fd);
    }
    else
        exit(1);
    exit(1);
}

读进程

#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<fcntl.h>
#include<limits.h>
#include<sys/types.h>
#include<sys/stat.h>

#define FIFO_NAME "/tmp/my_fifo"
#define BUF_SIZE 4000

int main(int argc, char *argv[]) {

	int res;
    if(access(FIFO_NAME,F_OK)==-1)
    {
        res=mkfifo(FIFO_NAME,0766);
        if(res!=0)
        {
            fprintf(stderr,"不能创建管道文件 %s
",FIFO_NAME);
            exit(1);
        }
    }
	char buf[BUF_SIZE];
	memset(buf, 0, BUF_SIZE);
	int pipe_fd;
 int num=0;
	int bytes_read = 0;

	printf("进程PID %d 打开管道 O_RDONLY
", getpid());
	pipe_fd = open(FIFO_NAME, O_RDONLY);//3
	//pipe_fd = open(FIFO_NAME, O_RDONLY|O_NONBLOCK);//4
	if (pipe_fd != -1) {

		do {
			num++;
			bytes_read = read(pipe_fd, buf, sizeof(buf));
			printf("第%d次读入数据,数据的长度是%d 
",num, bytes_read);


		} while (bytes_read != 0);

		close(pipe_fd);
	} else
		exit(1);
	exit(1);
}


上面两段代码各自是管道的读端进程与写端进程。当中有4个凝视行。分别代表

 

1.       pipe_fd=open(FIFO_NAME,O_WRONLY|O_TRUNC);//1堵塞写端

2.       pipe_fd= open (FIFO_NAME,O_WRONLY|O_TRUNC|O_NONBLOCK);//2非堵塞写端

3.       pipe_fd =open(FIFO_NAME, O_RDONLY);//3堵塞读端

4.       pipe_fd =open(FIFO_NAME, O_RDONLY|O_NONBLOCK);//4非堵塞读端

 

能够分下面3种情况分析:

说明:写端与读端不能同一时候都不堵塞

 

写端堵塞。读端不堵塞

控制台输出例如以下:

 

读端进程:

进程PID 5919打开管道 O_RDONLY

1次读入数据。数据的长度是-1

…………..

5次读入数据,数据的长度是4000

…………..

26次读入数据,数据的长度是4000

27次读入数据,数据的长度是888

28次读入数据,数据的长度是0

 

端进程:

进程PID 5906打开管道 O_WRONLY

写入数据的长度是88888

 

分析:读端满足读端规则2。前面因为写进程还未開始写入数据到管道因此返回-1

写端满足写端规则2

 

写端不堵塞,读端堵塞

运行流程:先运行读端程序,在运行写端

控制台输出例如以下:

 

读端进程:

进程PID 6046打开管道 O_RDONLY

1次读入数据。数据的长度是4000

…………

16次读入数据,数据的长度是4000

17次读入数据,数据的长度是1536

18次读入数据,数据的长度是0

 

端进程:

进程PID 6056打开管道 O_WRONLY

写入数据的长度是65536

 

分析:读端满足读端规则1,读进程在为读取到管道数据时一直处于等待堵塞状态

写端满足写端规则3。写端写满管道后推出,因此写入数据长度是65535,而不是88888

  

写端堵塞,读端堵塞

 

控制台输出例如以下:

 

读端进程:

进程PID 8386打开管道 O_RDONLY

1次读入数据。数据的长度是4000

…………

22次读入数据。数据的长度是4000

23次读入数据,数据的长度是888

24次读入数据,数据的长度是0

 

端进程:

进程PID 8373打开管道 O_WRONLY

写入数据的长度是88888

 

分析:读端满足读端规则1,读取处理用于读取数据已在管道等待堵塞状态

写满足最终写的 - 侧规则2

原文地址:https://www.cnblogs.com/lcchuguo/p/5033023.html