C语言第四章(进程间的通信,管道通信,pipe()函数)

简介: C语言第四章(进程间的通信,管道通信,pipe()函数)

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() 系统调用函数的进程间通信功能。

相关文章
|
15天前
|
消息中间件 Unix Linux
【C语言】进程和线程详解
在现代操作系统中,进程和线程是实现并发执行的两种主要方式。理解它们的区别和各自的应用场景对于编写高效的并发程序至关重要。
45 6
|
1月前
|
存储 Unix Linux
进程间通信方式-----管道通信
【10月更文挑战第29天】管道通信是一种重要的进程间通信机制,它为进程间的数据传输和同步提供了一种简单有效的方法。通过合理地使用管道通信,可以实现不同进程之间的协作,提高系统的整体性能和效率。
|
3月前
|
网络协议 C语言
C语言 网络编程(十三)并发的TCP服务端-以进程完成功能
这段代码实现了一个基于TCP协议的多进程并发服务端和客户端程序。服务端通过创建子进程来处理多个客户端连接,解决了粘包问题,并支持不定长数据传输。客户端则循环发送数据并接收服务端回传的信息,同样处理了粘包问题。程序通过自定义的数据长度前缀确保了数据的完整性和准确性。
|
3月前
|
网络协议 C语言
C语言 网络编程(十一)TCP通信创建流程---服务端
在服务器流程中,新增了绑定IP地址与端口号、建立监听队列及接受连接并创建新文件描述符等步骤。`bind`函数用于绑定IP地址与端口,`listen`函数建立监听队列并设置监听状态,`accept`函数则接受连接请求并创建新的文件描述符用于数据传输。套接字状态包括关闭(CLOSED)、同步发送(SYN-SENT)、同步接收(SYN-RECEIVE)和已建立连接(ESTABLISHED)。示例代码展示了TCP服务端程序如何初始化socket、绑定地址、监听连接请求以及接收和发送数据。
|
3月前
|
C语言
C语言 网络编程(八)并发的UDP服务端 以进程完成功能
这段代码展示了如何使用多进程处理 UDP 客户端和服务端通信。客户端通过发送登录请求与服务端建立连接,并与服务端新建的子进程进行数据交换。服务端则负责接收请求,验证登录信息,并创建子进程处理客户端的具体请求。子进程会创建一个新的套接字与客户端通信,实现数据收发功能。此方案有效利用了多进程的优势,提高了系统的并发处理能力。
|
3月前
|
网络协议 C语言
C语言 网络编程(十二)TCP通信创建-粘包
TCP通信中的“粘包”现象指的是由于协议特性,发送方的数据包被拆分并在接收方按序组装,导致多个数据包粘连或单个数据包分割。为避免粘包,可采用定长数据包或先传送数据长度再传送数据的方式。示例代码展示了通过在发送前添加数据长度信息,并在接收时先读取长度后读取数据的具体实现方法。此方案适用于长度不固定的数据传输场景。
|
3月前
|
C语言
C语言 网络编程(七)UDP通信创建流程
本文档详细介绍了使用 UDP 协议进行通信的过程,包括创建套接字、发送与接收消息等关键步骤。首先,通过 `socket()` 函数创建套接字,并设置相应的参数。接着,使用 `sendto()` 函数向指定地址发送数据。为了绑定地址,需要调用 `bind()` 函数。接收端则通过 `recvfrom()` 函数接收数据并获取发送方的地址信息。文档还提供了完整的代码示例,展示了如何实现 UDP 的发送端和服务端功能。
|
3月前
|
网络协议 C语言
C语言 网络编程(十)TCP通信创建流程---客户端
在TCP通信中,客户端需通过一系列步骤与服务器建立连接并进行数据传输。首先使用 `socket()` 函数创建一个流式套接字,然后通过 `connect()` 函数连接服务器。连接成功后,可以使用 `send()` 和 `recv()` 函数进行数据发送和接收。最后展示了一个完整的客户端示例代码,实现了与服务器的通信过程。
|
3月前
|
Linux C语言
C语言 多进程编程(七)信号量
本文档详细介绍了进程间通信中的信号量机制。首先解释了资源竞争、临界资源和临界区的概念,并重点阐述了信号量如何解决这些问题。信号量作为一种协调共享资源访问的机制,包括互斥和同步两方面。文档还详细描述了无名信号量的初始化、等待、释放及销毁等操作,并提供了相应的 C 语言示例代码。此外,还介绍了如何创建信号量集合、初始化信号量以及信号量的操作方法。最后,通过实际示例展示了信号量在进程互斥和同步中的应用,包括如何使用信号量避免资源竞争,并实现了父子进程间的同步输出。附带的 `sem.h` 和 `sem.c` 文件提供了信号量操作的具体实现。
|
3月前
|
编译器
【收藏】内核级利用通用Hook函数方法检测进程
【收藏】内核级利用通用Hook函数方法检测进程