命名管道的阻塞和非阻塞模式的初步探讨

c/c++

浏览数:239

2019-3-30

前言

进程间通信(IPC, InterProcess Communication)是指在不同进程之间传播或交换信息。
主要的方式有管道(包括无名管道,高级管道和命名管道),消息队列, 信号量, 共享内存, Socket等。 其中Socket可以用于不同主机上的进程间通信。
进程通信的主要目的如下:

  • 数据传输:一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几M字节之间

  • 共享数据:多个进程想要操作共享数据,一个进程对共享数据的修改,别的进程应该立刻看到。

  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。

  • 资源共享:多个进程之间共享同样的资源。为了作到这一点,需要内核提供锁和同步机制。

  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

IPC方式

  • 匿名管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。

  • 高级管道(popen):将另一个程序当做一个新的进程在当前程序进程中启动,则它算是当前程序的子进程,这种方式我们成为高级管道方式。

  • 命名管道 (named pipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。

  • 消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

  • 信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。

  • 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

  • 共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。

  • 套接字( socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。

命名管道

本文主要讲解命令管道。
命名管道是在文件系统中作为一个特殊的设备文件存在的, 不同祖先的进程之间可以通过管道共享数据,当所有访问管道的进程执行结束后,命名管道将继续存在以便以后使用。

命名管道的创建

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);

mkfifo用于创建一个FIFO文件, 参数mode决定文件权限。
The file creation mask of the process (see umask()) modifies the file permission bits of mode. The owner ID of the FIFO is set to the effective user ID of the process. The FIFO group ID is set to the effective group ID of the process.

如果成功,那么mkfifo()返回0;如果失败,那么返回-1, 并且errno被赋为下面的值:

  • EACCES: 某级路径无访问权限,或pathname指向的目录无写权限

  • EEXIST: 同名文件已存在

  • EINVAL: 在NuTCRCKER平台上描述路径不在fifos文录下

  • ENAMETOOLONG: pathname名太长

  • ENOENT: 路径中某一级目录不存在

  • ENOSPC: 磁盘满

  • ENOTDIR: 路径中某一级不是目录

  • EROFS:文件系统只读

详细信息可以查看: [这里](http://man7.org/linux/man-pag…

file mode如下:

  • S_IRWXU: 00700 read, write, execute/search by owner

  • S_IRUSR: 00400 read permission, owner

  • S_IWUSR: 00200 write permission, owner

  • S_IXUSR: 00100 execute/search permission, owner

  • S_IRWXG: 00070 read, write, execute/search by group

  • S_IRGRP: 00040 read permission, group

  • S_IWGRP: 00020 write permission, group

  • S_IXGRP: 00010 execute/search permission, group

  • S_IRWXO: 00007 read, write, execute/search by others

  • S_IROTH: 00004 read permission, others

  • S_IWOTH: 00002 write permission, others

  • S_IXOTH: 00001 execute/search permission, others

  • S_ISUID: 0004000 set-user-ID on execution

  • S_ISGID: 0002000 set-group-ID on execution

  • S_ISVTX: 0001000 on directories, restricted deletion flag

系统I/O函数如open, close, read, write都可以操作FIFO文件

读/写,阻塞/非阻塞

阻塞

默认不指定O_NONBLOCK时即为阻塞模式

  • open以只读方式打开FIFO时, 要阻塞到某个进程为写而打开此FIFO

  • open以只写方式打开FIFO时,要阻塞到某个进程为读而打开此FIFO

  • open以只读+只写方式打开FIFO时,调用read函数时read会阻塞

  • 调用write函数向FIFO写数据时,当缓冲区已满时write会阻塞

  • 通信过程中若写进程先退出了,则调用read函数从FIFO里读数据时不阻塞;若写进程又重新运行,则调用read函数从FIFO里读数据时又恢复阻塞

  • 通信过程中若读进程先退出, 则写进程向FIFO写数据,会收到SIGPIPE信号而退出

  • open以读写方式打开FIFO时,open不阻塞

非阻塞

  • open以只读方式打开FIFO,如果没有进程为写而打开FIFO, 只读open成功,并且open不阻塞

  • open以只写方式打开FIFO, 如果没有进程为读而打开FIFO,只写open将出错返回-1 (ENXIO: No Such device or address)

  • read, write读写FIFO中数据时不阻塞

  • 通信过程中, 读进程退出后, 写进程向命名管道内写数据时,写进程会收到SIGPIPE信号而退出

Examples

Non-Blocking

reader

//
// Created by         : Harris Zhu
// Filename           : fifo_read.cpp
// Author             : Harris Zhu
// Created On         : 2017-08-17 16:46
// Last Modified      :
// Update Count       : 2017-08-17 16:46
// Tags               :
// Description        :
// Conclusion         :
//
//=======================================================================


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

#define FIFO_SERVER "myfifo"
#define OPEN_MODE O_RDONLY | O_NONBLOCK
#define FIFO_MODE O_CREAT|O_RDWR|O_NONBLOCK

int main(int argc, char** argv) {
    char buf_r[100];
    int fd;
    int nread;
    int res;
    if (((res=mkfifo(FIFO_SERVER, FIFO_MODE)) < 0) && (errno != EEXIST))
    {
        printf("can not creat fifoserver %d :\n", res, errno);
        exit(1);
    }
    printf("preparing for reading bytes...\n");
    char cmd[100];
    sprintf(cmd, "chmod 704 %s", FIFO_SERVER);
    system(cmd);
    fd = open(FIFO_SERVER, OPEN_MODE);
    if (fd == -1) {
        perror("error in openning fifo server");
        exit(1);
    }
    int i=0;
    int len;
    while (i++<21)
    {
        memset(buf_r, 0, sizeof(buf_r));
        if ((nread = read(fd, buf_r, sizeof(buf_r))) < 0) {
            if (errno == EAGAIN)
            {
                printf("no data yet\n");
                sleep(1);
            }
        } else {
            if(nread > 0)
            {
                printf("read %s from FIFO %d \n", buf_r, i);
            }
            sleep(1);
        }
    }
//    pause();
    close(fd);
    unlink(FIFO_SERVER);
    return 0;
}

writer

//
// Created by         : Harris Zhu
// Filename           : fifo_write.c
// Author             : Harris Zhu
// Created On         : 2017-08-17 18:04
// Last Modified      :
// Update Count       : 2017-08-17 18:04
// Tags               :
// Description        :
// Conclusion         :
//
//=======================================================================

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

#define FIFO_SERVER "myfifo"
#define FIFO_MODE O_CREAT|O_NONBLOCK|O_RDWR
#define FILE_MODE O_WRONLY | O_NONBLOCK

int main(int argc, char** argv)
{
    int fd;
    char w_buf[100];
    char w_t_buf[50];
    const char *hstr = "hello world";
    if(mkfifo(FIFO_SERVER, FIFO_MODE<0)&& (errno != EEXIST))
    {
        perror("failed to create fifo server");
        exit(1);
    }

    char cmd[100];
    sprintf(cmd, "chmod 704 %s", FIFO_SERVER);
    system(cmd);

    int nwrite;
    fd = open(FIFO_SERVER, FILE_MODE);
    if (fd == -1)
    {
        if (errno == ENXIO)
        {
            printf("open errop;no reading process\n");
        } else {
            perror("open error");
            exit(EXIT_FAILURE);
        }
    }

    if (argc >= 2)
    {
        strcpy(w_t_buf, argv[1]);
    } else {
        strcpy(w_t_buf, hstr);
    }
    int i=0;
    int n;
    time_t tp;
    while(i++<20)
    {
        time(&tp);
        n=sprintf(w_buf, "Process %d is sending %s at %s", getpid(), w_t_buf, ctime(&tp));
        if ((nwrite = write(fd, w_buf, n)) < 0)
        {
            if (errno == EAGAIN)
            {
                printf("the fifo has not been read yet.Please try later\n");
            } else {
                exit(1);
            }
        }
        printf("Send Message to FIFO: %s \n", w_buf);
        sleep(1);
    }
    close(fd);
    return 0;
}

先运行reader, 然后运行writer
Makefile内容如下

build:
    gcc -g read.c -o read
    gcc -g write.c -o write

run:
    xterm -e ./read &
    sleep 1
    xterm -e ./write &

clean:
    rm -rf read write

因为reader先开读,当writer开始的时候,reader已经在while里走过和个循环了,所以reader会先结束,这时writer还想向FIFO里写数据,就会遇到“Broken pipe”问题
如果把writer的等待时间改小, 那么收端比较慢,所以fifo里的数据会比较多,reader收到的数据就不是一行一行

harriszh Fri 17:30@ ~/trunk/cpp/fifo1$ ./fifo_read
preparing for reading bytes...
read Process 11863 is sending hello world at Fri Aug 18 17:31:57 2017
Process 11863 is sending hello worl from FIFO
read d at Fri Aug 18 17:31:57 2017
Process 11863 is sending hello world at Fri Aug 18 17:31:57 2017
Proce from FIFO
read ss 11863 is sending hello world at Fri Aug 18 17:31:57 2017
Process 11863 is sending hello world at  from FIFO
read Fri Aug 18 17:31:57 2017
Process 11863 is sending hello world at Fri Aug 18 17:31:57 2017
Process 11 from FIFO
read 863 is sending hello world at Fri Aug 18 17:31:57 2017
Process 11863 is sending hello world at Fri A from FIFO
read ug 18 17:31:57 2017
Process 11863 is sending hello world at Fri Aug 18 17:31:57 2017
Process 11863 i from FIFO
read s sending hello world at Fri Aug 18 17:31:57 2017
Process 11863 is sending hello world at Fri Aug 18 from FIFO
read  17:31:57 2017
Process 11863 is sending hello world at Fri Aug 18 17:31:57 2017
Process 11863 is sen from FIFO
read ding hello world at Fri Aug 18 17:31:57 2017
Process 11863 is sending hello world at Fri Aug 18 17:3 from FIFO
read 1:57 2017
Process 11863 is sending hello world at Fri Aug 18 17:31:57 2017
Process 11863 is sending  from FIFO
read hello world at Fri Aug 18 17:31:57 2017
Process 11863 is sending hello world at Fri Aug 18 17:31:57  from FIFO
read 2017
Process 11863 is sending hello world at Fri Aug 18 17:31:57 2017
Process 11863 is sending hello from FIFO
read  world at Fri Aug 18 17:31:57 2017
Process 11863 is sending hello world at Fri Aug 18 17:31:57 2017
 from FIFO

Blocking

看完非阻塞再看阻塞就简单点了
代码如下
Reader

//
// Created by         : Harris Zhu
// Filename           : write.c
// Author             : Harris Zhu
// Created On         : 2017-08-17 22:05
// Last Modified      :
// Update Count       : 2017-08-17 22:05
// Tags               :
// Description        :
// Conclusion         :
//
//=======================================================================

#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<signal.h>
#define ERR_EXIT(m) \
    do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

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

    int outfd = open("Makefile2", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (outfd == -1)
        ERR_EXIT("open Makefile2 error");

    int infd;
    infd = open("tp", O_RDONLY );
    if (infd == -1)
        ERR_EXIT("open tp error");

    char buf[1024];
    int n;
    while ((n = read(infd, buf, 1024)) > 0)
        write(outfd, buf, n);

    close(infd);
    close(outfd);
    unlink("tp"); // delete a name and possibly the file it refers to
    return 0;
}

Writer

//
// Created by         : Harris Zhu
// Filename           : read.c
// Author             : Harris Zhu
// Created On         : 2017-08-17 22:05
// Last Modified      :
// Update Count       : 2017-08-17 22:05
// Tags               :
// Description        :
// Conclusion         :
//
//=======================================================================

#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<signal.h>
#define ERR_EXIT(m) \
    do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

int main(int argc, char *argv[])
{
    mkfifo("tp", 0644);
    int infd = open("Makefile", O_RDONLY );
    if (infd == -1)
        ERR_EXIT("open Makefile error");

    int outfd;
    outfd = open("tp", O_WRONLY );
    if (outfd == -1)
        ERR_EXIT("open tp error");

    char buf[1024];
    int n;
    while ((n = read(infd, buf, 1024)) > 0)
        write(outfd, buf, n);
    printf("Makefile2 is created successfully!\n")

    close(infd);
    close(outfd);

    return 0;
}

先运行writer, 再运行reader, 原因是FIFO由writer创建
Makefile

build:
    gcc -g read.c -o read
    gcc -g write.c -o write

clean:
    rm -rf Makefile2
    rm -rf ./read ./write

run:
    xterm -e ./write &
    sleep 0.1
    xterm -e ./read &

.PHONY: clean

运行后,reader端会把从writer写入的内容保存到Makefile2里

后言

非阻塞模式的理解有点绕,在编写程序时也要注意两者的通讯机制以及关闭条件。
可以自己先写个例子,看看能不能正确工作。一般第一个例子是非常难以正确工作的,需要在调试的过程中,来加深了解。
如果有任何疑问,欢迎发邮件给我。