【命名管道】命名管道(FIFO)与进程通信

目录

0. 命名管道

1. 有名管道的创建

2. 有名管道的基本读写操作

3. 有名管道实现进程间通信

4. 有名管道的读写规律(阻塞)

4.1 读写端都存在,只读不写

4.2 读写端都存在,只写不读

4.3 同一个进程中,只有读端,没有写端

4.4 同一个进程中,只有写段,没有读端

扫描二维码关注公众号,回复: 16997854 查看本文章

4.5 一个进程只读,一个进程只写

 总结:


0. 命名管道

命名管道(FIFO)和管道(pipe)基本相同,但也有一些显著的不同。

其特点是:

  • 半双工,数据在同一时刻只能在一个方向上流动。
  • 写入FIFO中的数据遵循先入先出的规则。
  • FIFO所传送的数据是无格式的,这要求FIFO的读出方与写入方必须事先约定好数据的格式,如多少字节算一个消息等。
  • FIFO在文件系统中作为一个特殊的文件而存在并且在文件系统中可见,所以有名管道可以实现不相关进程间通信,FIFO中的内容却存放在内存中。
  • 管道在内存中对应一个缓冲区。不同的系统其大小不一定相同。
  • 从FIFO读数据是一次性操作,数据一旦被读取,它就从FIFO中被抛弃,释放空间以便写更多的数据。
  • 当使用FIFO的进程退出后,FIFO文件将继续保存在文件系统中以便以后使用。
  • FIFO有名字,不相关的进程可以通过打开命名管道进行通信。

1. 有名管道的创建

方法1:用shell命令mkfifo创建有名管道

mkfifo 文件名

 

方法2:使用函数mkfifo

#include<sys/types.h>

#include<sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);

功能:创建一个有名管道,产生一个本地文件系统可见的文件pathname

参数:

        pathname:有名管道创建生成的文件,可以代码路径

        mode: 管道文件的权限,一般通过八进制数设置即可,例如0664

返回值:

        成功:0

        失败:-1

代码示例:

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

int main()
{
    if (mkfifo("fifo_file", 0664) == -1)
    {
        //              printf("errno:%d\n",errno);
        if (errno != EEXIST)
        {
            perror("fail to mkfifo");
            exit(1);
        }
    }
    return 0;
}

2. 有名管道的基本读写操作

        由于有名管道在本地创建了一个管道文件,所以系统调用的IO函数基本都可以对有名管道进行操作,但是不能使用lseek修改管道文件的偏移量。

        注意:有名管道创建的本地的文件只是起到表示作用,真正有名管道实现进程间通信还是在内核空间开辟内存,所以本地产生的文件只是一个标识,没有其他作用,对本地管道文件的操作实质就是对内核空间的操作。

代码案例:

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

#define FIFONAME "fifo_file"
int main()
{
    if (mkfifo(FIFONAME, 0664) == -1)
    {
        //              printf("errno:%d\n",errno);
        if (errno != EEXIST)
        {
            perror("fail to mkfifo");
            exit(1);
        }
    }

    int fd;
    //打开
    fd = open(FIFONAME, O_RDWR);
    if (fd == -1)
    {
        perror("fail to open");
        exit(1);
    }
    //写入数据
    if (write(fd, "hello world", strlen("hello world")) == -1)
    {
        perror("fail to write");
        exit(1);
    }
    //再次写数据
    write(fd, "nihao bejing", strlen("hello world"));
    char buf[32] = "";
    //读数据
    if (read(fd, buf, sizeof(buf)) == -1)
    {
        perror("fail to read");
        exit(1);
    }
    printf("buf = [%s]\n", buf);

    /*     此时管道中无数据,再读则阻塞
    if (read(fd, buf, sizeof(buf)) == -1)
    {
        perror("fail to read");
        exit(1);
    }
    */
    //关闭
    close(fd);
    return 0;
}

3. 有名管道实现进程间通信

        由于有名管道在本地创建了一个管道文件,所有不相关的进程间也可以实现通信。

下面程序1和程序2分别是两个不同的进程通过命名管道进行通信。

程序1:

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

#define FIFO_01 "fifo_01"
#define FIFO_02 "fifo_02"


