进程间通信之管道

一. 匿名管道

1. 基本概念

由于没有名字,只能在有共同祖先(有亲缘关系)的进程间使用。管道普遍用于shell中,也可以在程序中实现子进程向父进程回传信息。**实现原理: 内核借助环形队列机制,使用内核缓冲区实现。**管道的大小一般默认为4096。
管道虽然是单个进程创建的,但是却很少在单个进程内使用。其典型用途是为两个不同进程(父子进程)提供进程间通信的手段。当需要一个双向数据流时,我们必须创建两个管道,每个方向一个,步骤如下:
创建管道1(fd1[0]和fd1[1])和管道2(fd2[0]和fd2[1]);
fork创建子进程;
父进程关闭管道1的读 fd1[0] 与 管道2的写fd2[1];
子进程关闭管道1的写 fd1[1] 与 管道2的读fd2[0];

在这里插入图片描述

特质:	1. 伪文件
		2. 管道中的数据只能一次读取。
	    3. 数据在管道中,只能单向流动。
局限性   1. 自己写,不能自己读。
	    2. 数据不可以反复读。
	    3. 半双工通信。(数据的传输方向是单向的)
	    4. 血缘关系进程间可用。

2. 匿名管道的使用

pipe函数创建,提供一个单向数据流。

int pipe(int fd[2]);
参数:	
	fd[0]: 读端,用来读取数据
	fd[1]: 写端,用来写入数据
返回值: 
	成功: 0
	失败: -1 errno

管道的读写行为:

读管道:
	1. 管道有数据,read返回实际读到的字节数。
	2. 管道无数据:	1)无写端,read返回0 (类似读到文件尾)
                    2)有写端,read阻塞等待。
写管道:
	1. 无读端, 异常终止。 (SIGPIPE导致的)
	2. 有读端:    	1) 管道已满, 阻塞等待
                    2) 管道未满, 返回写出的字节个数。

代码示例:实现进程间的通信

#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
#include<fcntl.h>
#include<string.h>
#include<errno.h>

#define MAXLEN 100

void client(int readfd, int writefd);
void server(int readfd, int writefd);

int main(int argc,char* argv[])
{
	//定义两个无名管道
	int pipe1[2],pipe2[2];
	//定义一个子线程
	pid_t childpid;
	//创建两个无名管道
	pipe(pipe1);
	pipe(pipe2);
	//父进程fock子进程,子进进程也携带两个pipes
	if(childpid == fork() == 0)
	{
		/*子进程关闭pipe1的写 与 pipe2的读*/
		close(pipe1[1]);
		close(pipe2[0]);
		server(pipe1[0],pipe2[1]);/*子进程运行server 程序*/  
        exit(0);/*子进程终止,变成僵尸进程,*/
	}
	close(pipe1[0]);/*父进程关闭pipe1的读 与 pipe2的写*/  
    close(pipe2[1]);  
    client(pipe2[0],pipe1[1]);/*父进程运行client 程序*/  
    /*父进程等待子进程结束,取得已终止的子进程(僵尸进程)的终止状态。若无waitpid,则子进程将托孤给Init进程,最终由init进程取得僵尸子进程的状态。*/  
    waitpid(childpid,NULL,0);
    exit(0);
}
/*参数分别为pipe2[0],pipe1[1]*/
void client(int readfd, int writefd)
{
	size_t len;
	ssize_t n;
	char buff[MAXLEN];
	/*从标准输入stdin中获取输入(一段路径名)放入buff中*/
	fgets(buff, MAXLEN, stdin);
	len = strlen(buff);
	/*删除由fgets存入的换行符*/
	if(buff[len-1] == '\n')
		len--;
	/*将buff中内容写入管道pipe1[1] writefd,传输到子进程server*/ 
	write(writefd, buff, len);
	/*从pipe[0] readfd中读取子进程server传输过来的内容放入buff,并显示到标准输出stdout*/
	 while((n = read(readfd,buff,MAXLEN)) > 0)  
        write(STDOUT_FILENO,buff,n); 
}
void server(int readfd,int writefd)/*参数对应pipe1[0],pipe2[1]*/    
{   
    int fd;  
    ssize_t n;  
    char buff[MAXLEN+1];
    /*从管道pipe1[0] readfd中读取来自父进程client的输入并放入buff*/    
    if((n = read(readfd,buff,MAXLEN)) == 0)
        printf("end-of-file while reading pathname\n");  
    buff[n] = '\n';
    /*open 打开client 通过管道传输过来的路径名*/    
    if((fd = open(buff,O_RDONLY)) < 0)
    {  
        snprintf(buff+n,sizeof(buff) - n,"can not open %s\n",strerror(errno));  
        n=strlen(buff); 
        /*打开失败,将错误信息写入管道writefd pipe2[1],传输至父进程client*/    
        write(writefd,buff,n);
    } 
    else  
    {  
        /*打开成功,将路径名对应的文件中的内容读出到buff,并写到管道writefd pipe2[1] 传输至父进程client */   
        while((n = read(fd,buff,MAXLEN)) > 0)                                           
            write(writefd,buff,n);  
        close(fd);  
    }   
}

