欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

进程间通信之管道

程序员文章站 2022-05-22 19:22:54
...

一. 匿名管道

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;
}

运行结果:
进程间通信之管道