进程通信--管道

一、无名管道

参考:
1、
https://www.cnblogs.com/zhangxuan/p/6704915.html
2、https://www.cnblogs.com/kunhu/p/3608109.html

管道的基本理解:
1、管道基于文件描述符,管道建立时,有两个文件描述符:
2、管道是一种把两个进程之间的标准输入和标准输出连接起来的机制,从而提供一种让多个进程间通信的方法,当进程创建管道时,每次都需要提供两个文件描述符来操作管道。其中一个对管道进行写操作,另一个对管道进行读操作。对管道的读写与一般的IO系统函数一致,使用write()函数写入数据,使用read()读出数据。
                                          
3、pipe()函数

#include<unistd.h>
int pipe(int filedes[2]);

返回值:成功,返回0,否则返回-1。参数数组包含pipe使用的两个文件的描述符。
fd[0]:读管道,fd[1]:写管道。必须在fork()中调用pipe(),否则子进程不会继承文件描述符

一般步骤:
1. pipe()创建管道
2. fork()创建子进程
3. 子进程会继承父进程的管道
4.关闭管道: 逐个关闭文件描述符,close().
父子进程间的管道通信:父子进程对管道分别有自己的读写通道,把无关的读端或写段关闭。
掌握函数 pipe()、read()、write()、close()。

实例1.1

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <wait.h>
#define MAX_LINE 80
int main()
{
    int thePipe[2], ret;
    //数组thePipe被填入两个有效的文件描述符。thePipe[0]固定于读管道,thePipe[1]固定于写管道
    char buf[MAX_LINE+1];
    const char *testbuf="a test string.";
    if ( pipe( thePipe ) == 0 )
    {
        printf("thePipe[0]=%d, thePipe[1]=%d \n", thePipe[0], thePipe[1]);
        if (fork() == 0)
        {//child 进程
            close(thePipe[1]);
            ret = read( thePipe[0], buf, MAX_LINE );
            printf("read ret=%d \n", ret);
            buf[ret] = 0;
            printf( "Child read %s\n", buf );
        }
        else
        {//parent 进程
            close(thePipe[0]);
            ret = write( thePipe[1], testbuf, strlen(testbuf) );
            printf("write ret=%d \n", ret);
            ret = wait( NULL );
            printf("wait ret=%d \n", ret);
        }
    }
    //逐个关闭文件描述符
    close(thePipe[0] );
    close(thePipe[1] );
    return 0;
}

运行结果:

补充:

1、文件标识符
thePipe,一般会用fd表示,可以理解为文件的地址,这个int就是文件标识符,相当于FILE *的作用,但他就是一个int。实质上,这个int非常独特,同open函数,int fd里面存着要被操作文件的地址,但它却又不是int *,之后的write和read函数都要根据这个fd所指明的方向来,你可以发现write,read参数都有一个地方,填入了fd,可要求填入的,却是一个int变量,这在windows里面是没有的,同时不了解文件标识符的人,看到write和read的使用可能是云里雾里的,之后的close就更不用说了,就是清楚这个fd与被操作文件的关联。

2、read函数
头文件:#include<unistd.h>
功能:用于从文件描述符对应的文件读取数据(从打开的设备或文件中读取数据)
ssize_t read(int fd,void*buf,size_t count)
参数说明: fd: 是文件描述符 buf: 为读出数据的缓冲区; count: 为每次读取的字节数(是请求读取的字节数,读上来的数据保存在缓冲区buf中,同时文件的当前读写位置向后移)

3、write函数
函数定义:ssize_t write (int fd, const void * buf, size_t count);
函数说明:write()会把参数buf所指的内存写入count个字节到参数放到所指的文件内

4、wait()函数返回的是子进程的PID。

实例1.2

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#define MAX_DATA_LEN 256
#define DELAY_TIME 1
int main()
{
    pid_t pid;//进程pid
    char buf[MAX_DATA_LEN];//数据缓冲区
    const char *data="Pipe Test program";
    int real_read,real_write;
    int pipe_fd[2];
    memset((void*)buf,0,sizeof(buf));//初始化
    if(pipe(pipe_fd)<0)//返回值为-1,创建管道失败
    {
        perror("Pipe create error!\n");
        exit(1);
    }
    if ((pid=fork())<0)
    {
        perror("Fork error!\n");
        exit(1);
    }
    else if (pid==0)
    {//child 进程
        //printf("hhhhh\n");
        close(pipe_fd[1]);
        sleep(DELAY_TIME*3);//延迟3秒
        if ((real_read=read(pipe_fd[0],buf,MAX_DATA_LEN))>0)
        {
            printf("Child receive %d bytes from pipe: '%s'.\n",real_read,buf);
        }
        close(pipe_fd[0]);
        exit(0);
    }
    else
    {
        close(pipe_fd[0]);//关闭读进程
        sleep(DELAY_TIME);
        if ((real_write=write(pipe_fd[1],data,strlen(data)))>0)
        {
            printf("Parent write %d bytes into pipe: '%s'.\n",real_write,data);
        }
        close(pipe_fd[1]);
        waitpid(pid,NULL,0);
        exit(0);
    }
    return 0;
}

