APUE读书笔记---进程间通信(IPC)之管道和有名管道(FIFO)

APUE读书笔记—进程间通信(IPC)之管道和有名管道(FIFO)

1. 管道

pipe函数可以创建管道,提供一个单向数据流(半双工)。

#include <unistd.h>

int pipe(int pipefd[2]);
//返回值:若成功,返回0,若出错,返回-1
  • 该函数返回两个文件描述符,fd[0],fd[1]。前者打开来读,后者打开来写。所以管道在用户程序看起来像是一个打开的文件,通过read(fd[0])或者write(fd[1])。向这个文件读写数据其实是在读写内核缓冲区。
  • 调用pipe函数时在内核中开辟一块缓冲区(称为管道)用于通信,它有一个读端一个写端,然后通过pipefd参数传出给用户程序两个文件描述符。
  • 管道作用于有血缘关系的进程之间,先pipe,再通过fork来传递。
  • 管道使用环形队列实现的,数据从写端流入从读端流出。

1.1 管道如何进行通信

这里写图片描述

  • 父进程调用pipe函数在内核中开辟管道,得到两个文件描述符指向管道的两端。
  • 父进程调用fork函数,子进程共享两个文件描述符指向同一管道。
  • 父进程关闭管道读端,子进程关闭管道写端。父进程可以往管道里写,子进程可以从管道里读。

1.2 管道实例

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

int main(void)
{
    int fd[2];
    pid_t pid;
    int len;
    int flag;
    char str[1024] = "hello world\n";
    char buf[1024];

    if(pipe(fd) < 0){
        perror("pipe err"); 
        exit(1);
    }

    //父写子读
    if((pid = fork()) < 0){
        perror("fork err"); 
        exit(1);
    }else if(pid > 0){  //parent
        close(fd[0]);   //关闭父进程读端
        sleep(5);
        write(fd[1], str, strlen(str));
        close(fd[1]);       //写完关闭写端
        wait(NULL);
    }else{      //child
        close(fd[1]);   //关闭子进程写端

        //设置子进程读端为非阻塞,如失败返回EAGAIN
        flag = fcntl(fd[0], F_GETFL);
        flag |= O_NONBLOCK;
        fcntl(fd[0], F_SETFL, flag);

tryagain:
        len = read(fd[0], buf, sizeof(str));

        if(len < 0){
            if(errno == EAGAIN){    //非阻塞读若失败则返回EAGAIN
                write(STDOUT_FILENO, "try again\n", 10);
                sleep(1);
                goto tryagain;
            }else{
                perror("read err"); 
                exit(1);
            }
        }   

        close(fd[0]);       //读完关闭读端
        write(STDOUT_FILENO, buf, len);
    }

    return 0;
}

父进程睡眠5秒后为子进程写数据,子进程每秒尝试读数据,读不到打印出try again,否则打印出读到的数据,运行如下:

try again
try again
try again
try again
try again
hello world

1.3 管道的使用事项

  1. 两个进程通过一个管道只能实现单向通信,如果要实现双向通信必须使用两个管道。
  2. 写端关闭,当读端读完管道的内容,再次读,返回0,相当于读到EOF。
  3. 如果管道写端没关闭,写端暂时无数据,读端读完管道里的数据后再次读,读端会阻塞。
  4. 读端关闭,写端写管道,会产生SIGPIPE信号,写进程默认情况下会终止进程。
  5. 读端没读管道数据,当写端写满管道后,再次写,写端阻塞。

1.4 管道大小

利用fpathconf函数可以查看本机管道大小

#include <stdio.h>
#include <unistd.h>

int main()
{
    int fd[2];
    pipe(fd);
    printf("pipe buf = %ld\n", fpathconf(fd[1], _PC_PIPE_BUF));

    return 0;
}

运行结果:

pipe buf = 4096

2. FIFO有名管道

FIFO是指先进先出(first in first out),它是一个单向数据流(半双工),每个FIFO有一个路径名与之关联,从而允许无亲缘关系的进程访问同一个FIFO。FIFO又称有名管道。

#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);
//返回值:若成功,返回0,若出错,返回-1
  • pathname是一个普通的路径名,它是FIFO的名字。
  • mode指定文件权限位。
  • mkfifo函数隐含包含O_CREATE | O_EXCL。也就是说,要么创建一个新的FIFO,要么返回一个EEXIST错误。

