有名管道介绍及使用
    匿名管道,由于没有名字,只能用于有亲缘关系的进程间通信(通过进程fork 拷贝内核区的文件描述符这个机制来实现),为了克服这个缺点提出了有名管道(FIFO 先入先出)。
    有名管道(FIFO 也是由环形队列实现)不同于匿名管道之处在于它提供路径名与这个管道关联以FIFO的文件形式存在于文件系统中,并且其打开方式与打开一个普通文件的方式是一样的,这样即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径就可以彼此通过FIFO互相通信,因此通过FIFO不相关的进程也能交换数据。
    一旦打开了FIFO,就能在它上面使用与操作匿名管道和其他文件的系统调用一样的I/O系统调用(如read、write、close),与管道一样,FIFO也有一个写入端和读取端,并且从管道中读取数据的顺序与写入的顺序是一样的(先入先出)。

    FIFO与pipe(匿名管道)的不同点:
    1 FIFO在文件中作为一个特殊文件存在,但是FIFO中的内容却存放在内存中(内核中的一个缓存区中)
    2 当使用FIFO的进程退出后,FIFO文件将继续保存在文件系统中以便以后使用
    3 FIFO有名字,不相关的进程可以通过打开有名管道进行通信

    有名管道的使用
    通过命令创建有名管道 mkfifo 管道名
    通过函数创建有名管道
    #include
    #include
    int mkfifo(const char pathname,mode_t mode);
    一旦使用mkfifo创建了一个FIFO就可以用open,常见的文件IO操作含糊书(close read wirte unlink删除)都可以用于FIFO文件
    FIFO严格遵顼先入先出,对管道及FIFO的读总是从开始处返回数据(单向队列队首数据),对它们的写则把数据添加到末尾(向单向队列队尾添加数据),FIFO文件不支持诸如lseek()等文件定位操作。


    一个为只读(写)而打开(open)的管道的进程会阻塞(阻塞在open),直到另一个进程以包含写(读)的方式打开相同的管道,才不会继续阻塞而是继续向下运行。
    1 当指向管道写端的fd都close了,此时有进程从管道读端读数据,若此时管道中有剩余数据则读取剩余数据,直到剩余数据被读完后,再次read返回0(读到文件末尾read是返回0的 读出现异常是返回-1的)。

    2 有指向管道写端的文件描述符没有关闭(管道写端的引用计数大于0)并且没有进程向管道写端写数据,此时将管道内数据读空后再继续读是出现read阻塞,直到有进程写了一些数据,read读到返回读到的字节数。

    3 所有的指向管道读端的文件描述符 都关闭了(管道读端的引用计数为0),此时有进程向管道中写数据,那么该进程会收到一个信号SIGPIPE,通常这个信号会导致进程异常终止。

    4 有指向管道读端的文件描述符没有关闭(管道读端的引用计数大于0)并且没有进程从管道读端读数据,此时*有进程向管道中写数据
    ,在管道写满后,再次调用写write会导致写进程阻塞,直到管道中有空位write才不会阻塞并返回写入的字节数。

    1. //为了实验 还需要创建两个进程 我们通过两个.c文件来实现
    2. //这个.c为写fifo进程
    3. #include <stdio.h>
    4. #include <sys/types.h>
    5. #include <sys/stat.h>
    6. #include <stdlib.h>
    7. #include <unistd.h> //acess
    8. #include <fcntl.h> //open
    9. #include <string.h> //strlen
    10. int main()
    11. {
    12. //判断文件是否存在
    13. int ret = access("f1", F_OK); //判断叫这个名字的文件是否存在
    14. if (ret == 0) //return 0 表示管道以存在
    15. {
    16. ;
    17. }
    18. else
    19. {
    20. int ret = mkfifo("f1", 0664);
    21. if (ret == -1)
    22. {
    23. perror("mkfifo:");
    24. return -1;
    25. }
    26. }
    27. //打开管道
    28. // 这个进程只向管道里写数据 所以以只写的方式打开管道
    29. int fd = open("f1", O_WRONLY); //以只写的形式打开 若没有其他线程以包含读的方式打开文件 这个open会一直阻塞在这!!!!
    30. if (fd == -1)
    31. {
    32. perror("open:");
    33. return -1;
    34. }
    35. //写数据
    36. for (int i = 0; i < 100; i++)
    37. {
    38. char buf[1024];
    39. sprintf(buf, "hello %d\n", i); //向buf中写数据
    40. printf("write data:%s\n", buf); //打印当前buf中的字符数据
    41. write(fd, buf, stelen(buf)); //向管道中写数据
    42. sleep(1);
    43. }
    44. //当所有读端都关闭 此时有写线程向管道中写数据会收到SIGPIPE信号导致进程直接异常退出
    45. close(fd); //关闭管道
    46. return 0;
    47. }
    48. //为了实验 还需要创建两个进程 我们通过两个.c文件来实现
    49. //这个.c为读fifo进程
    50. #include <stdio.h>
    51. #include <sys/types.h>
    52. #include <sys/stat.h>
    53. #include <stdlib.h>
    54. #include <unistd.h> //acess
    55. #include <fcntl.h> //open
    56. int main()
    57. {
    58. //判断文件是否存在
    59. int ret = access("f1", F_OK); //判断叫这个名字的文件是否存在
    60. if (ret == 0) //return 0 表示管道以存在
    61. {
    62. ;
    63. }
    64. else
    65. {
    66. int ret = mkfifo("f1", 0664);
    67. if (ret == -1)
    68. {
    69. perror("mkfifo:");
    70. return -1;
    71. }
    72. }
    73. //打开管道
    74. // 这个进程只读管道里的数据 所以以只读的方式打开管道
    75. int fd = open("f1", O_RDONLY); //以只读的形式打开 若没有其他线程以包含写的方式打开文件 这个open会一直阻塞在这!!!!
    76. if (fd == -1)
    77. {
    78. perror("open:");
    79. return -1;
    80. }
    81. //读数据
    82. while (1)
    83. {
    84. char buf[1024] = {0};
    85. int len = read(fd, buf, sizeof(buf));
    86. //当写端关闭 并且管道中无数据 返回len=0
    87. if (len == 0)
    88. break;
    89. printf("recv buf %s\n", buf);
    90. memset(buf, 0, sizeof(buf));
    91. }
    92. close(fd); //关闭管道
    93. return 0;
    94. }



    有名管道实现简单版聊天功能
    A进程先给B发,B收到后再给A发,A收到后。。。就这么交替地进行
    一个管道只能单向通信,要实现相互的收发需要用两个管道实现。
    2 Linux多进程开发4 有名管道介绍及使用 - 图1
    进程A 1 以只写方式打开fifo1 以只读的方式打开fifo2 2循环读写数据
    while(1){ 获取键盘录入的数据不用scanf(遇到换行就结束)而使用fgets; 并将录入的数据写管道1; 读管道2;
    }
    进程B 1 以只读方式打开fifo1 以只写的方式打开fifo2 2循环读写数据
    while(1){ 读管道1;
    获取键盘录入的数据不用scanf(遇到换行就结束)而使用fgets; 并将录入的数据写管道2; }

    要实现有名管道的同时读写 需要fork出子进程 父进程open fifo1的读端 进行读
    子进程 open fifo2的写端 进行写
    A

    1. #include <stdio.h>
    2. #include <sys/types.h>
    3. #include <sys/stat.h>
    4. #include <stdlib.h>
    5. #include <unistd.h> //acess
    6. #include <fcntl.h> //open
    7. #include <string.h> //open
    8. int main()
    9. {
    10. //判断有名管道文件是否存在
    11. //存在则不再创建 不存在则创建
    12. int ret = access("fifo1", F_OK); //判断叫这个名字的文件是否存在
    13. if (ret == 0) //return 0 表示管道以存在
    14. {
    15. ;
    16. }
    17. else //-1
    18. {
    19. int ret = mkfifo("f1", 0664);
    20. if (ret == -1)
    21. {
    22. perror("mkfifo:");
    23. return -1;
    24. }
    25. }
    26. int ret = access("fifo2", F_OK); //判断叫这个名字的文件是否存在
    27. if (ret == 0) //return 0 表示管道以存在
    28. {
    29. ;
    30. }
    31. else //-1
    32. {
    33. int ret = mkfifo("f1", 0664);
    34. if (ret == -1)
    35. {
    36. perror("mkfifo:");
    37. return -1;
    38. }
    39. }
    40. // 以只写的方式打开管道fifo1
    41. int fdw = open("fifo1", O_WRONLY);
    42. if (fdw == -1)
    43. {
    44. perror("open FIFO1 WRITE:");
    45. return -1;
    46. }
    47. //以只读的方式打开管道fifo2
    48. int fdr = open("fifo2", O_RDONLY);
    49. if (fdr == -1)
    50. {
    51. perror("open FIFO2 READ:");
    52. return -1;
    53. }
    54. char buf[128];
    55. //循环写读数据
    56. while (1)
    57. {
    58. //写数据 写fifo1
    59. memset(buf, 0, sizeof(buf));
    60. fgets(buf, sizeof(buf), stdin); //从屏幕的stdin标准输入(标准输入也有缓冲区) 获取128个字节并装到buf里
    61. //将从标准输入得到的数据写到fifo里
    62. int ret = write(fdw, buf, strlen(buf));
    63. if (ret == -1)
    64. {
    65. perror("write:");
    66. return -1;
    67. }
    68. //读数据 读fifo2
    69. memset(buf, 0, sizeof(buf));
    70. int len = read(fdr, buf, strlen(buf));
    71. if (len <= 0)
    72. {
    73. //读出错 -1 读到末尾(或fifo2的写端已经关闭了 并且管道内容被读完了)返回0
    74. perror("read:");
    75. return -1;
    76. }
    77. printf("buf:%s\n", buf);
    78. }
    79. close(fdw);
    80. close(fdr);
    81. return 0;
    82. }


    B

    1. #include <stdio.h>
    2. #include <sys/types.h>
    3. #include <sys/stat.h>
    4. #include <stdlib.h>
    5. #include <unistd.h> //acess
    6. #include <fcntl.h> //open
    7. #include <string.h> //open
    8. int main()
    9. {
    10. //判断有名管道文件是否存在
    11. //存在则不再创建 不存在则创建
    12. int ret = access("fifo1", F_OK); //判断叫这个名字的文件是否存在
    13. if (ret == 0) //return 0 表示管道以存在
    14. {
    15. ;
    16. }
    17. else //-1
    18. {
    19. int ret = mkfifo("f1", 0664);
    20. if (ret == -1)
    21. {
    22. perror("mkfifo:");
    23. return -1;
    24. }
    25. }
    26. int ret = access("fifo2", F_OK); //判断叫这个名字的文件是否存在
    27. if (ret == 0) //return 0 表示管道以存在
    28. {
    29. ;
    30. }
    31. else //-1
    32. {
    33. int ret = mkfifo("f1", 0664);
    34. if (ret == -1)
    35. {
    36. perror("mkfifo:");
    37. return -1;
    38. }
    39. }
    40. // 以只读的方式打开管道fifo1
    41. int fdr = open("fifo1", O_RDONLY);
    42. if (fdr == -1)
    43. {
    44. perror("open FIFO1 READ:");
    45. return -1;
    46. }
    47. //以只写的方式打开管道fifo2
    48. int fdw = open("fifo1", O_WRONLY);
    49. if (fdw == -1)
    50. {
    51. perror("open FIFO2 WRITE:");
    52. return -1;
    53. }
    54. char buf[128];
    55. //循环读写数据
    56. while (1)
    57. {
    58. //读数据 读fifo1
    59. memset(buf, 0, sizeof(buf));
    60. int len = read(fdr, buf, strlen(buf));
    61. if (len <= 0)
    62. {
    63. //读出错 -1 读到末尾(或fifo2的写端已经关闭了 并且管道内容被读完了)返回0
    64. perror("read:");
    65. return -1;
    66. }
    67. printf("buf:%s\n", buf);
    68. //写数据 写fifo2
    69. memset(buf, 0, sizeof(buf));
    70. fgets(buf, sizeof(buf), stdin); //从屏幕的stdin标准输入(标准输入也有缓冲区) 获取128个字节并装到buf里
    71. //将从标准输入得到的数据写到fifo里
    72. int ret = write(fdw, buf, strlen(buf));
    73. if (ret == -1)
    74. {
    75. perror("write:");
    76. return -1;
    77. }
    78. }
    79. close(fdw);
    80. close(fdr);
    81. return 0;
    82. }