int main()
{
    if (mkfifo(FIFO_01, 0664) == -1)
    {
        if (errno != EEXIST)
        {
            perror("fail to mkfifo");
            exit(1);
        }
    }

    if (mkfifo(FIFO_02, 0664) == -1)
    {
        if (errno != EEXIST)
        {
            perror("fail to mkfifo");
            exit(1);
        }
    }
    int fd_w, fd_r;
    if ((fd_w = open(FIFO_01, O_WRONLY)) == -1)
    {
        perror("fail to oepn");
        exit(1);
    }
    if ((fd_r = open(FIFO_02, O_RDONLY)) == -1)
    {
        perror("fail to open");
        exit(1);
    }

    //      printf("%d %d\n",fd_w,fd_r);
    char buf[128] = "";
    ssize_t bytes;
    pid_t pid;
    pid = fork();

    if (pid < 0)
    {
        perror("fail to fokr");
        exit(1);
    }
    else if (pid > 0)
    {
        while (1)
        {
            fgets(buf, sizeof(buf), stdin);
            buf[strlen(buf) - 1] = '\0';

            if ((bytes = write(fd_w, buf, sizeof(buf))) == -1)
            {
                perror("fail to write");
                exit(1);
            }
        }
    }
    else
    {
        while (1)
        {
            if ((bytes = read(fd_r, buf, sizeof(buf))) == -1)
            {
                perror("fail to read");
                exit(1);
            }
            if (bytes)
            {
                buf[bytes] = '\0';
                printf("from fifo_02: %s\n", buf);
                memset(buf, 0, sizeof(buf));
            }
        }
    }
    return 0;
}

程序2:

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

#define FIFO_01 "fifo_01"
#define FIFO_02 "fifo_02"


int main()
{
    if (mkfifo(FIFO_01, 0664) == -1)
    {
        if (errno != EEXIST)
        {
            perror("fail to mkfifo");
            exit(1);
        }
    }

    if (mkfifo(FIFO_02, 0664) == -1)
    {
        if (errno != EEXIST)
        {
            perror("fail to mkfifo");
            exit(1);
        }
    }
    int fd_w, fd_r;
    if ((fd_r = open(FIFO_01, O_RDONLY)) == -1)
    {
        perror("fail to open");
        exit(1);
    }
    if ((fd_w = open(FIFO_02, O_WRONLY)) == -1)
    {
        perror("fail to oepn");
        exit(1);
    }

    //      printf("%d %d\n",fd_w,fd_r);
    char buf[128] = "";
    ssize_t bytes;
    pid_t pid;
    pid = fork();

    if (pid < 0)
    {
        perror("fail to fork");
        exit(1);
    }
    else if (pid > 0)
    {
        while (1)
        {
            if ((bytes = read(fd_r, buf, sizeof(buf))) == -1)
            {
                perror("fail to read");
                exit(1);
            }
            if (bytes)
            {
                buf[bytes] = '\0';
                printf("from fifo_01: %s\n", buf);
                memset(buf, 0, sizeof(buf));
            }
        }
    }
    else
    {
        while (1)
        {

            fgets(buf, sizeof(buf), stdin);
            buf[strlen(buf) - 1] = '\0';

            if ((bytes = write(fd_w, buf, sizeof(buf))) == -1)
            {
                perror("fail to write");
                exit(1);
            }
        }
    }
    return 0;
}

 运行截图:

4. 有名管道的读写规律(阻塞)

4.1 读写端都存在,只读不写

    //读写端都存在,只读不写
    //如果原本管道中有数据,则正常读取
    //如果管道中没有数据,则read函数会阻塞等待

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

#define FIFONAME1 "myfifo"

int main()
{
    if (mkfifo(FIFONAME1, 0664) == -1)
    {
        if (errno != EEXIST)
        {
            perror("fail to mkfifo");
            exit(1);
        }
    }
    //读写端都存在,只读不写
    //如果原本管道中有数据,则正常读取
    //如果管道中没有数据,则read函数会阻塞等待
    int fd;
    if ((fd = open(FIFONAME1, O_RDWR)) == -1)
    {
        perror("fail to open");
        exit(1);
    }

    write(fd, "hello world", 11);

    char buf[128] = "";
    read(fd, buf, sizeof(buf));
    printf("buf = %s\n", buf);
    
    read(fd, buf, sizeof(buf));
    printf("buf = %s\n", buf);

    return 0;
}

执行结果:

4.2 读写端都存在,只写不读

    //读写端都存在,只写不读
    //当有名管道的缓冲区写满后,write函数会发送阻塞
    //默认有名管道的缓冲区为64K字节

代码示例:

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

#define FIFONAME "myfifo"