2.1 例子:无亲缘关系的客户与服务器

服务器主函数代码 server_main,c
#include "unpipc.h"


int main(int argc, char *argv[])
{
    int readfd, writefd;

    //create two FIFO
    if((mkfifo(FIFO1, FILE_MODE) < 0) && (errno != EEXIST))
        sys_err("can't create FIFO1\n");
    if((mkfifo(FIFO2, FILE_MODE) < 0) && (errno != EEXIST)) {
        unlink(FIFO1); 
        sys_err("can't create FIFO2\n");
    }

    readfd = open(FIFO1, O_RDONLY, 0);
    writefd = open(FIFO2, O_WRONLY, 0);

    server(readfd, writefd);

    exit(0);
}
服务器函数代码 server.c
#include "unpipc.h"


void server(int readfd, int writefd)
{
    int fd;
    ssize_t n;
    char buff[MAXLINE + 1];

    //read pathname from ipc channel
    if((n = read(readfd, buff, MAXLINE)) == 0)
        sys_err("end-of-file while reading pathname");
    buff[n] = '\0';

    if((fd = open(buff, O_RDONLY)) < 0) {
        snprintf(buff + n, sizeof(buff) - n, ": can't open, %s\n", strerror(errno)); //tell client error
        n = strlen(buff);
        write(writefd, buff, n);
    } else {
        while((n = read(fd, buff, MAXLINE)) > 0)
            write(writefd, buff, n);
        close(fd);
    }
}
客户端主函数代码 client_main.c
#include "unpipc.h"

int main(int argc, char *argv[])
{
    int readfd, writefd;

    writefd = open(FIFO1, O_WRONLY, 0);
    readfd = open(FIFO2, O_RDONLY, 0);

    client(readfd, writefd);

    close(readfd);
    close(writefd);

    unlink(FIFO2);
    unlink(FIFO1);
    exit(0);
客户端函数代码 client.c
#include "unpipc.h"

void client(int readfd, int writefd)
{
    size_t len;
    ssize_t n;
    char buff[MAXLINE];

    //read pathname
    fgets(buff, MAXLINE, stdin);    //fgets 会读最后一个‘\n’字节
    len = strlen(buff);
    if(buff[len-1] == '\n')
        len--;  //del newline from fgets

    //write pathname to ipc channel
    write(writefd,  buff, len);

    //read from ipc. write to standard output
    while((n = read(readfd, buff, MAXLINE)) > 0)
        write(STDOUT_FILENO, buff, n);
}
头文件 unpipc.h
#ifndef _UNPIPE_H_
#define _UNPIPE_H_

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

#define MAXLINE 4096
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)

#define FIFO1 "/home/menwen/work/APUE/IPC/apue_code/fifo.1"
#define FIFO2 "/home/menwen/work/APUE/IPC/apue_code/fifo.2"


void client(int readfd, int writefd); 
void server(int readfd, int writefd);
void sys_err(char *str);

#endif
出错处理函数代码
//sys_err.c是我自己简单封装的一个出错处理函数
#include "unpipc.h"

void sys_err(char *str)
{
    perror(str); 
    exit(1);
}
  • 编译:
gcc server.c server_main.c unpipc.h sys_err.c -o server
gcc client.c client_main.c unpipc.h -o client          
  • 运行:
➜  apue_code ./server & //后台运行服务器端代码
[1] 21581 
➜  apue_code ./client       //在执行客户端代码
/home/menwen/work/APUE/IPC/apue_code/unpipc.h   //输入一个文件的路径,就会通过FIFO得到文件内容如下:正是unpipc.h的内容
#ifndef _UNPIPE_H_
#define _UNPIPE_H_

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

#define MAXLINE 4096
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)

#define FIFO1 "/home/menwen/work/APUE/IPC/apue_code/fifo.1"
#define FIFO2 "/home/menwen/work/APUE/IPC/apue_code/fifo.2"


void client(int readfd, int writefd); 
void server(int readfd, int writefd);
void sys_err(char *str);

#endif
[1]  + 21581 done       ./server
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值