您的位置:首页 > 运维架构 > Linux

Linux管道pipe -- C和Python两种实现方案解析

2016-09-19 20:22 357 查看

一:什么是管道

管道是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,即部分写入,写入时可能有其他进程穿插写入。

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

系统加于管道和FIFO的唯一限制为:

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

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  CC++ python 通信 管道 linux