进程间通信之管道pipe与fifo

  • Post author:
  • Post category:其他


关于UNIX环境高级编程和UNIX网络编程的学习记录和总结




前言

管道是UNIX IPC(InterProcess Communication)的最老形式,并且所有UNIX系统都提供此种通信机制,管道有两种限制;

(1) 它们是半双工的。数据只能在一个方向上流动。

(2) 它们只能在具有公共祖先的进程之间使用。通常,一个管道由一个进程创建,然后该

进程调用fork,此后父、子进程之间就可应用该管道。

流管道没有第一种限制, FIFO和命名流管道则没有第二种限制。

管道基本是最简单的进程间通信方式




一、pipe()

管道:

在内核中借助环形队列机制,使用内核缓冲区实现。 管道是一种伪文件

管道常见的形式:
半双工

从父进程到子进程

管道的特点:管道中的数据只能一次读取, 数据在管道中只能单向流动。 不能在一端同时读写。管道pipe()只用在血缘关系进程间。

pipe()原型:

int pipe(int pipefd[2]);
int pipe2(int pipefd[2], int flags);		//If flags is  0,  then  pipe2()  is  the  same  as pipe().

​ 参数: fd[0]: 读端。

​ fd[1]: 写端。

​ 返回值: 成功: 0

​ 失败: -1 设置errno

读管道:

1. 管道有数据,read返回实际读到的字节数。

	2. 管道无数据:	1)无写端,read返回0 (类似读到文件尾)

			2)有写端,read阻塞等待。

写管道:

1. 无读端, 异常终止。 (SIGPIPE导致的)

	2. 有读端:	1) 管道已满, 阻塞等待

				2) 管道未满, 返回写出的字节个数。



pipe()的使用

尽管管道是由单个进程创建的,却很少在单个进程间使用。管道的典型用途是为两个不同的进程提供进程间的通信。

管道的一端只能占用读或写,因此在使用时需在一端关闭读端(或写端),而在另一端关闭写端(或读端)。

如:父子进程使用pipe(),父写子读

在调用fork之前先创建一个管道。 fork之后父进程关闭其读端,子进程关闭其写端。

	ret = pipe(fd);                 // 父进程先创建一个管道,持有管道的读端和写端 
	...
 	pid = fork();  					// 子进程同样持有管道的读和写端
    if (pid > 0) {  
        close(fd[0]);       // 关闭读段  
        //sleep(3);  
        write(fd[1], str, strlen(str));  
        close(fd[1]);  
    } else if (pid == 0) {  
        close(fd[1]);       // 子进程关闭写段  
        ret = read(fd[0], buf, sizeof(buf));  
        printf("child read ret = %d\n", ret);  
        write(STDOUT_FILENO, buf, ret);  	//输出到屏幕
  
        close(fd[0]);


使用pipe()兄弟进程通信



父进程只需等待回收子进程———–关闭读写端————-wait(NULL),阻塞回收子进程

for(i = 0; i < 2; i++) {        //创建子进程
        pid = fork();  
        if (pid == -1) {  
            sys_err("fork error");  
        }   
        if (pid == 0)               // 子进程,出口  
            break;  
    } 
	//父进程pid返回的是子进程id,所以父pid>0    
   if (i == 2) {                    // 父进程 
        close(fd[0]);               // 关闭管道的 读端/写端.  
        close(fd[1]);  
  
        wait(NULL);                 // 回收子进程  
        wait(NULL);  
    } else if (i == 0) {  			//子1
        close(fd[0]);  
        dup2(fd[1], STDOUT_FILENO);     // 重定向stdout  
        execlp();  
       ...
    } else if (i == 1) {            //子2
        close(fd[1]);  
        dup2(fd[0], STDIN_FILENO);      // 重定向 stdin  
        execlp(); 
        ...

也可以父进程完成读,两个子进程完成写。




二、有名管道fifo(有的书也叫命名管道)

FIFO有两种用途:

(1) FIFO由s h e l l命令使用以便将数据从一条管道线传送到另一条,为此无需创建中间临时

文件。

(2) FIFO用于客户机-服务器应用程序中,以在客户机和服务器之间传递数据。

如可以用于无血缘关系的进程间通信。

有名管道是存在于文件系统中的文件节点,所以我们可以用建立文件节点的方式来建立有名管道。在 shell 中我们可以用下面的命令:

shell创建fifo:

mkfifo [OPTION]… NAME…



mkfifo("fifo1",0664);

//0664文件权限


mkfifo –m 0664 fifo1

//与上述等价 “ -m”选项指定所建立的有名管道的存取权限


mknod fifo2 p

//也是创建fifo,但mknod 则需要之后使用 chmod 来改变有名管道的存取权限



C中创建fifo:


int mknod( char *pathname, mode_t mode, dev_t dev);


返回值:成功 0

失败-1,设置errno

errno = EFAULT (pathname invalid)
		EACCES (permission denied)
		ENAMETOOLONG (pathname too long)
		ENOENT (invalid pathname)
		ENOTDIR (invalid pathname)
(see man page for mknod for others)
  • 读端,open fifo O_RDONLY
  • 写端,open fifo O_WRONLY



fifo的I/O使用

有名管道和管道的操作是相同的,只是要注意,在引用已经存在的有名管道时,首先

要用系统中的文件函数来打开它,才能接下来进行其他的操作。

因此我们可以像操作文件流一样来操作fifo



fifo非血缘进程间通信

//创建fifo
mkfifo("fifoname",0644);

/*---------------写端------------*/
fd=open(fifoname,O_WRONLY);
...
write(fd,buf,strlen(buf));		//向管道写
...
close(fd);

/*---------------读端----------------*/
fd=open(fifoname,O_RDONLY);
...
int len=read(fd,buf,sizeof(buf));		//从管道读
write(STDOUT_FILENO,buf,len);			//输出到屏幕
...
close(fd);



管道和fifo的额外属性

有名管道自动支持进程阻塞, 一个文件描述符能以两种方式设置成非阻塞

  1. writefd=open(FIFO1,O_WRONLY|O_NONBLOCK,0)

  2. 如果一个文件描述符已经打开,可以使用fcntl()设置非阻塞

    原型:

    int fcntl(int fd, int cmd, ...)


    cmd 命令,决定了后续参数个数

    ​ int flgs = fcntl(fd, F_GETFL);

    ​ flgs |= O_NONBLOCK 或等于,不管原来状态如何一定会添加上ONONBLOCK

    ​ fcntl(fd, F_SETFL, flgs);

    ​ 获取文件状态: F_GETFL

    ​ 设置文件状态: F_SETFL

使用fcntl将文件改为非阻塞读:

 flags = fcntl(STDIN_FILENO, F_GETFL); //获取stdin属性信息 
 flags |= O_NONBLOCK;  
    int ret = fcntl(STDIN_FILENO, F_SETFL, flags);  
    if(ret == -1){  
        perror("fcntl error");  
        exit(1);  
    }  
 



总结

管道可以用于一个读端和多个写端,或者多个读端一个写端,但是管道中的数据只能读一次,所以一般情况下应该是一对一模型,这样才能保证数据的顺序。

FIFO的真正优势表现在作为服务器,可以是一个长期运行的进程,构成单个服务器多个客户的模型,而且可以与客户无血缘关系,服务器的守护进程创建fifo读端,客户启动写端把自己的请求写入服务器。

在这里插入图片描述



版权声明:本文为qq_43460315原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。