Linux进程间通信——管道

一句话总结:管道分无名与有名,无名只能用于父子进程,有名可用于两个相互独立的进程。

1、无名管道

创建管道int pipe(int pipefd[2]); 成功:0;失败:-1
函数调用成功返回r/w两个文件描述符。无需open,但需手动close。规定:fd[0] → r; fd[1] → w,就像0对应标准输入,1对应标准输出一样。

向管道文件读写数据其实是在读写内核缓冲区。
管道创建成功以后,创建该管道的进程(父进程)同时掌握着管道的读端和写端。如何实现父子进程间通信呢?通常可以采用如下步骤:


1. 父进程调用pipe函数创建管道,得到两个文件描述符fd[0]、fd[1]指向管道的读端和写端。
2. 父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道。
3. 父进程关闭管道读端,子进程关闭管道写端。父进程可以向管道中写入数据,子进程将管道中的数据读出。由于管道是利用环形队列实现的,数据从写端流入管道,从读端流出,这样就实现了进程间通信。

从图2可以看到,父进程和子进程都有一对读写fd,每个fd[0]都可以读取到另外两个fd[1]写入管道的数据。

#include <unistd.h>  
#include <string.h>  
#include <stdlib.h>  
#include <stdio.h>  
#include <sys/wait.h>  
  
void sys_err(const char *str)  
{  
    perror(str);  
    exit(1);  
}  
  
int main(void)  
{  
    pid_t pid;  
    char buf[1024];  
    int fd[2];  
      
   if (pipe(fd) == -1)   
      sys_err("pipe");  
  
   pid = fork();  
   if (pid < 0) {  
      sys_err("fork err");  
   } else if (pid == 0) {  
      printf("son process\n");
      char p[20] = "I am son process\n";
      write(fd[1], p, strlen(p));
      close(fd[1]);  //子进程关闭写端
      int len = read(fd[0], buf, sizeof(buf));  
      write(STDOUT_FILENO, buf, len);  
      close(fd[0]); 
      printf("son process end\n"); 
   } else {  
      //sleep(2); //延迟2秒可以清楚看到,子进程写的数据直接被子进程读走了,父进程阻塞在read
      printf("parent process\n");
      int len = read(fd[0], buf, sizeof(buf));  
      write(STDOUT_FILENO, buf, len); 
      	close(fd[0]);    //父进程关闭读端
      char p[30] = "I am parent process\n";
      	write(fd[1], p, strlen(p));  
      	//wait(NULL);  
      	close(fd[1]);  
      printf("parent process end\n");
   }  
      
    return 0;  
}

运行结果:

zhaojunyandeMacBook-Pro:~ zhaojunyan$ ./pipe 

parent process

son process

I am son process

I am parent process

parent process end

son process end

zhaojunyandeMacBook-Pro:~ zhaojunyan$ ./pipe 

parent process

son process

I am son process

son process end

^Z

[3]+  Stopped                 ./pipe

zhaojunyandeMacBook-Pro:~ zhaojunyan$ 

说明如果刚好父进程读完子进程写入管道的数据后,子进程才开始写数据,则不会出现阻塞;如果子进程抢先将自己写入管道的数据读走了,则父进程就会阻塞在read处。

父进程read数据之前加上延时,则结果为:

zhaojunyandeMacBook-Pro:~ zhaojunyan$ ./pipe 

son process

I am son process

son process end

parent process,add 2 sec

^Z

[4]+  Stopped                 ./pipe

zhaojunyandeMacBook-Pro:~ zhaojunyan$ 


全双工通信要特别注意,下面是半双工例子(写两次数据,read时一次全部读出来):

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

void sys_err(const char *str)  
{  
    perror(str);  
    exit(1);  
}
  
