进程间通信(1)--管道

来源:互联网 发布:js concat连接字符串 编辑:程序博客网 时间:2024/06/05 09:41

一、什么是管道

Linux下一切皆文件,我们可以创建一个管道文件进行通信,实际上是调用pipe函数在内核中开辟一块缓冲区(称为管道)用于通信,管道是一种最基本的IPC机制,由pipe函数创建
#include <unistd.h>
int pipe (int filedes[2]);

它有一个读端一个写端,然后通过filedes参数传出给用户程序两个文件描述符,filedes[0]指向管道的读端,filedes[1]指向管道的写端(很好记,就像0是标准输入1是标准输出一样)。所以管道在用户程序看起来就像一个打开的文件,通过read(filedes[0]);或者write(filedes[1]);向这个文件读写数据其实是在读写内核缓冲区。pipe函数调⽤用成功返回0,调⽤用失败返回-1。
管道的种类
<1>普通管道:pipe也叫匿名管道
a.单向通信
b.只有在具有亲缘关系的进程间通信
c.具有同步机制
d.它是一种面向字节流的通信服务
e.生命周期随进程

<2>流管道:s_pipe
可以进行双向传输,其他同上

<3>命名管道:name_pipe
可以使毫不相干的进程进行通信,其他同匿名管道

二、使用管道进行通信

单向通信

#include <stdio.h>#include <unistd.h>#include <errno.h>#include <string.h>#include <sys/wait.h>int main(){    int _pipe[2];    int ret = pipe(_pipe);    if(ret == -1){        printf("creat pipe error!errno code is:%d\n",errno);        return 1;    }    pid_t id = fork();    if(id < 0)    {        printf("fork error!");        return 2;    }else if(id==0){//child    close(_pipe[0]);//关掉读    int i = 0;    char* _mesg_c = NULL;    while(i<20){        if(i<10){      _mesg_c = " i am child!";    write(_pipe[1], _mesg_c,strlen(_mesg_c)+1);//写入        }    sleep(1);    i++;    }   // close(_pipe[1]); }else{//father    close(_pipe[1]);//关掉写    char _mesg[100];    int j = 0;      while(j<3)        {    memset(_mesg,'\0',sizeof(_mesg));    int ret = read(_pipe[0],_mesg,sizeof(_mesg));//读出    printf("%s:code is %d\n",_mesg,ret);           j++;       }       close(_pipe[0]);       sleep(10);     if(waitpid(id,NULL,0)< 0)       {           return 3;       }      }    return 0;}

运行结果:
这里写图片描述
分析:父子进程进行单向通信,子进程写,父进程读

使用管道需要注意以下四种特殊情况(假设都是阻塞I/O操作,没有设置O_NONBLOCK标志)
1. 如果所有指向管道写端的文件描述符都关闭了,(管道写端的引用计数为0),而仍然有进程从管道的读端读取数据,那么管道中剩余的数据都被读取之后,再次read将会返回0,就像读到文件结尾一样。也就是说,写端不会写,读端读完之后就会再等着写端去写,但是写端关闭了啊,不会写了,所以就出现上面说的情况。这就体现出了管道的同步机制。

  1. 如果有指向管道写端的文件描述符没有关闭,(管道写端的引用计数大于0)而持有管道写端的进程也没有向管道中写数据,这时有进程管道读端读数据,那么管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。通俗讲就是,读端读数据,一直读,但是写端不写了,而且写端并没有关闭,所以这时读端就会一直等着写端去写。这就造成了阻塞式等待。

  2. 如果所有指向管道读端的文件描述符都关闭了(管道读端的引用计数为0),这时有进程向管道的写端写数据,那么该进程会收到SIGPIPE,通常会导致进程异常终止。所以进程就会异常退出了。

  3. 如果有指向管道读端的文件描述符没关闭(管道读端的引用计数大于0)而持有管道读端的进程也没有从管道中读取数据,这时有进程向管道写端写数据,那么在管道写满时再写将会阻塞,直到管道中有了空位置才写入并返回,也就是管道的同步机制。
    双向通信
    命名管道:
    命名管道可以在不相关的进程之间和不同计算机之间使用,服务器建立命名管道时给它指定一个名字,任何进程都可以通过该名字打开管道的另一端,根据给定的权限和服务器进程通信。而且,FIFO总是按照先进先出的原则工作,第一个被写入的数据首先从管道中读出。