int main()
{
    if (mkfifo(FIFONAME, 0664) == -1)
    {
        if (errno != EEXIST)
        {
            perror("fail to mkfifo");
            exit(1);
        }
    }
    
    //读写端都存在,只写不读
    //当有名管道的缓冲区写满后,write函数会发送阻塞
    //默认有名管道的缓冲区为64K字节
    int fd;
    if ((fd = open(FIFONAME, O_RDWR) == -1)
    {
        perror("fail to open");
            exit(1);
    }

    int num = 0;
        while (1)
        {
            write(fd, "", 1024);
                num++;
                printf("num = %d\n", num);
        }
    return 0;
}

执行结果:

4.3 同一个进程中,只有读端,没有写端

    //在一个进程中,只有读端,没有写端
    //会在open函数的位置阻塞

代码示例:

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

#define FIFONAME1 "myfifo"

int main()
{
    if (mkfifo(FIFONAME1, 0664) == -1)
    {
        if (errno != EEXIST)
        {
            perror("fail to mkfifo");
            exit(1);
        }
    }
    //在一个进程中,只有读端,没有写端
    //会在open函数的位置阻塞
    printf("********************************\n");
    int fd;
    if ((fd = open(FIFONAME1, O_RDONLY)) == -1)
    {
        perror("fail to open");
        exit(1);
    }
    printf("___________________________\n");

    char buf[128] = "";
    ssize_t bytes;
    if ((bytes = read(fd, buf, sizeof(buf))) == -1)
    {
        perror("fail to read");
        exit(1);
    }
    printf("buf = %s\n", buf);
    printf("bytes = %ld\n", bytes);

    return 0;
}

执行结果:

4.4 同一个进程中,只有写段,没有读端

    //在一个进程中只有写端,没有读端
    //会在open函数的位置阻塞

代码示例:

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

#define FIFONAME "myfifo"

int main()
{
    if (mkfifo(FIFONAME, 0664) == -1)
    {
        if (errno != EEXIST)
        {
            perror("fail to mkfifo");
            exit(1);
        }
    }
    //在一个进程中只有写端,没有读端
    //会在open函数的位置阻塞
    int fd;
    if ((fd = open(FIFONAME, O_WRONLY)) == -1)
    {
        perror("fail to open");
        exit(1);
    }

    write(fd, "hello world", 11);
    printf("****************************\n");
    return 0;
}

执行结果:

4.5 一个进程只读,一个进程只写

将上面4.3和4.4代码一起运行,保证有名管道读写端都存在。

规律:

        只要保证有名管道的读写端都存在,不管是几个进程,都不会在open这里阻塞了。

        如果一个进程只读,一个进程只写,都运行后,如果关系写端,读端read会返回0;

        如果一个进程只读,一个进程只写,都运行后,如果关闭读端,写端会立即产生SIGPIPE信号,默认的处理方式是退出进程。

5.有名管道的读写规律(非阻塞)

        指定O_NONBLOCK(即open位或O_NONBLOCK)

  • 先以只读方式打开,如果没有进程为写而打开一个FIFO,只读open成功,并且open不阻塞
  • 先以只写方式打开,如果没有进程为读而打开一个FIFO,只写open将出错返回-1
  • read、write读写命名管道中读数据时不阻塞
  • 通信过程中,读进程退出后,写进程想命名管道内些数据时,写进程也会(收到SIGPIPE信号)退出。

代码示例:

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

#define FIFONAME "myfifo"

int main()
{
    int  fd;

    if (mkfifo(FIFONAME, 0664) == -1)
    {
        if (errno != EEXIST)
        {
            perror("fail to mkfifoJ");
            exit(1);
        }
    }
#if 0
    //如果open标志位设置为非阻塞,并且以只读的方式打开管道文件
    //open函数和read函数都不会阻塞
    fd = open(FIFONAME, O_RDONLY | O_NONBLOCK);
    if (fd < 0)
    {
        perror("fail to open");
        exit(1);
    }

    while (1)
    {
        char recv[100] = "";
        memset(recv, 0, sizeof(recv));
        read(fd, recv, sizeof(recv));
        printf("read from myfifo buf = [%s]\n", recv);
        sleep(1);
    }
#endif

#if 0
    //如果open标志位设置为非阻塞,并且以只写方式打开管道文件
    //open函数会直接报错
    char send[100] = "Hello I love you";

    fd = open(FIFONAME, O_WRONLY | O_NONBLOCK);
    if (fd < 0)
    {
        perror("fail to open");
        exit(1);
    }
    write(fd, send, strlen(send));
    printf("write to myfifo buf = %s\n", send);

    char recv[100];
    read(fd, recv, sizeof(recv));
    printf("read from myfifo buf = [%s]\n", recv);
#endif
#if 1
    //如果open标志位设置为非阻塞,并且以读写方式打开管道文件
    //这样和阻塞是一样的效果。
    char send[100] = "Hello I love you";

    fd = open(FIFONAME, O_RDWR | O_NONBLOCK);
    if (fd < 0)
    {
        perror("fail to open");
        exit(1);
    }
    write(fd, send, strlen(send));
    printf("write to myfifo buf = %s\n", send);

    char recv[100];
    read(fd, recv, sizeof(recv));
    printf("read from myfifo buf = [%s]\n", recv);
#endif
    return 0;
}

执行截图:

 

 总结:

        命名管道(FIFO)为我们提供了一个在不同进程间进行数据传递的简单而有效的途径。了解如何正确使用FIFO,避免常见的问题,比如权限设定和缓冲区管理。虽然有其复杂性,但是只要掌握了这些要点,我们就能够用好FIFO,为进程间的通信带来便利。

猜你喜欢

转载自blog.csdn.net/crr411422/article/details/131421645