【Linux】进程通信

news2024/11/23 20:56:33

目录

一、管道通信

二、共享内存

三、消息队列


一、管道通信

管道是由操作系统维护的一个文件,管道通信的本质就是将管道文件作为临界资源,实现不同进程之间的数据读写,但是管道只允许父子进程或者兄弟进程之间的通信。

管道文件本身是全双工机制的,但是在管道通信中,它的工作模式是半双工的,在管道通信之前,读端会关闭对管道文件的写通道,写端会关闭对管道文件的读通道。

当读端在读数据时,若写端关闭,读端会将管道文件中剩余数据读取结束之后再关闭;当写端在写数据时,若读端关闭,写端会被操作系统直接关闭。

那么管道通道如何实现数据交互呢?方法是创建两个管道进行通信。

管道通信方式分为匿名管道命名管道,由 pipe() 系统调用创建并打开,命名管道由 mkfifo() 系统调用创建并由 open() 打开,他们的通信本质是一样的。

// comm.hpp 头文件
#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <string>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <unistd.h>
#include <fcntl.h>
#include <fstream>

#define NAMED_PIPE "./named_pipe"

bool create_fifo(const std::string& path) {
    umask(0);
    int n = mkfifo(path.c_str(), 0666);
    if (n == 0) {
        return true;
    }
    else {
        std::cout << "errno: " << errno << "err string: " << strerror(errno) << std::endl;
        return false; 
    }
}

void remove_fifo(const std::string& path) {
    int n = unlink(path.c_str());
    // unlink 函数功能是删除文件,但会在判断此文件状态之后再删除
    // 若有进程打开此文件,则不会立即删除,等到无进程打开该文件时才会删除
    // 若此文件有多个链接,则进行连接数减一操作
    // 执行成功返回 0,失败返回 -1
    assert(n == 0);
}
// Server
#include "comm.hpp"

int main() {
    std::cout << "server begin" << std::endl;
    int rfd = open(NAMED_PIPE, O_RDONLY);
    if (rfd < 0) {
        bool r = create_fifo(NAMED_PIPE);
        assert(r);
        rfd = open(NAMED_PIPE, O_RDONLY);
    }

    // read
    char buffer[1024];
    while (true) {
        ssize_t s = read(rfd, buffer, sizeof(buffer) - 1);
        if (s > 0) {
            buffer[s] = 0;
            std::cout << "client->server# " << buffer << std::endl;
        }
        else if (s == 0) {
            std::cout << "client quit, me too" << std::endl;
            break;
        }
        else {
            std::cout << "err string: " << strerror(errno) << std::endl;
            break;
        }
    }

    close(rfd);
    std::cout << "server end" << std::endl;

    remove_fifo(NAMED_PIPE);
    return 0;
}
// Client
#include "comm.hpp"

int main() {
    std::cout << "client begin" << std::endl;
    int wfd = open(NAMED_PIPE, O_WRONLY, 0666);
    if (wfd < 0) {
        exit(1);
    }

    // write
    char buffer[1024];
    while (true) {
        std::cout << "please say# ";
        fgets(buffer, sizeof(buffer), stdin);
        if (strlen(buffer) > 0) {
            buffer[strlen(buffer) - 1] = 0;
        }
        if (strcmp(buffer, "quit") == 0) {
            break;
        }
        ssize_t n = write(wfd, buffer, strlen(buffer));
        assert(n == strlen(buffer));
    }

    close(wfd);
    std::cout << "client end" << std::endl;
    return 0;
}

二、共享内存

共享内存是由操作系统维护的一块地址空间,它的工作机制是全双工。

// comm.hpp
#pragma once
#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <unistd.h>

#define NAME_PATH "."
#define PROJ_ID 0X66
#define MAX_SIZE 4096

// 创建共享内存时,通过 ftok() 函数获得 key 值保证共享内存在系统中的唯一性
key_t get_key() {
    key_t k = ftok(NAME_PATH, PROJ_ID);
    if (k < 0) {
        std::cout << errno << ": " << strerror(errno) << std::endl;
        exit(1);
    }
    return k;
}