3. 设置非阻塞状态

管道默认读写两端都是阻塞状态
设置读端为非阻塞方式
方法

fcntl-变参函数
1 赋值文件描述符-dup函数
2 修改文件属性
//获取原来的flags
int flags = fcntl(fd[0], F_GETFL);
//设置新的flags
flags |= O_NINBLOCK;
fcntl(fd[0],F_SETFL, flags);

二. 有名管道

1. fifo管道的基本概念

fifo管道:可以用于无血缘关系的进程间通信。FIFO指代先进先出,first in first out,也称为有名管道。其是一个单向的(半双工)的数据流,每个FIFO有一个路径名与之关联,从而实现无亲缘关系的进程访问同一个FIFO。
命名管道: mkfifo
无血缘关系进程间通信:
读端,open fifo O_RDONLY
写端,open fifo O_WRONLY

#include <sys/types.h> 
#include <sys/stat.h>
int mkfifo(const char *pathname,mode_t mode);
/*pathname 为文件路径名,是FIFO的名字。mode是文件权限位*/
特点
	有名管道
	在磁盘上有这样一个文件 ls -l
	伪文件,在磁盘上大小永远为0
	在内核中有一个对应的缓冲区
	半双工的通信方式
FIFO文件可以执行IO函数操作,如open、close、read、write
     但是不能执行lseek函数

2. fifo管道实现

在这里插入图片描述

示例1:

#include <sys/types.h>  
#include <unistd.h>  
#include <stdio.h>
#include <fcntl.h>
#include <string.h> 
#include <errno.h>
#include <stdlib.h>
 
#define FIFO1 "/tmp/fifo.1" 
#define FIFO2 "/tmp/fifo.2"
 
void client(int readfd,int writefd);
void server(int readfd,int writefd);
 
int main(int argc,char *argv[])
{
 
    int readfd,writefd; 
    pid_t childpid; 
    /*创建两个FIFO*/
    if((mkfifo(FIFO1,FILE_MODE) < 0 ) && (errno != EEXIST)) 
        printf("can not create %s\n",FIFO1);     
    if((mkfifo(FIFO2,FILE_MODE) < 0 ) && (errno != EEXIST)) 
    { 
        unlink(FIFO1); 
        printf("can not create %s\n",FIFO2);
    }
    if ((childpid = fork()) == 0)/*子进程*/ 
    { 
        readfd = open(FIFO1,O_RDONLY,0);/*读FIFO1*/ 
        writefd = open(FIFO2,O_WRONLY,0);/*写FIFO2*/ 
        server(feadfd,writefd); 
        exit(0); 
    }
 
    /*父进程*/ 
    writefd = open(FIFO1,O_WRONLY,0);/*写FIFO1*/ 
    readfd = open(FIFO2,O_WRONLY,0);/*读FIFO2*/ 
    client(readfd,writefd); 
    waitpid(childpid,NULL,0); 
    close(readfd); 
    close(writefd); 
    unlink(FIFO1);/*手动删除FIFO1/2*/
    unlink(FIFO2); 
}
 