运行结果:

 二、有名管道FIFO

参考:
1、https://www.cnblogs.com/fangshenghui/p/4039805.html

概念:命名管道也被称为FIFO文件,是一种特殊的文件。
1. 使不相关的两个进程彼此通信:
a. 通过路径名指出,在文件系统中可见
b. 管道建立后,两进程可按普通文件一样对其操作
2. FIFO 遵循先进先出规则:
a. 对管道读从开始处返回数据
b. 对管道写则把数据添加到末尾
c. 不支持如 lseek()等文件定位操作
3、创建有名管道:mkfifo()
filname是指文件名,而mode是指定文件的读写权限,创建的有名管道的模式,指明其存取权限。S_IFIFO|0666 指明创建一个有名管道且存取权限为0666,即创建者、与创建者同组的用户、其他用户对该有名管道的访问权限都是可读可写。

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *filename, mode_t mode);
mkfifo("/tmp/fifo", S_IFIFO|0666)

4、与普通文件不同的是阻塞问题
•普通文件的读写时不会出现阻塞问题
•在管道的读写中却有阻塞的可能
•非阻塞标志:在 open()函数中设定为 O_NONBLOCK
5、阻塞打开与非阻塞打开:
对于读进程
•若该管道是阻塞打开,且当前 FIFO 内没有数据,则对读进程而言将一直阻塞到有数据写入
•若该管道是非阻塞打开,则不论 FIFO 内是否有数据,读进程都会立即执行读操作。即如果 FIFO 内没有数据,则读函数将立刻    返回 0
对于写进程
•若该管道是阻塞打开,则写操作将一直阻塞到数据可以被写入
•若该管道是非阻塞打开而不能写入全部数据,则读操作进行部分写入或者调用失败

实例2

 read.c

/*fifo_read.c 读管道程序*/
#include <unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<errno.h>
#include<fcntl.h>
#include<string.h>
#include<stdlib.h>
#include<stdio.h>
#include<limits.h>
#define MYFIFO "/tmp/myfifo" /*有名管道文件名*/
/*在 limits.h 中有 #define PIPE_BUF 4096 即 4 个字节大小*/
#define MAX_BUFFER_SIZE PIPE_BUF /*定义在 limits.h 中*/
int main()
{
    char buff[MAX_BUFFER_SIZE];
    int fd;
    int nread;
    /*判断有名管道是否已经存在,若尚未创建,则以相应的权限创建*/
    if(access(MYFIFO,F_OK)==-1)
    {
        if((mkfifo(MYFIFO,0666)<0)&&(errno!=EEXIST))
        {
            printf("Cannot create fifo file\n");
            exit(1);
        }
    }
    printf("hello\n");
    /*以只读阻塞方式打开有名管道*/
    fd=open(MYFIFO,O_RDONLY);
    //printf("hello\n");
    if(fd==-1)
    {
        printf("Open fifo file error\n");
        exit(1);
    }
    while(1)
    {
        memset(buff,0,sizeof(buff));
        if((nread=read(fd,buff,MAX_BUFFER_SIZE))>0)
        {
            printf("Read '%s' from FIFO\n",buff);
        }
//假设读取到 exit 的时候退出
        if(!strcmp(buff,"exit")) break;
    }
    close(fd);
    exit(0);
}/*end*/

write.c

