一:什么是管道

管道是Linux 支持的最初Unix IPC形式之一,具有以下特点:

管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道; 只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程); 单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在于内存中。数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。

二:C管道函数

NAME
       pipe - create an interprocess channel
SYNOPSIS
       #include <unistd.h>
       int pipe(int fildes[2]);
RETURN VALUE
       Upon successful completion, 0 shall  be  returned;  otherwise,  -1  shall be returned and errno set to indicate the error.

三:C/C++和Python区别

在C/C++中,我们是利用一个含有两个文件描述符的数组来表示一根管道,通常使用fd[1]来读,fd[2]来写。而Python中则是由multiprocessing.Pipe()直接创建两个描述符,直接用就行。刚开始用Python时,我误以为Python中的管道是全双工的,这是错误的。管道都是半双工的,你在一端读的时候,一端只能写。不可能同时进行。

四:代码说明

本文代码实现父进程和子进程对话,对话顺序是通过C中的read或者Python中的recv函数阻塞特性实现的。你一句我一句,不会乱顺序。

五:C代码如下

头文件:
#ifndef _UTILI_H
#define _UTILI_H

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>

#define err_quit(m) \
		do{ \
			perror(m); \
			exit(1); \
		}while(0)
#define MAXLINE 512

#endif
实现:
#include "utili.h"

void child_handler(int *fd1, int *fd2, char **chls);
void parent_handler(int *fd1, int *fd2, char **pars);

int main()
{
	char* parent_say[] = {"hello", "how old are you", "me too,bye", NULL};
	char* child_say[] = {"hi", "i'm fine,and you?", "byebye", NULL};
	
	int fd1[2];    //use fd[1] which is for reading, another is for writing
	int fd2[2];    //like the above
	
	if(pipe(fd1) == -1 || pipe(fd2) == -1)
		err_quit("create pipe err.\n");
	
	pid_t pid;
	if( (pid = fork()) < 0)   //C还是很坑的,第一次在这写成了==,这个错误愣是害我找了半天
		err_quit("fork err.\n");
	else if(pid == 0){
		child_handler(fd1, fd2, child_say);
		exit(0);
	}
	
	parent_handler(fd1, fd2, parent_say);

	int status;
	wait(&status);

	return 0;
}

void child_handler(int *fd1, int *fd2, char **chls)
{
	close(fd1[1]);
	close(fd2[0]);
	
	char buff[MAXLINE];
	for(int i=0; *(chls+i) != NULL; ++i){
		read(fd1[0], buff, sizeof(buff));
		printf("parent say:>%s\n", buff);

		write(fd2[1], *(chls+i), strlen(*(chls+i))+1);
	}
	
	close(fd1[0]);
	close(fd2[1]);
}

void parent_handler(int *fd1, int *fd2, char **pars)
{
	close(fd1[0]);
	close(fd2[1]);

	char buff[MAXLINE];
	for(int i=0; *(pars+i) != NULL; ++i){
		write(fd1[1], *(pars+i), strlen(*(pars+i))+1);

		read(fd2[0], buff, sizeof(buff));
		printf("child say:>%s\n", buff);
	}
	
	close(fd1[1]);
	close(fd2[0]);
}

六:Python代码如下

import multiprocessing

parent_say = ('hello', 'how old are you', 'me too,bye')
child_say = ('hi', 'i am fine,and you?', 'bye')

def child_handler(read_fd, write_fd):
	for word in child_say:
		print 'child say:>' + (read_fd.recv())
		write_fd.send(word)

def parent_handler(read_fd, write_fd):
	for word in parent_say:
		write_fd.send(word)
		print 'parent say:>' + (read_fd.recv())

if __name__ == '__main__':
	(read_fd1, write_fd1) = multiprocessing.Pipe()
	(read_fd2, write_fd2) = multiprocessing.Pipe()
	child = multiprocessing.Process(target=child_handler, args=(read_fd1, write_fd2))
	child.start()

	parent_handler(read_fd2, write_fd1)

	read_fd1.close()
	read_fd2.close()
	write_fd1.close()
	write_fd2.close()

	child.join()

七:结果如图:



update:


管道操作的原子性:

一个管道的容量是有限的。POSIX规定,少于 PIPE_BUF 的写操作必须原子完成:要写的数据应被连续的写到管道;大于 PIPE_BUF 的写操作可能是非原子的: 内核可能会把此数据与其它进程的对此管道的写操作交替起来。POSIX规定PIPE_BUF至少为512B(linux中为4096B),具体的语义如下: 其中n为要写的字节数

  • n <= PIPE_BUF, O_NONBLOCK无效:原子的写入n个字节。如果管道当前的剩余空间不足以立即写入n个字节,就阻塞直到有足够的空间。
  • n <= PIPE_BUF, O_NONBLOCK有效:写入具有原子性,如果有足够的空间写入n个字节,write立即成功返回。否则一个都不写入,返回错误,并设errno为EAGAIN。
  • n >  PIPE_BUF, O_NONBLOCK无效:非原子写。可能会和其它的写进程交替写。write阻塞直到将n个字节写入管道。
  • n >  PIPE_BUF, O_NONBLOCK有效:如果管道满,则write失败,返回错误,并将errno设置为 EAGIN。如果不满,则返回写入的字节数为1~n,即部分写入,写入时可能有其他进程穿插写入。

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


系统加于管道和FIFO的唯一限制为:
  • OPEN_MAX     一个进程在任意时刻打开的最大描述符数(Posix要求至少为16)。
  • PIPE_BUF        可原子性地写往一个管道或FIFO的最大数据量(Posix要求至少为512)。




 


Logo

更多推荐