进程间通信-管道

管道

管道是针对于本地计算机的两个进程之间的通信而设计的通信方法,管道建立后,实际上是获得两个文件描述符:一个用与读取而另一个用于写入。任何从管道写入端写入的数据,可以从管道读取端读出。

管道具有以下特点:

  • 管道是一种半双工通信机制,即数据只能在一个方向上流动,不能同时进行双向数据传输。一个进程可以将数据写入管道,另一个进程则可以从管道读取数据,但不能同时进行双向通信。需要双向通信时,要建立起两个管道。
  • 管道是一种特殊的文件系统,进程通过文件描述符进行读写操作,实现数据传输。

管道分类:

  • 匿名管道
  • 命名管道

匿名管道

匿名管道又称为无名管道,用于父子进程之间进行通信。匿名管道没有在文件系统中创建文件,而是通过内存进行数据传输。

pipe() 函数用于创建一个管道,该函数定义如下:

#include <unistd.h>

int pipe(int [2]);

参数说明

  • pipefd:接受一个整型数组作为参数,数组包含两个文件描述符,pipefd[0] 为管道的读取端,pipefd[1] 为管道的写入端。

返回值

  • 如果函数执行成功,返回值为0。
  • 如果函数执行失败,返回值为-1,可以通过 errno 变量来获取具体的错误信息。

原理

在单个进程中使用管道几乎没有任何意义。通常情况下,父进程先调用 pipe() 函数创建一条管道,再通过 fork() 函数创建子进程。子进程在创建时,会拷贝父进程的文件描述符表。之后父进程关闭 读(写)端,子进程关闭 写(读)端,从而建立一条父子进程间通信的通道。流程如下图所示:

示例

 1 #include<stdio.h>
 2 #include<unistd.h>
 3 #include<stdlib.h>
 4 #include<string.h>
 5 #include<signal.h>
 6 #include<errno.h>
 7 
 8 void childWrite(int fds[2])
 9 {
10     close(fds[0]);
11     char buf[1024];
12     int i = 0;
13     while(1)
14     {
15         sprintf(buf, "Child Data%d", i++);
16         int nRet = write(fds[1], buf, strlen(buf));
17         if(nRet == -1)
18         {
19             perror("write");
20             exit(1);
21         }
22 
23         printf("child write %d bytes\n", nRet);
24         sleep(1);
25     }
26     close(fds[1]);
27 }
28 
29 void parentRead(int fds[2])
30 {
31     close(fds[1]);
32     char buf[1024];
33     while(1)
34     {
35         int nRet = read(fds[0], buf, sizeof(buf) - 1);
36         if(nRet == -1)
37         {
38             perror("read");
39             exit(1);
40         }
41         buf[nRet] = '\0';
42         printf("parent read : %s\n", buf);
43         sleep(1);
44     }
45 }
46 
47 int main(int argc, char** argv)
48 {
49 
50     int fds[2];
51     if(pipe(fds) == -1)
52     {
53         perror("pipe");
54         return -1;
55     }
56 
57     pid_t pid = fork();
58     if(pid == -1)
59     {
60         perror("fork");
61         return -1;
62     }
63     else if(pid == 0)
64         childWrite(fds);
65     else
66         parentRead(fds);
67 
68     return 0;
69 }

输出:

child write 11 bytes
parent read : Child Data0
child write 11 bytes
parent read : Child Data1
child write 11 bytes
parent read : Child Data2
...

获取管道最大容量

 1 void childWrite(int fds[2])
 2 {
 3     close(fds[0]);
 4     char buf[1024];
 5     int i = 0;
 6     while(1)
 7     {
 8         write(fds[1], "1", 1);
 9         printf("write count : %d\n",++i);
10     }
11     close(fds[1]);
12 }
13 
14 void parentRead(int fds[2])
15 {
16     close(fds[1]);
17     while(1);
18 }

上述代码中,写端每次向管道中写入1个字节,之后输出当前写入的个数。读端不做任何处理,输出如下:

write count : 1
...
write count : 65536
                        //阻塞

可以发现,管道的默认大小为64KB,可以通过 ulimit -a 命令查看:

$ ulimit -a
...
pipe size            (512 bytes, -p) 8
...

总结

  • 匿名管道只能用于父子进程或者兄弟进程之间。
  • 当前读端关闭,写端继续写入,管道会破裂,将产生 SIGPIPE 信号,该信号会导致进程退出。同时, write() 函数返回-1,errno 被设置为 EPIPE。
  • 当前写端关闭,读端会将管道中数据读取完,再次读取时,read函数返回0,表示写端已经关闭。
  • 读写端都未关闭,当管道写满时,再次写入数据,write() 函数会被阻塞。
  • 读写端都未关闭,当管道为空时,再次读取数据,read() 函数会被阻塞。

命名管道

