【Linux】进程间通信-管道

发布于:2025-05-09 ⋅ 阅读:(14) ⋅ 点赞:(0)

目录

一、进程间通信(IPC机制:interprocess communicate)

二、管道

三、无名管道

无名管道的使用步骤

练习1

无名管道的特性

练习2

四、有名管道

有名管道使用步骤

练习1

练习2

memset函数介绍


一、进程间通信(IPC机制:interprocess communicate)

同一主机进程间通信:

1.无名管道

2.有名管道

3.信号

4.共享内存

5.消息队列

6.信号灯

不同主机进程间通信:7.网络套接字


二、管道

无名管道:用于同一主机下,具有亲缘关系的父子进程间通信。

有名管道:用于同一主机下,具有任意关系的进程间通信。

三、无名管道

无名管道的使用步骤

1.创建一个无名管道并打开

#include <unistd.h>
int pipe(int pipefd[2]);
/*
参数:pipefd[0]->管道的读端
     pipefd[1]->管道的写端
返回值:
成功:0
失败:-1
*/

2.读管道read

3.写管道write

4.关闭管道close

练习1

父进程从终端读数据,子进程打印父进程读到的数据,读到".quit",两进程退出

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
    // 定义一个数组来存储管道的两个文件描述符
    // pipefd[0] 用于读操作,pipefd[1] 用于写操作
    int pipefd[2];
    // 调用 pipe 函数创建一个管道
    // 若创建成功,pipefd 数组会被填充相应的文件描述符
    // 若失败,ret 会为 -1
    int ret = pipe(pipefd);
    if (ret < 0)// 检查管道是否创建成功
    {
        perror("fail to pipe\n");
        return -1;
    }
    pid_t pid = fork();
    if (pid > 0)
    {
        
        close(pipefd[0]);// 当前为父进程,关闭管道的读端,因为父进程只负责写数据
        char buff[1024] = {0};
        while (1)
        {
            // 从标准输入读取一行内容到 buff 数组
            fgets(buff, sizeof(buff), stdin);
            // 将 buff 数组中的内容写入管道的写端
            // 写入的字节数为 buff 字符串的长度
            write(pipefd[1], buff, strlen(buff));
            // 检查用户输入的是否为 ".quit"
            // 若输入 ".quit",则跳出循环
            if (0 == strcmp(buff, ".quit\n"))
            {
                break;
            }
        }
        wait(NULL);// 父进程等待子进程结束
    }
    else if (0 == pid)
    {
        close(pipefd[1]);// 当前为子进程,关闭管道的写端,因为子进程只负责读数据
        char buff[1024] = {0};
        ssize_t n;
        while (1)
        {
            // 将 buff 数组清零,以便存储新的读取内容
            memset(buff, 0, sizeof(buff));
            // 从管道的读端读取数据到 buff 数组
            // n 存储实际读取的字节数
            n = read(pipefd[0], buff, sizeof(buff));
            // 检查读取的字节数
            // 若 n 小于等于 0,说明管道关闭或出现错误,跳出循环
            if (n <= 0)
            {
                // 管道关闭或出错,跳出循环
                break;
            }
            if (0 == strcmp(buff, ".quit\n"))
            {
                break;
            }
            printf("buff = %s\n", buff);
        }
    }
    else
    {
        perror("fail fork");
        return -1;
    }
    // 关闭管道的读端和写端,释放资源
    close(pipefd[0]);
    close(pipefd[1]);
    return 0;
}    

无名管道的特性

1.默认64K大小

测试代码

#include<stdio.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
    int pipefd[2];
    int ret = pipe(pipefd);
    if (ret < 0)
    {
        perror("fail to pipe!\n");
        return -1;
    }
    // pipefd[0]->read
    // pipefd[1]->write
    int cnt = 0;
    while (1)
    {
        ssize_t size = write(pipefd[1], "a", 1);
        if (size < 0)
        {
            perror("fail to write!\n");
            break;
        }
        cnt++;
        printf("cnt = %d\n", cnt);
    }
    close(pipefd[0]);
    close(pipefd[1]);
    return 0;
}

测试结果:

65536 / 1024 = 64K

2.管道存储数据时,按照FIFO的方式存储

3.写阻塞:管道读写端都存在时,向管道中写入数据,当管道满时,发生写阻塞

读阻塞:管道读写端都存在时,向管道中读数据,如果管道中有数据,read返回实际读到的字节数;如果管道中无数据,read发生读阻塞

读到0返回:管道的写端关闭,只保留读端,从管道读数据,若有数据,则读到数据;若无数据,则read返回0,不阻塞

管道破裂:管道的读端关闭,只保留写端,向管道中写入数据,发生管道破裂(异常)

练习2

父进程将"1.txt"文件通过管道发送给子进程"2.txt"

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