#include <unistd.h>
#include<sys/types.h>
#include<sys/stat.h>#include<errno.h>
#include<fcntl.h>
#include<stdlib.h>
#include<stdio.h>
#include<limits.h>
#define MYFIFO "/tmp/myfifo" /*有名管道文件名*/
/*在 limits.h 中有 #define PIPE_BUF 4096 即 4 个字节大小*/
#define MAX_BUFFER_SIZE PIPE_BUF /*定义在 limits.h 中*/
int main(int argc,char *argv[]) /*参数为即将写入的字符串*/
{
    int fd;
    char buff[MAX_BUFFER_SIZE];
    int nwrite;
    if(argc<=1)
    {
        printf("Usage: ./fifo_write string\n");
        exit(1);
    }
    /*sscanf()表示从字符串中格式化输出,与 scanf 类似,都是用于输入的,只是
    scanf()以键盘为输入源,sscanf()是以固定字符串为输入源*/
    sscanf(argv[1],"%s",buff);/*将 argv[1]的内容以字符串(%s)的形式存入 buf 中*/
    /*以读写阻塞方式打开 FIFO 管道*/
    fd=open(MYFIFO,O_RDWR);
    if(fd==-1)
    {
        printf("Open fifo file error\n");
        exit(1);
    }
    /*向管道中写入字符串*/
    if((nwrite=write(fd,buff,MAX_BUFFER_SIZE))>0)
    {
        printf("Write '%s' to FIFO\n",buff);
    }
    close(fd);
    exit(0);
} /*end*/

运行步骤:
1、运行read.c,让读进程等待
2、运行write.c,此时可以发现读进程已经可以读了
3、写进程输入exit,退出。
建议使用CTRL+SHIFT+T使用多个窗口运行。
                                            

1、open()函数

int open(const char*pathname,int flags);
参数说明:
1.pathname 要打开或创建的目标文件
2.flags 打开文件时,可以传入多个参数选项,用下面的 一个或者多个常量进行“或”运算,构成falgs
参数:
O_RDONLY: 只读打开
O_WRONLY: 只写打开
O_RDWR: 读,写打开
这三个常量,必须制定一个且只能指定一个
返回值
成功:新打开的文件描述符
失败:-1
open返回的文件描述符一定是最小的而且没有被使用的

三、设计说明学会使用有名管道在多进程间建立通信

#include <unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<errno.h>
#include<fcntl.h>
#include<stdlib.h>
#include<stdio.h>
#include<limits.h>
#define MYFIFO "/tmp/myfifo" /*有名管道文件名*/
/*在 limits.h 中有 #define PIPE_BUF 4096 即 4 个字节大小*/
#define MAX_BUFFER_SIZE PIPE_BUF /*定义在 limits.h 中*/
int main(int argc,char *argv[]) /*参数为即将写入的字符串*/
{
    char buff[MAX_BUFFER_SIZE];
    int fd,p;
    int nread,nwrite;
    /*判断有名管道是否已经存在,若尚未创建,则以相应的权限创建*/
    if(access(MYFIFO,F_OK)==-1)
    {
        if((mkfifo(MYFIFO,0666)<0)&&(errno!=EEXIST))
        {
            printf("Cannot create fifo file\n");
            exit(1);
        }
    }
    p=fork();
    if(p==0)
    {
        //child 进程
        //以写方式打开命名管道
        sleep(5);//保证先执行读进程,让读进程进入阻塞队列
        printf("子进程 write 数据\n");
        if(argc<=1)
        {
            printf("Usage: ./fifo_write string\n");
            exit(1);
        }
        /*sscanf()表示从字符串中格式化输出,与 scanf 类似,都是用于输入的,只是
        scanf()以键盘为输入源,sscanf()是以固定字符串为输入源*/
        sscanf(argv[1],"%s",buff);/*将 argv[1]的内容以字符串(%s)的形式存入 buf 中*/
        /*以读写阻塞方式打开 FIFO 管道*/
        fd=open(MYFIFO,O_RDWR);
        if(fd==-1)
        {
            printf("Open fifo file error\n");
            exit(1);
        }
        /*向管道中写入字符串*/
        if((nwrite=write(fd,buff,MAX_BUFFER_SIZE))>0)
        {
            printf("Write '%s' to FIFO\n",buff);
        }
        printf("成功写入数据.\n");
        close(fd);
    }
    else
    {
        //parent 进程
        printf("父进程read 数据\n");
        fd=open(MYFIFO,O_RDONLY);
        //printf("hello\n");
        if(fd==-1)
        {
            printf("Open fifo file error\n");
            exit(1);
        }
        memset(buff,0,sizeof(buff));
        if((nread=read(fd,buff,MAX_BUFFER_SIZE))>0)
        {
            printf("Read '%s' from FIFO\n",buff);
        }
        close(fd);
    }

} /*end*/

发布了43 篇原创文章 · 获赞 23 · 访问量 5317

猜你喜欢

转载自blog.csdn.net/weixin_43442778/article/details/95195629
今日推荐