管道代码及分析.txt

来源:互联网 发布:中国传媒大学网络 编辑:程序博客网 时间:2024/05/16 11:52

管道
管道是进程间通信中最古老的方式,它包括无名管道和有名管道两种,前者可用于具有亲缘关系进程间的通信,即可用于父进程和子进程间的通信,后者克服了管道没有名字的限制,因此,除具有前者所具有的功能外,它还允许无亲缘关系进程间的通信,即可用于运行于同一台机器上的任意两个进程间的通信。


无名管道由pipe()函数创建:
#include <unistd.h>
int pipe(int filedis[2]);

参数filedis返回两个文件描述符:filedes[0]为读而打开,filedes[1]为写而打开。filedes[1]的输出是filedes[0]的输入。 无名管道占用两个文件描述符,  不能被非血缘关系的进程所共享,  一般应用在父子进程中. 
 
在父进程中关闭 fildes[0],  向 fildes[1]写入数据;  在子进程中关闭 filedes[1],  从
fildes[0]中读取数据可实现从父进程流向子进程的管道. 

    在进程的通信中,  我们无法判断每次通信中报文的字节数,  即无法对数据流进行自行拆分,  侧耳发生了上例中子进程一次性读取父进程两次通信的报文情况. 

管道是进程之间的一种单向交流方法,  要实现进程间的双向交流,  就必须通过两个管道来完成.  双向管道流的创立过程如下: 

    (1)  创建管道,  返回两个无名管道文件描述符 fildes1 和 fildes2: 

    (2)  创建子进程,  子进程中继承管道 fildes1 和 fildes2. 

    (3)  父进程关闭只读文件描述符 fildes1[0],  只写描述符 fildes2[1] 

    (4)  子进程关闭只写文件描述符 fildes1[1],  只读描述符 fildes2[0] 

    创建的结果如下: 

              父进程  --写--> fildes1[1] --管道--> fildes1[0] --读-->  子进程 

              父进程  <--读-- fildes2[0] <--管道-- fildes2[1] <--写--  子进程 

    这里实现一个父子进程间双向通信的实例:  父进程先向子进程发送两次数据,  再接收子进程传送刚来

的两次数据.

  为了正确拆分时间留从父进程流向子进程的管道采用'固定长度'方法传送数据;  从子进程流向

父进程的管道采用'显式长度'方法传回数据. 

    (1)  固定长度方式 

char bufG[255]; 

void WriteG(int fd, char *str, int len){ 

                memset(bufG, 0, sizeof(bufG)); 

                sprintf(bufG, "%s", str); 

                write(fd, bufG, len); 

char *ReadG(int fd, int len){ 

                memset(bufG, 0, sizeof(bufG)); 

                read(fd, bufG, len); 

                return(bufG); 

    在此设计中,  父子程序需要约定好每次发送数据的长度;  且长度不能超过 255 个字符.

    (2)  显式长度方式 

char bufC[255]; 

void WriteC(int fd, char str[]){ 

                sprintf(bufC, "%04d%s", strlen(str), str); 

                write(fd, bufC, strlen(bufC)); 

char *ReadC(int fd){ 

                int i, j; 

                memset(bufC, 0, sizeof(bufC)); 

                j = read(fd, bufC, 4); 

                i = atoi(bufC); 

                j = read(fd, bufC, i); 

                return(bufC); 

    父子进程约定在发送消息前先指明消息的长度. 

    (3)  主程序 

#include <unistd.h> 

#include <stdio.h> 

#include <assert.h> 

#include <sys/types.h> 

int main() 

                int fildes1[2], fildes2[2]; 

                pid_t pid; 

                char buf[255]; 

                assert(pipe(fildes1) == 0); 

                assert(pipe(fildes2) == 0); 

                assert((pid = fork()) >= 0); 

                if(pid == 0){ 

                                close(fildes1[1]); 

                                close(fildes2[0]); 

                                strcpy(buf, ReadG(fildes1[0], 10)); 

                                fprintf(stderr, "[child] buf = [%s]\n", buf); 

                                WriteC(fildes2[1], buf); 

                                strcpy(buf, ReadG(fildes1[0], 10)); 

                                fprintf(stderr, "[child] buf = [%s]\n", buf); 

                                WriteC(fildes2[1], buf); 

                                return(0); 

                } 

                close(fildes1[0]); 

                close(fildes2[1]); 

                WriteG(fildes1[1], "hello!", 10); 

                WriteG(fildes1[1], "world!", 10); 

                fprintf(stderr, "[father] buf = [%s] \n", ReadC(fildes2[0])); 

                fprintf(stderr, "[father] buf = [%s] \n", ReadC(fildes2[0])); 

                return 0; 

 

   

在Linux系统下,有名管道可由两种方式创建:命令行方式mknod系统调用和函数mkfifo。
下面的两种途径都在当前目录下生成了一个名为myfifo的有名管道:

方式一:mkfifo("myfifo","rw");

方式二:mknod myfifo p

生成了有名管道后,就可以使用一般的文件I/O函数如open、close、read、write等来对它进行操作。

    管道是 UNIX 中最古老的进程间通信工具,  它提供了进程之间的一种单向通信的方法. 

  popen 模型 

    从前面的程序可以看出,  创建连接标准 I/O 的管道需要多个步骤,  这需要使用大量的代码, UNIX 为了

简化这个操作,  它提供了一组函数实现之.  原型如下: 

#include <stdio.h> 

FILE *popen(const char *command, char *type); 

int pclose(FILE *stream); 

    函数 popen 调用成功时返回一个标准的 I/O 的 FILE 文件流,  其读写属性由参数 type 决定. 

    这里看一个模拟 shell 命令'ps -ef | grep init'的实例. 

[bill@billstone Unix_study]$ cat pipe3.c 

#include <stdio.h> 

#include <assert.h> 

int main() 

                FILE *out, *in; 

                char buf[255]; 

                assert((out = popen("grep init", "w")) != NULL);                //    创建写管道流 

                assert((in = popen("ps -ef", "r")) != NULL);                      //  创建读管道流 

                while(fgets(buf, sizeof(buf), in))                //  读取 ps -ef 的结果 

                                fputs(buf, out);                            //  转发到 grep init 

                pclose(out); 

                pclose(in); 

                return 0; 

有名管道 FIFO 

    FIFO 可以在整个系统中使用. 

    在 Shell 中可以使用 mknod 或者 mkfifo 命令创建管道;  而在 C 程序中,  可以使用 mkfifo 函数创建有名管道. 

    要使用有名管道,  需要下面几个步骤: 

    (1)  创建管道文件 

    (2)  在某个进程中以只写方式打开管道文件,  并写管道 

    (3)  在某个进程中以只读方式打开管道文件,  并读管道 

    (4)  关闭管道文件. 

    低级文件编程库和标准文件编程库都可以操作管道.  管道在执行读写操作之前,  两端必须同时打开,  否

则执行打开管道某端操作的进程将一直阻塞到某个进程以相反方向打开管道为止.