int get_shm_helper(key_t k, int flags) {
    // shmget : 创建共享内存
    int shmID = shmget(k, MAX_SIZE, flags);
    if (shmID < 0) {
        std::cout << errno << ": " << strerror(errno) << std::endl;
        exit(1);
    }
    return shmID;
}

int get_shm(key_t k) {
    return get_shm_helper(k, IPC_CREAT);
}

int create_shm(key_t k) {
    return get_shm_helper(k, IPC_CREAT | IPC_EXCL | 0600);
    // IPC_EXCL 不能单独使用,若共享内存不存在则创建,存在则报错
}

// 删除共享内存
void del_shm(int shmID) {
    // shmctl : 控制共享内存
    if (shmctl(shmID, IPC_RMID, NULL) == -1) {
        std::cout << errno << ": " << strerror(errno) << std::endl;
        exit(1);
    }
}

// 关联共享内存
void* attach_shm(int shmID) {
    void* start = shmat(shmID, NULL, 0);
    if ((long long)start == -1) {
        std::cout << errno << ": " << strerror(errno) << std::endl;
        exit(1);
    }
    return start;
}

// 取消关联
void del_attach_shm(void* start) {
    if (shmdt(start) == -1) {
        std::cout << errno << ": " << strerror(errno) << std::endl;
        exit(1);
    }
}
// Server
#include "comm.hpp"

int main() {
    key_t k = get_key();
    printf("0x%x\n", k);
    int shmID = create_shm(k);      // 创建共享内存
    printf("shmID = %d\n", shmID);

    // 内核为每个共享内存维护一个结构体:struct shmid_ds
    // struct shmid_ds
    // {
    //     struct ipc_perm shm_perm;    /* Ownership and permissions */
    //     size_t          shm_segsz;   /* Size of segment (bytes) */
    //     time_t          shm_atime;   /* Last attach time */
    //     time_t          shm_dtime;   /* Last detach time */
    //     time_t          shm_ctime;   /* Last change time */
    //     pid_t           shm_cpid;    /* PID of creator */
    //     pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */
    //     shmatt_t        shm_nattch;  /* No. of current attaches */
    // };

    struct shmid_ds ds;
    shmctl(shmID, IPC_STAT, &ds);
    printf("获取属性: size(%d) pid(%d) myself(%d) key(0x%x)\n",
        ds.shm_segsz, ds.shm_cpid, getpid(), ds.shm_perm.__key);

        
    sleep(2);
    char* start = (char*)attach_shm(shmID);     // 关联共享内存
    printf("attach success, address start: %p\n", start);
    
    // 从共享内存中读取数据
    while (true) {
        if (*start != '\0') {
            printf("clent say# %s\n", start);
            snprintf(start, MAX_SIZE, "\0");    // 打印消息后清空共享内存
        }
    }

    sleep(2);
    del_attach_shm(start);
    return 0;
}
// Client
#include "comm.hpp"

// 查看ipc资源
// 查看:ipcs -m
// 删除:ipcrm -m shmID

int main() {
    key_t k = get_key();
    printf("0x%x\n", k);
    int shmID = get_shm(k);
    printf("%d\n", shmID);

    sleep(2);
    char* start = (char*)attach_shm(shmID);     // 关联共享内存
    printf("attch success, address start: %p\n", start);

    // 将数据写入共享内存
    int count = 1;
    while (true) {
        printf("[please input]# ");
        char message[1024] = {0};
        fgets(message, sizeof(message) - 1, stdin);
        message[strlen(message) - 1] = 0;
        snprintf(start, MAX_SIZE, "%s [pid(%d)][messageID: %d]\n", message, getpid(), count++);
    }

    sleep(2);
    del_attach_shm(start);

    return 0;
}

优点:相比于管道而言,共享内存能够支持任意进程之间的通信,而且访问数据的速度也比管道要快。这得益于通信直接访问内存,而管道则需要先通过操作系统访问文件再获得内存数据。