int main(void)  
{  
    pid_t pid;  
    char buf[1024];  
    int fd[2];  
      
   if (pipe(fd) == -1)   
      sys_err("pipe");  
  
   pid = fork();  
   if (pid < 0) {  
      sys_err("fork err");  
   } else if (pid == 0) {  
      close(fd[1]);  //子进程关闭写端
      int len = read(fd[0], buf, sizeof(buf));  
      write(STDOUT_FILENO, buf, len);  
      close(fd[0]); 
   } else {  
      close(fd[0]);    //父进程关闭读端
      char p[30] = "I am parent process\n";
      write(fd[1], p, strlen(p));  
      write(fd[1], p, strlen(p));  
      close(fd[1]);  
   }  
      
    return 0;  
}


2、有名管道

int mkfifo(const char *pathname, mode_t mode)
pathname: FIFO文件名
mode: 属性
一旦创建了了FIFO,就可open去打开它,可以使用open,read,close等去操作FIFO
当打开FIFO时,非阻塞标志(O_NONBLOCK)将会对读写产生如下影响:
(1)没有使用O_NONBLOCK:访问要求无法满足时进程将阻塞。如试图读取空的FIFO,将导致进程阻塞;
(2)使用O_NONBLOCK:访问要求无法满足时不阻塞,立即出错返回,errno是ENXIO;
有名管道的总体操作:
创建管道mkfifo
打开管道open
读管道read
写管道write
关闭管道close
删除管道unlink

FIFO文件在使用上和普通文件的不同之处:
1. 读取fifo文件的进程只能以”RDONLY”方式打开fifo文件。
2. 写fifo文件的进程只能以”WRONLY”方式打开fifo
3. fifo文件里面的内容被读取后,就消失了。但是普通文件里面的内容读取后还存在。

#include <sys/types.h>  
#include <sys/stat.h>  
#include <errno.h>  
#include <fcntl.h>  
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h> //这个很重要,要不open、read找不到

#define FIFO "/home/zjy/Documents/myfifo"  
  
int main()  
{  
    char buf_r[100];  
    int  fd;  
    int  nread;  
      
    /* 创建管道 */  
    if((mkfifo(FIFO,O_CREAT|O_EXCL)<0)&&(errno!=EEXIST))  
        printf("cannot create fifoserver\n");  
      
    printf("Preparing for reading bytes...\n");  
    /* 打开管道 */  
    fd=open(FIFO,O_RDONLY|O_NONBLOCK,0);  //只读、非阻塞
    if(fd==-1)  
    {  
        perror("open");  
        exit(1);      
    }  
    while(1)  
    {  
        memset(buf_r,0,sizeof(buf_r));       
        if((nread=read(fd,buf_r,100))==-1)  
        {  
            if(errno==EAGAIN)  
                printf("no data yet\n");  
        } 
	    else if(nread > 0)
            printf("read %s from FIFO\n",buf_r);  
        sleep(1);  
    }     
    pause(); /*暂停,等待信号*/  
    unlink(FIFO); //删除文件  
    return 0;
}  

#include <sys/types.h>  
#include <sys/stat.h>  
#include <errno.h>  
#include <fcntl.h>  
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>
#define FIFO_SERVER "/home/zjy/Documents/myfifo"  
  
int main()  
{  
    int fd;  
    char w_buf[100] = "hello world";  
    int nwrite;  
          
    /*打开管道*/  
    fd=open(FIFO_SERVER, O_WRONLY|O_NONBLOCK, 0);  //只写,非阻塞
    /* 向管道写入数据 */  
    if((nwrite=write(fd,w_buf,100))==-1)    
        printf("The FIFO has not been read yet.Please try later\n");  
    else   
        printf("write %s to the FIFO\n",w_buf);  

    return 0;
} 

运行结果:


第一次:先运行fifo_read,程序阻塞,直到运行fifo_write有数据写入管道;

第二次:先运行fifo_write,直接将数据写入管道后退出,然后运行fifo_read可以将数据读出来。如果read端open时不加o_nonblock,则在再次运行fifo_write之前是无法获取到之前的信息的。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值