CC语言第四章(进程间的通信,管道通信,pipe()函数)
简介
本文讲解的是C语言的进程之间的通信,这里讲解的是管道通信,和相关的函数pipe().
管道
管道通信是 Unix/Linux 系统中比较常见的进程间通信方式之一。其基本原理是,创建一个临时文件(即管道),然后将一个进程的标准输出(或标准错误)重定向到管道写入端口,这样子进程就可以读取运行另一个可执行文件的程序的输出信息了。
在 C 语言中,使用 pipe() 函数来创建管道,其基本格式如下:
#include <unistd.h> int pipe(int filedes[2]); // filedes:用于存储读/写两个文件描述符
其中,filedes 参数需要提供两个长度为2的数组,分别代表文件描述符的读/写端。
发送进程序可以使用 write() 函数将数据写入管道,而接收进程则可使用 read() 函数从管道读取数据:
// 发送进程 char str[] = "hello world"; write(writeFd, str, sizeof(str)); // 接收进程 char buffer[256]; read(readFd, buffer, 255); printf("%s\n", buffer);
示例代码中,str 为需要发送的字符串,sizeof(str) 表示字符串大小。write() 函数将字符串数据写入 writeFd 中,读进程通过 read() 从 readFd 中读取数据,并使用 printf() 输出到屏幕上。
一种简单的利用管道进行进程间通信的方法是,创建一个子进程。子进程调用 fork() 函数,将自己的标准输出重定向到管道读端口,然后调用 exec() 函数来运行另一个可执行文件。
父进程在 fork() 之前创建一个管道并将其写入端口发给子进程。父进程需要等待子进程结束并通过管道读出端口获取其输出。
实现部分代码可以参考下面示例:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #define BUFFER_SIZE 1024 int main() { int pipe_fd[2]; pid_t pid; char* buffer = malloc(sizeof(char) * BUFFER_SIZE); if (buffer == NULL) { printf("动态内存申请失败!\n"); return -1; } // 创建管道 if (pipe(pipe_fd) < 0) { perror("无法创建管道!"); exit(1); } // 创建子进程,使得父进程和子进程都能共享管道。 pid = fork(); if (pid < 0) { perror("无法创建子进程!"); exit(1); } else if (pid == 0) { // 子进程 printf("===> 这里是子进程...\n"); close(pipe_fd[0]); // 关闭读取端,只保留写入端 if(write(pipe_fd[1], "Hello, world!", strlen("Hello, world!")) >= strlen("Hello, world!")){ printf("===> 子进程已经写完了!\n"); }else{ printf("===> 子进程写管道时发生了错误 \n"); } } else { // 父进程 printf("===> 这里是父进程...\n"); close(pipe_fd[1]); // 关闭写入端,只保留读取端 memset(buffer, 0, BUFFER_SIZE); // 先清空缓冲区内容 if(read(pipe_fd[0], buffer, BUFFER_SIZE) > 0){ printf("===> 父进程读取到消息了: %s\n", buffer); }else{ printf("===> 无法从管道中读取数据\n"); } wait(NULL); // 等待子进程结束 } free(buffer); exit(0); }
在该示例代码中,首先创建了一个长度为 BUFFER_SIZE 的字符缓冲区;然后使用 pipe() 函数创建了一个长度为 2 的整型数组,存储了。
运行结果:
这个程序的运行结果可能如下:
===> 这里是父进程... ===> 父进程读取到消息了: Hello, world! ===> 子进程已经写完了!
可以看到,程序首先输出 “这里是父进程…”,然后父进程通过管道读取到子进程输出的 “Hello, world!” 消息,并输出 “父进程读取到消息了: Hello, world!”。最后输出 “子进程已经写完了!”。
需要注意的是,在使用 read() 函数时,它可能会阻塞并一直等待直到有数据可用,这意味着如果没有数据可供读取,则程序可能会挂起或死锁。为了避免这种情况,可以使用非阻塞模式进行读取(在本例中演示)或者使用 select() 函数来确定何时可读入数据,从而避免阻塞。
运行结果分析:
在该程序中,首先创建一个长度为 BUFFER_SIZE 的字符缓冲区。接着使用 pipe() 函数创建了一个长度为 2 的整型数组,存储了管道的读取端和写入端口。
在父进程和子进程之间,父进程调用 fork() 函数创建了一个子进程。该程序基于尽可能少的关系来实现进程间通信。 父进程关闭管道的写入端口,只保留读取端,以便从子进程中读取数据。 子进程关闭管道的读取端口,只保留写入端口,让该进程可以向管道中写入数据。
在子进程内部,它打印了一条消息 “这里是子进程…” 然后使用 write() 函数将 “Hello, world!” 字符串写入了管道的写入端口,然后结束了其自身。
在父进程内部,它首先显示 “这里是父进程…” 消息,紧接着就开始等待从管道中读取数据。因此,父进程通过 read() 函数从管道的读取端口读取数据,并将其存储到预先定义的缓存中。
最后,当从管道中读取数据并且子进程写完数据,程序输出 “父进程读取到消息了: Hello, world!” 与 “===> 子进程已经写完了!” 两个消息。
总结来看,该程序成功演示了使用管道在父进程与子进程之间进行通信的基本过程,父进程从读取管道中得到数据(“Hello, world!” ),说明了管道确实可以在两个进程之间传递数据。
pipe函数
pipe()函数是一个系统调用函数,用于创建管道(Pipe)和输入输出流。它的函数原型定义在 unistd.h 头文件中。
#include <unistd.h> int pipe(int filedes[2]);
其中,filedes 是一个描述符数组,同时包含了一个读描述符和一个写描述符。这两个描述符可以单向地进行数据传输,即一端写入数据,另一端就能读入该数据。
该函数成功时返回 0 ,失败时返回 -1 ,并设置相应的 errno 错误码。
下面详解一下各参数:
- filedes :传递一个长度为 2 的整数数组作为参数。当创建管道成功时,该数组会被填充上两个打开文件描述符: filedes[0] 表示管道的读取端,而 filedes[1] 则表示管道的写入端。
下面代码实现了基本的利用 pipe() 函数实现进程间通信的操作:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #define BUF_SIZE 100 int main(void) { pid_t pid; int fd[2]; char buff[BUF_SIZE]; if (pipe(fd) == -1) { // 创建管道失败 perror("pipe error"); return -1; } pid = fork(); // fork() 系统调用 if (pid > 0) { // 父进程 close(fd[0]); // 关闭读端 const char* str = "Hello, child process!\n"; if (write(fd[1], str, strlen(str)) != strlen(str)) perror("write error"); close(fd[1]); } else if (pid == 0) { // 子进程 close(fd[1]); // 关闭写端 int len = read(fd[0], buff, BUF_SIZE); if (len < 0) perror("read error"); printf("recv msg from parent: %s", buff); close(fd[0]); } else { perror("fork error"); return -1; } return 0; }
在上述代码中,首先使用 pipe() 系统调用函数创建一个管道。然后通过 fork() 函数创建一个子进程。在父进程中,通过 write() 方法向管道里面的写入端发送数据;而在子进程中,则通过 read() 从管道里面的读取端获取数据。
总之,pipe() 系统调用函数可以创建管道,并提供了打开文件描述符,使得某个进程的输出可以通过一条管道与另一个进程的输入端连接起来,实现了两个进程之间通信的目的。
运行结果:
上述代码运行结果如下:
recv msg from parent: Hello, child process!
可以看到,父进程向子进程发送了一条消息 “Hello, child process!\n”,而子进程通过管道接收到该消息并输出。这表明,使用 pipe() 函数实现的进程间通信是有效的。
分析运行结果:
在上述代码中,创建了一个包含两个端点的管道 fd ,然后通过 fork() 函数创建了两个子进程:父进程和子进程。在父进程中,先调用 write() 方法将消息发送到管道写入端,发送完成后再关闭相应的文件描述符。而在子进程中,则先关闭写入端,接着通过 read() 方法从管道读取数据,并输出这条信息。
因此,当程序运行时,父进程首先向子进程通过管道发送了一条消息,并关闭文件描述符;而子进程中对读文件描述符进行监听,等待数据传输,从管道的读取端接收到数据之后,再将其输出。
综上所述,该程序实现了基于 pipe() 系统调用函数的进程间通信功能。