缺点:用于进程间通信时,共享内存本身不支持阻塞等待操作。这是因为当读端读取数据后,数据并不会在内存中清空。因此读端和写端可以同时访问内存空间,即全双工。因为共享内存本质是进程直接访问内存,无法主动停止读取,如果读端不加以限制,那么将持续读取数据。同理,写端也会持续写入数据。换句话说,共享内存本身没有访问控制。 

三、消息队列

消息队列是由操作系统维护的一个数据结构,遵循队列的FIFO原则,半双工机制。

通过消息队列通信的两个进程也分为读端和写端,读端只负责从消息队列中拿数据,写端只负责向消息队列中写数据。

消息队列的优点是异步性和系统解耦,异步性是指发送消息的进程不需等待接收消息的进程的响应,可以继续执行自己的任务,系统解耦是指发送方和接收方都不关心对方进程的状态,只关注消息的发送和接收。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<pthread.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<sys/shm.h>
#include<fcntl.h>
#include<semaphore.h>

struct msg_buffer {
    long type;		//1表示sender发送的正常数据,2表示sender1发送的结束信号,3表示sender2发送的结束信号
    char text[100];	
};

void *sender1_thread() {
    struct msg_buffer msg;
    char buf[100];
    
    int msqid = msgget((key_t) 2023, 0666|IPC_CREAT);
    if(msqid == -1) {
        printf("消息队列创建失败!");
        exit(-1);
    }
    
    sem_t *mutex = sem_open("mutex", O_CREAT | O_RDWR, 0666, 0);
    sem_t *sender1 = sem_open("sender1", O_CREAT | O_RDWR, 0666, 0);
    sem_t *receive1 = sem_open("receive1", O_CREAT | O_RDWR, 0666, 0);
    sem_t *next_input = sem_open("next_input", O_CREAT | O_RDWR, 0666, 0);
    
    while(1) {
        sem_wait(mutex);
        sem_wait(next_input);
        printf("sender1 input: ");
        gets(buf);
        
        msg.type = 1;
        if(strcmp(buf, "exit") == 0) {
            strcpy(msg.text, "end1");
            msgsnd(msqid, (void *)&msg, 100, 0);	// 将消息发送到消息队列中
            sem_wait(receive1);
            msgrcv(msqid, (void *)&msg, 100, 2, 0);
            printf("sender1 receive: %s\n\n", msg.text);
            
            sem_post(sender1);
            sem_post(mutex);
            sleep(1);
            return 0;
        }
        else {
            strcpy(msg.text, buf);
            msgsnd(msqid, (void *)&msg, 100, 0);
            sem_post(mutex);
            sleep(1);
        }
    }
}

void *sender2_thread() {
    struct msg_buffer msg;
    char buf[100];
    
    int msgid = msgget((key_t) 2023, 0666|IPC_CREAT);
    if(msgid == -1) {
        printf("消息队列创建失败!");
        exit(-1);
    }
    
    sem_t *mutex = sem_open("mutex", O_CREAT | O_RDWR, 0666, 0);
    sem_t *sender2 = sem_open("sender2", O_CREAT | O_RDWR, 0666, 0);
    sem_t *receive2 = sem_open("receive2", O_CREAT | O_RDWR, 0666, 0);
    sem_t *next_input = sem_open("next_input", O_CREAT | O_RDWR, 0666, 0);
    
    while(1) {
        sem_wait(mutex);
        sem_wait(next_input);
        printf("sender2 input: ");
        gets(buf);
        
        msg.type = 1;
        if(strcmp(buf,"exit") == 0) {
            strcpy(msg.text, "end2");	
            msgsnd(msgid, (void *)&msg, 100, 0);	
            sem_wait(receive2);
            msgrcv(msgid, (void *)&msg, 100, 3, 0);
            printf("sender2 receive: %s\n\n", msg.text);
            
            sem_post(sender2);
            sem_post(mutex);
            sleep(1);
            return 0;
        }
        else {
            strcpy(msg.text, buf);
            msgsnd(msgid, (void *)&msg, 100, 0);
            sem_post(mutex);
            sleep(1);
        }
    }
}