int main(int argc, char const *argv[])
{
    int pipefd[2];
    int ret = pipe(pipefd);
    if (ret < 0)
    {
        perror("fail pipe!\n");
        return -1;
    }
    pid_t pid = fork();
    if (pid > 0)
    {
        close(pipefd[0]);
        int fd = open("1.txt", O_RDONLY);// 打开文件 "1.txt" 以只读模式,返回文件描述符
        if (fd < 0)
        {
            perror("fail open!\n");
            return -1;
        }
        char buff[1024] = {0};
        while (1)
        {
            size_t size = read(fd, buff, sizeof(buff));
            if (size <= 0)
            {               
                break;// 如果读取的字节数小于等于 0,表示文件读取完毕,退出循环
            }            
            write(pipefd[1], buff, size);// 将缓冲区中的数据写入管道的写端,写的字节数等于读取的字节数
        }     
        close(pipefd[1]);       
        close(fd);
        wait(NULL);
    }
    else if (0 == pid)
    {
        close(pipefd[1]);
        // 打开文件 "2.txt" 以只写模式,如果文件不存在则创建,如果文件已存在则截断,权限为 0664
        int fd = open("2.txt", O_WRONLY | O_CREAT | O_TRUNC, 0664);
        if (fd < 0)
        {
            perror("fail open\n");
            return -1;
        }
        char buff[1024] = {0};
        while (1)
        {
            // 从管道的读端读取数据到缓冲区,返回实际读取的字节数
            size_t size = read(pipefd[0], buff, sizeof(buff));
            if (size <= 0)
            {
                // 如果读取的字节数小于等于 0,表示管道中没有数据了,退出循环
                break;
            }
            // 将缓冲区中的数据写入文件
            write(fd, buff, size);
        }
        close(pipefd[0]);
        close(fd);
    }
    else
    {
        perror("fail fork!\n");
        return -1;
    }
    return 0;
}

四、有名管道

有名管道在同一主机下,用于任意进程间通信

有名管道使用步骤

1.创建管道文件

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
/*
参数:
pathname:文件描述符
mode:读写执行权限(一般为0664)
*/

2.打开管道文件open

3.读写管道文件read

4.关闭管道文件close

5.删除管道文件

#include <stdio.h>
int remove(const char *pathname);

父子进程间使用有名管道通信

#include"head.h"
int main(int argc, char const *argv[])
{
    mkfifo("./myfifo", 0664);
    pid_t pid = fork();
    if (pid > 0)
    {
        int fd = open("./myfifo", O_WRONLY);
        if (fd < 0)
        {
            perror("fail open");
            return -1;
        }
        write(fd, "hello word", strlen("hello word"));
        close(fd);
        wait(NULL);
    }
    else if (0 == pid)
    {
        int fd = open("./myfifo", O_RDONLY);
        if(fd < 0)
        {
            perror("fail open");
            return -1;
        }
        char buff[512] = {0};
        read(fd, buff, sizeof(buff));
        printf("buff = %s\n", buff);
        close(fd);
    }
    else
    {
        perror("fail fork\n");
    }
    return 0;
}


练习1

使用有名管道实现两个没有亲缘关系的进程间通信

进程a,发送

#include"head.h"
int main(int argc, char const *argv[])
{
    mkfifo("./myfifo", 0664);
    int fd = open("./myfifo", O_WRONLY);
    if (fd < 0)
    {
        perror("fail open");
        return -1;
    }
    char buff[1024] = {0};
    while (1)
    {
        fgets(buff, sizeof(buff), stdin);
        write(fd, buff, strlen(buff));  
    }
    close(fd);
    return 0;
}

进程b,接收并打印

#include"head.h"
int main(int argc, char const *argv[])
{
    mkfifo("./myfifo", 0664);
    int fd = open("./myfifo", O_RDONLY);
    if (fd < 0)
    {
        perror("fail open");
        return -1;
    }
    char buff[1024] = {0};
    while (1)
    {
        memset(buff, 0, sizeof(buff));
        size_t size = read(fd, buff, sizeof(buff));
        if(size <= 0)
        {
            break;
        }
        printf("buff  = %s\n", buff);
    }  
    return 0;
}

练习2

两个线程间发送文件

思路:先把要发送的文件读入到发送进程中的缓冲区,再由缓冲区写入到管道,接收进程读取管道中的内容,写入到文件

图解:

发送进程

#include"head.h"
int main(int argc, char const *argv[])
{
    if (argc != 2)
    {
        printf("Usage:./send <srcfile>");
        return -1;
    }
    mkfifo("./myfifo", 0664);
    int fdfifo = open("./myfifo", O_WRONLY);
    if (fdfifo < 0)
    {
        perror("fail open fdfifo");
        return -1;
    }

    int fdsrc = open(argv[1], O_RDONLY);   
    if(fdsrc < 0)
    {
        perror("fail open fdsrc");
        return -1;
    }

    char buff[1024] = {0};
    while (1)
    {
        size_t size = read(fdsrc, buff, sizeof(buff));
        if (size <= 0)
        {
            break;
        }
        write(fdfifo, buff, size);
    }
    close(fdfifo);
    close(fdsrc);
    return 0;
}

接收进程

#include"head.h"
int main(int argc, char const *argv[])
{
    if (argc != 2)
    {
        printf("Usage:./recv <dstfile>");
        return -1;
    }
    mkfifo("./myfifo", 0664);
    int fdfifo = open("./myfifo", O_RDONLY);
    if (fdfifo < 0)
    {
        perror("fail open fdfifo");
        return -1;
    }

    int fddst = open(argv[1], O_WRONLY | O_CREAT | O_TRUNC, 0664); 
    if(fddst < 0)
    {
        perror("fail open fddst");
        return -1;
    }

    char buff[1024] = {0};
    while (1)
    {
        size_t size = read(fdfifo, buff, sizeof(buff));
        if (size <= 0)
        {
            break;
        }
        write(fddst, buff, size);
    }
    close(fdfifo);
    close(fddst);
    remove("./myfifo");
    return 0;
}


memset函数介绍

将内存清成指定字节

#include <string.h>

void *memset(void *s, int c, size_t n);


网站公告

今日签到

点亮在社区的每一天
去签到