void client(int readfd,int writefd)/*参数分别为 pipe2[0],pipe1[1]*/   
{  
    size_t len;  
    ssize_t n;  
    char buff[MAXLEN];  
    fgets(buff,MAXLEN,stdin);/*从标准输入stdin中获取输入(一段路径名)放入buff*/  
    len = strlen(buff);  
    if(buff[len - 1] == '\n') /*删除由fgets存入的换行符*/  
        len--;  
    write(writefd,buff,len);/*将buff中内容写入管道pipe1[1] writefd,传输到子进程server*/  
    while((n = read(readfd,buff,MAXLEN)) > 0)/*从pipe[0] readfd中读取子进程server传输过来的内容放入buff,并显示到标准输出stdout*/   
        write(STDOUT_FILENO,buff,n);   
}  
void server(int readfd,int writefd)/*参数对应pipe1[0],pipe2[1]*/  
{  
    int fd;  
    ssize_t n;  
    char buff[MAXLEN+1];  
    if((n = read(readfd,buff,MAXLEN)) == 0)/*从管道pipe1[0] readfd中读取来自父进程client的输入并放入buff*/  
        printf("end-of-file while reading pathname\n");  
    buff[n] = '\n'; 
    if((fd = open(buff,O_RDONLY)) < 0)/*open 打开client 通过管道传输过来的路径名*/ 
    {   
        snprintf(buff+n,sizeof(buff) - n,"can not open %s\n",strerror(errno));  
        n=strlen(buff);  
        write(writefd,buff,n);/*打开失败,将错误信息写入管道writefd pipe2[1],传输至父进程client*/  
    }  
    else  
    {  
        while((n = read(fd,buff,MAXLEN)) > 0)/*打开成功,将路径名对应的文件中的内容读出到buff, 并写到管道writefd pipe2[1] 传输至父进程client */ 
            write(writefd,buff,n);  
        close(fd); 
    }   
}  

示例2:
write_fifo.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
 
int main(int argc, const char* argv[])
{
    if(argc < 2)
    {
        printf("./a.out fifoname\n");
        exit(1);
    }
    // 判断文件是否存在
    int ret = access(argv[1], F_OK);
    if(ret == -1)
    {
        int r = mkfifo(argv[1], 0664);
        if(r == -1)
        {
            perror("mkfifo error");
            exit(1);
        }
        printf("有名管道%s创建成功\n", argv[1]);
    }
     
    int fd = open(argv[1], O_WRONLY);
    if(fd == -1)
    {
        perror("open error");
        exit(1);
    }
    char *p = "hello, world";
    while(1)
    {
        sleep(1);
        int len = write(fd, p, strlen(p)+1);
    }
    close(fd);
    return 0;
}

read_fifo.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
 
int main(int argc, const char* argv[])
{
    if(argc < 2)
    {
        printf("./a.out fifoname\n");
        exit(1);
    } 
    // 判断文件是否存在
    int ret = access(argv[1], F_OK);
    if(ret == -1)
    {
        int r = mkfifo(argv[1], 0664);
        if(r == -1)
        {
            perror("mkfifo error");
            exit(1);
        }
        printf("有名管道%s创建成功\n", argv[1]);
    } 
    int fd = open(argv[1], O_RDONLY);
    if(fd == -1)
    {
        perror("open error");
        exit(1);
    } 
    char buf[512];
    while(1)
    {
        int len = read(fd, buf, sizeof(buf));
        buf[len] = 0;
        printf("buf = %s\n, len = %d", buf, len);
    } 
    close(fd);
     return 0;
}

运行结果:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值