void *receiver_thread() {
    int finish1 = 0;
    int finish2 = 0;
    struct msg_buffer msg;
    char buf[100];
    
    sem_t *sender1 = sem_open("sender1", O_CREAT | O_RDWR, 0666, 0);
    sem_t *receive1 = sem_open("receive1", O_CREAT | O_RDWR, 0666, 0);
    sem_t *sender2 = sem_open("sender2", O_CREAT | O_RDWR, 0666, 0);
    sem_t *receive2 = sem_open("receive2", O_CREAT | O_RDWR, 0666, 0);
    sem_t *next_input = sem_open("next_input", O_CREAT | O_RDWR, 0666, 0);
    
    int msqid = msgget((key_t) 2023, 0666 | IPC_CREAT);
    if( msqid == -1) {
        printf("create failed");
        exit(-1);
    }
    
    int n;
    while(1){
        if(msgrcv(msqid, (void *)&msg, 100, 0, 0) > 0) {
            printf("Receiver receive: %s\n", msg.text);
            sem_post(next_input);
            
            if(strcmp(msg.text, "end1") == 0) {
                msg.type = 2;
                strcpy(msg.text, "over1");
                n = msgsnd(msqid, (void *)&msg, 100, 0);
                if(n != 0) {
                    sem_post(receive1);
                    sem_wait(sender1);
                }
                finish1 = 1;
            }
            else if(strcmp(msg.text, "end2") == 0) {
            	msg.type = 3;
	    	    strcpy(msg.text, "over2");
            	n = msgsnd(msqid, (void *)&msg, 100, 0);    // 将详细发送到消息队列中
            	if(n != 0) {
                    sem_post(receive2);
                    sem_wait(sender2);
				}
        	finish2 = 1;
            }
        }
        
        if(finish1 == 1 && finish2 == 1) {
            msgctl(msqid, IPC_RMID, 0);	// 删除消息队列
            exit(0);
        }
    }
}