创建:

#include<sys/types.h>#include<sys/stat.h>int mknod(const char*path,mode_t mod,dev_t dev);int mkfifo(const char*path,mode_t mode);参数:path为创建的命名管道的路径名,mod为创建命名管道的模式,指明其存取权限,dev为设备值,该值文件创建的种类,它只在创建设备文件时才会用到。这两个函数掉用成功返回0,失败都返回-1.

命名管道实现通信的代码:

这里写代码片.PHONY:allall:server clientclient:client.c    gcc -o $@ $^server:server.c    gcc -o $@ $^.PHONY:cleanclean:    rm -f server client

comm.h

#ifndef __COMM_H_#define __COMM_H_#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <stdlib.h>#include <unistd.h>#include <fcntl.h>#include <string.h>#define  ONEPATH "./fifo_one.c"#define  TWOPATH "./fifo_two.c"#define  SIZE  1000#endif

client.c

#include "comm.h"int main(){    int _ret = mkfifo(ONEPATH,S_IFIFO|0666);    if(_ret == -1)    {        printf("mkfifo ONEPATH error\n");        return 1;    }    int fdone = open(ONEPATH,O_WRONLY);    int fdtwo = open(TWOPATH,O_RDONLY);    if((fdone<0)||(fdtwo<0))      {      printf("open file error\n");          return 1;      }       char buf[SIZE];       memset(buf,'\0',sizeof(buf));       while(1)       {        printf("guangyuan->>>");        scanf("%s,buf");        int retone = write(fdone,buf,strlen(buf)+1);           if(retone<0)           {               printf("write error\n");               break;           }        printf("guangyuan<<<-");        int rettwo = read(fdtwo,buf,sizeof(buf));        if(rettwo<0)           {            printf("write error\n");            break;           }        printf("%s\n",buf);       }       close(fdone);       close(fdtwo);    return 0;}

server.c

#include "comm.h"int main(){    int ret = mkfifo(TWOPATH,S_IFIFO|0666);    if(ret == -1)    {        printf("mkfifo TWOPATH error\n");        return 1;    }    int fdone = open(ONEPATH,O_RDONLY);    int fdtwo = open(TWOPATH,O_WRONLY);    if((fdone<0)||(fdtwo<0))      {      printf("open file error\n");          return 1;      }       char buf[SIZE];       memset(buf,'\0',sizeof(buf));       while(1)       {        printf("mengnan<<<-");        int retone = read(fdone,buf,sizeof(buf));        if(retone<0)        {            printf("read error\n");            break;        }         printf("%s\n",buf);        printf("megnnan->>>");        scanf("%s",buf);        int rettwo = write(fdtwo,buf,strlen(buf)+1);        if(rettwo<0)           {            printf("write error\n");           break;           }       }       close(fdone);       close(fdtwo);    return 0;}

结果:
这里写图片描述
注意
命名管道的使用和匿名管道基本相同,只是在使用命名管道之前首先要使用open函数打开,因为命名管道是存在于硬盘上的文件,而管道是存在于内存中的特殊文件。
需要注意,使用open的几点:
1. 调用open()打开命名管道可能会被阻塞,但是如果同时用读写方式(O_RDWR)打开,则一定不会造成阻塞。
2. 如果以只读方式(O_RDONLY)打开,则调用open()函数的进程将会被阻塞直到有写才能打开管道。
3. 同样,以写方式(O_WRONLY)打开也会阻塞直到有读方式打开管道。

原创粉丝点击