命名管道是一种特殊类型的文件,被称为管道文件。与普通文件不同,管道文件不会再磁盘上分配数据块,是通过内存进行数据传输。对管道的操作与对普通文件的操作类似,都是通过文件描述符进行访问。进程通过向管道中写入数据或从管道中读取数据来进行通信。

命名管道的特点:

  • 可以在不相关的进程之间进行通信,只要它们可以访问同一个管道文件。
  • 数据传输是单向的,一个进程向管道写入数据,另一个进程从管道读取数据。
  • 数据传输是基于字节流,没有消息边界。
  • 命名管道会一直保持存在,不受进程退出影响,直到被显式地删除。

对于管道文件的创建,可以使用 mkfifo 命令或 mkfifo() 函数来创建命名管道,mkfifo 命令格式如下:

mkfifo [选项] <管道名称>
  • 选项:通常情况下不需要任何选项
  • 管道名称:指定要创建的命名管道的路径和名称。

mkfifo() 函数定义如下:

#include <sys/stat.h>

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

参数说明

  • pathname:要创建的命名管道文件的路径名。
  • mode:用于设置文件权限的参数,类似于 chmod() 函数中所使用的参数。

返回值

如果函数执行成功,返回值为0。如果函数执行失败,返回值为-1,并且可以通过 errno 变量来获取具体的错误信息。常见错误值如下:

  • EEXIST:创建的文件已经存在。
  • ENAMETOOLONG:路径名太长。
  • EACCES:权限不足,无法创建命名管道。

通过 mkfifo 命令或 mkfifo() 函数创建管道,管道文件信息如下:

$ stat myFifo 
  文件:myFifo
  大小:0             块:0          IO 块:4096   先进先出
设备:805h/2053d    Inode:1192546     硬链接:1
权限:(0664/prw-rw-r--)  Uid:( 1000/ test)   Gid:( 1000/ test)
...

$ ls -l myFifo 
prw-rw-r-- 1 test test 0 8月  21 14:17 myFifo

通过open函数打开管道文件时,可能会发送阻塞行为,只有当两边的管道都打开的时,open函数才会返回。例如:

 1 #include<stdio.h>
 2 #include<fcntl.h>
 3 #include<sys/stat.h>
 4 #include<unistd.h>
 5 #include<errno.h>
 6 
 7 int main(int argc, char** argv)
 8 {
 9     int nRet = mkfifo("./myfifo", 0666);
10     if(nRet == -1)
11     {
12         if(errno != EEXIST)
13         {
14             perror("mkfifo");
15             return -1;
16         }
17     }
18     
19     printf("create fifo success!\n");
20     
21     int fd = open("./myfifo", O_WRONLY);
22     if(fd == -1)
23     {
24         perror("open");
25         return -1;
26     }
27     
28     printf("open fd success!\n");
29     return 0;
30 }

输出:

create fifo success!
            //阻塞

从输出结果来看,函数阻塞在 open() 函数位置。

示例:

fifowrite.c

 1 #include<stdio.h>
 2 #include<fcntl.h>
 3 #include<sys/stat.h>
 4 #include<unistd.h>
 5 #include<errno.h>
 6 #include<string.h>
 7 #include<signal.h>
 8 
 9 void sigHandler(int sig)
10 {
11     printf("signal : %d\n", sig);
12 }
13 
14 int main(int argc, char** argv)
15 {
16     signal(SIGPIPE, sigHandler);
17 
18     if(mkfifo("./myfifo", 0666) == -1)
19     {
20         if(errno != EEXIST)
21         {
22             perror("mkfifo");
23             return -1;
24         }
25     }    
26 
27     int fd = open("./myfifo", O_WRONLY);
28     char buf[32] = {0};
29     int i = 0;
30     while(1)
31     {
32         sprintf(buf, "Data%d", ++i);
33         int nRet = write(fd, buf, strlen(buf));
34         if(nRet == -1)
35         {
36             perror("write");
37             return -1;
38         }
39         sleep(1);
40     }
41 
42     return 0;
43 }

fiforead.c

 1 #include<stdio.h>
 2 #include<fcntl.h>
 3 #include<sys/stat.h>
 4 #include<unistd.h>
 5 #include<errno.h>
 6 #include<string.h>
 7 
 8 int main(int argc, char** argv)
 9 {
10     if(mkfifo("./myfifo", 0666) == -1)
11     {
12         if(errno != EEXIST)
13         {
14             perror("mkfifo");
15             return -1;
16         }
17     }    
18 
19     int fd = open("./myfifo", O_RDONLY);
20     char buf[32] = {0};
21     while(1)
22     {
23         int nRet = read(fd, buf, sizeof(buf) - 1);
24         if(nRet == -1)
25         {
26             perror("read");
27             return 0;
28         }
29         else if(nRet == 0)
30         {
31             printf("pipe has closed!\n");
32             return 0;
33         }
34         printf("read : %s\n", buf);
35         sleep(1);
36     }
37 
38     return 0;
39 }
posted @ 2024-03-01 10:39  西兰花战士  阅读(62)  评论(0编辑  收藏  举报