int main() {
	// msgget函数用于创建一个新的消息队列或者获取一个已经存在的消息队列的标识符
    int msqid = msgget((key_t) 2023, 0666 | IPC_CREAT);
    msgctl(msqid, IPC_RMID, 0);	// 删除消息队列,释放相应的系统资源
    sem_unlink("mutex");
    sem_unlink("sender1");
    sem_unlink("sender2");
    sem_unlink("receiver1");
    sem_unlink("receiver2");
    sem_unlink("next_input");
    
    sem_t *mutex = sem_open("mutex", O_CREAT | O_RDWR, 0666, 0);
    sem_t *sender1 = sem_open("sender1", O_CREAT | O_RDWR, 0666, 0);
    sem_t *receive1 = sem_open("receive1", O_CREAT | O_RDWR, 0666, 0);
    sem_t *sender2 = sem_open("sender2", O_CREAT | O_RDWR, 0666, 0);
    sem_t *receive2 = sem_open("receive2", O_CREAT | O_RDWR, 0666, 0);
    sem_t *next_input = sem_open("next_input", O_CREAT | O_RDWR, 0666, 0);
    
    pthread_t p1, p2, p3;
    if(pthread_create(&p1, NULL, sender1_thread, NULL) != 0) {
        printf("Sender1线程创建失败!");
        exit(-1);
    }
    
    if(pthread_create(&p2, NULL, sender2_thread, NULL) != 0) {
        printf("Sender2线程创建失败!");
        exit(-1);
    }
    
    if(pthread_create(&p3, NULL, receiver_thread, NULL) != 0) {
        printf("receiver线程创建失败!");
        exit(-1);
    }
  
    sem_post(mutex);
    sem_post(next_input);
    
    pthread_join(p1, NULL);
    pthread_join(p2, NULL);
    pthread_join(p3, NULL);
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1532934.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据结构从入门到精通——堆排序

堆排序 前言一、堆排序的基本思想二、堆排序的特性总结三、堆排序的动图展示四、堆排序的代码实现向上建堆test.c 前言 堆排序是一种利用堆数据结构实现的排序算法。首先&#xff0c;它将待排序的数组构建成一个大顶堆或小顶堆。然后&#xff0c;通过不断将堆顶元素&#xff0…

MC10T1S-10BASE-T1S车载以太网转换器

10BASE-T1S车载以太网转换器 为10BASE-T1S车载以太网转换器&#xff0c;支持Multidrop bus line和Point-to-Point。采用的DB9接口类型&#xff0c;支持PLCA。10BASE-T1S是IEEE 802.3cg标准制定的IEEE汽车以太网的最新标准之一&#xff0c;采用UTP这样的一对无屏蔽的双芯电缆进行…

Uibot (RPA设计软件)财务会计Web应用自动化(批量开票机器人)

Uibot (RPA设计软件&#xff09;Mage AI智能识别&#xff08;发票识别&#xff09;———机器人的小项目友友们可以参考小北的课前材料五博客~ (本博客中会有部分课程ppt截屏,如有侵权请及请及时与小北我取得联系~&#xff09; 紧接着小北的前两篇博客&#xff0c;友友们我们…

webpack5零基础入门-13生产模式

1.生产模式介绍 生产模式是开发完成代码后&#xff0c;我们需要得到代码将来部署上线。 这个模式下我们主要对代码进行优化&#xff0c;让其运行性能更好。 优化主要从两个角度出发: 优化代码运行性能优化代码打包速度 2.生产模式准备 我们分别准备两个配置文件来放不同的…

【C++】为什么vector的地址与首元素地址不同?

文章目录 一、问题发现&#xff1a;二、结果分析三、问题解析 一、问题发现&#xff1a; &vector和&vector[0]得到的两个地址居然不相同&#xff0c;对数组array取变量名地址和取首元素地址的结果是相同的。这是为啥呢&#xff1f; 使用下面代码进行验证&#xff1a;…

流畅的 Python 第二版(GPT 重译)(五)

第九章. 装饰器和闭包 有人对将这个功能命名为“装饰器”的选择提出了一些抱怨。主要的抱怨是该名称与其在 GoF 书中的用法不一致。 名称 decorator 可能更多地归因于其在编译器领域的用法—语法树被遍历并注释。 PEP 318—函数和方法的装饰器 函数装饰器让我们在源代码中“标记…

单片机-- 数电(3)

编码器与译码器 译码 &#xff1a;将二进制代码转化为其他进制的代码 编码 &#xff1a;就是将其他代码转换为二进制码 编码器的类型 1二进制编码器 用n位二进制数码对2的n次方个输入信号进行编码的电路 2二-十进制编码器 将0到9十个十进制数转化为二进制代码的电路 2…

SpringCloud中的@EnableDiscoceryClient和@EnableFeignClients注解的作用解析、RPC远程过程调用

目录 EnableDiscoveryClient 服务发现的核心概念 服务注册中心 EnableDiscoveryClient注解的作用 服务心跳健康检查 使用示例 EnableFeignClients Feign简介 EnableFeignClients注解的作用 RPC&#xff08;Remote Procedure Call&#xff09; 参考链接 Spring Cloud…

C++项目 -- 负载均衡OJ(一)compile_server

C项目 – 负载均衡OJ&#xff08;二&#xff09;compile_server 文章目录 C项目 -- 负载均衡OJ&#xff08;二&#xff09;compile_server一、compile_server设计1.总体服务流程 二、compiler.hpp三、runner.hpp四、compile_run.hpp五、compile_server.cc5.1.编译功能调试&…

Dockerfile Docker Compose(实战总结)

Dockerfile & Docker Compose&#xff08;实战总结&#xff09; Dockerfile Dockerfile 是用来构建Docker镜像文件&#xff0c;是由一条条构建镜像所需的指令构成的脚步。 步骤&#xff1a; 编写Dockerfile 文件docker build 构建镜像docker run 运行镜像docker push 发…

【黄金手指】windows操作系统环境下使用jar命令行解压和打包Springboot项目jar包

一、背景 项目中利用maven将Springboot项目打包成生产环境jar包。名为 prod_2024_1.jar。 需求是 修改配置文件中的某些参数值&#xff0c;并重新发布。 二、解压 jar -xvf .\prod_2024_1.jar释义&#xff1a; 这段命令是用于解压缩名为"prod_2024_1.jar"的Java归…

【QT入门】实现一个简单的图片查看软件

声明&#xff1a;该专栏为本人学习Qt知识点时候的笔记汇总&#xff0c;希望能给初学的朋友们一点帮助(加油&#xff01;) 往期回顾&#xff1a; 【QT入门】qmake和cmake的简单区别-CSDN博客 【QT入门】VS qt和QtCreator项目的相互转换-CSDN博客 【QT入门】Qt架构与三个窗口的区…

航顺车规级SoC全新亮相,助推汽车智能化发展

受益于汽车电动化、智能化和网联化的推进&#xff0c;汽车车身域和座舱域MCU市场规模持续扩大。据统计&#xff0c;2021年中国车载芯片MCU市场规模达30.01亿美元&#xff0c;同比增长13.59%&#xff0c;预计2025年市场规模将达42.74亿美元。 在技术要求方面&#xff0c;对…

Docker如何端口映射?

Docker是一种流行的开源容器化平台&#xff0c;它允许开发者将应用程序和其依赖资源打包到一个称为容器的可移植单元中。Docker提供了强大的管理和部署工具&#xff0c;使得应用程序可以在不同的环境中运行&#xff0c;无需担心环境配置的问题。在使用Docker部署应用程序时&…

Websocket + Vue使用

这里有一篇文档可以参考一下> 闪现 POM文件 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId><version>2.7.0</version> </dependency> WebSocketConf…

模型部署 - onnx的导出和分析 - onnx 的架构和 onnx helper 的使用 - 学习记录

onnx 的架构和 onnx helper 的使用 简介一、onnx 的架构二、onnx 实践2.1、 create - linear.onnx2.1.1、要点一&#xff1a;创建节点2.1.2、要点二&#xff1a;创建张量2.1.3、要点三&#xff1a;创建图 2.2、 create - onnx.convnet2.3、使用 onnx helper 导出的基本流程总结…

Web框架开发-Django模型层(数据库操作)

一、ORM介绍 MVC或者MVC框架中包括一个重要的部分,就是ORM,它实现了数据模型与数据库的解耦,即数据模型的设计不需要依赖于特定的数据库,通过简单的配置就可以轻松更换数据库,这极大的减轻了开发人员的工作量,不需要面对因数据库变更而导致的无效劳动ORM是“对象-关系-映…

g++在windows下使用C++进程库无法传入参数<求助>

如题&#xff1a; windows11使用g的时候&#xff0c;想使用下线程库。但是就发现了如题的问题。在使用时&#xff0c;不传入参数时不会报错的&#xff0c;但是传入参数之后就产生了报错。 点击进入定义发现头文件定义明明是正确的。 具体报错如下图。

流畅的 Python 第二版(GPT 重译)(二)

第三章&#xff1a;字典和集合 Python 基本上是用大量语法糖包装的字典。 Lalo Martins&#xff0c;早期数字游牧民和 Pythonista 我们在所有的 Python 程序中都使用字典。即使不是直接在我们的代码中&#xff0c;也是间接的&#xff0c;因为dict类型是 Python 实现的基本部分。…

绝地求生:PUBG延长GPU崩溃时间新方法

相信大家都在被GPU游戏崩溃苦恼已久&#xff0c;PUBG这个游戏崩溃&#xff0c;跟超频是没有多大关系的&#xff0c;只要超频TM5过测&#xff0c;YC过测&#xff0c;或者双烤过测&#xff0c;就没问题。主要是这个游戏的优化不行&#xff0c;特别40系显卡&#xff0c;对内存条也…