初始Linux进程间通信(单机)

news2024/10/7 7:23:11

目录

1、前言

2、进程间通信的分类

管道

System V IPC

POSIX IPC

3、管道

3.1 匿名管道

pipe

实例代码

 管道读写规则:

管道特点

 3.2 命名管道

创建一个命名管道

两个进程间使用命名管道进行通信示例代码

4、system V共享内存

4.1  共享内存数据结构

4.2 共享内存函数

shmget函数

shmat函数

shmdt函数

shmctl函数

 实例代码

结语


1、前言

        在程序运行期间,每一个程序,每一个进程都是独立的,包括父子进程,写时拷贝也保证了每个进程的独立性,但是呢,在某些场景,就是需要我们进行不同的进程间通信,交互数据,今天咱们就来看看在单机模式下,有哪些可以进行进程间通信的方式呢。

2、进程间通信的分类

管道

        匿名管道pipe

        命名管道

System V IPC

        System V 消息队列

        System V 共享内存

        System V 信号量

POSIX IPC

        消息队列

        共享内存

        信号量

        互斥量

        条件变量

        读写锁

3、管道

        管道是Unix中最古老的进程间通信的形式。从一个进程连接到另一个进程的一个数据流称为一个“管道”

3.1 匿名管道

pipe

#include <unistd.h>

int pipe(int fd[2]);

功能:

        创建一无名管道

参数

        fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端

返回值:

        成功返回0,失败返回错误代码

实例代码

#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <cassert>
#include <cstring>
using namespace std;
int main() {
    int pipefd[2] = {0};
    int n = pipe(pipefd);
    assert(n == 0);
    (void)n;
    int fd = fork();
    if (fd < 0) {
        perror("fork");
        return 1;
    }
    if  (fd == 0) {
        //这是子进程,用来接收数据,关闭用以写入数据的端口
        close(pipefd[1]);
        char buff[1024];
        memset(buff, '\0', sizeof(buff));
        while (read(pipefd[0], buff, sizeof(buff)) > 0) {
            
            cout << "子进程接收到的内容:" << buff << endl;
        }
        close(pipefd[0]);
        exit(1);
    }
    //这是父进程,需要关闭读端端口
    close(pipefd[0]);
    for (int i = 0; i < 10; ++i) {
        write(pipefd[1], "hello ltx!", 10);
        sleep(1);
    }
    close(pipefd[1]);
    return 0;

}

运行结果

 使用fork来完成进程间通信的原理

        fork前:

         fork后:

 站在文件描述符角度,深度理解:

        在fork前,父进程打开了一个文件,这个文件被放在files_struct中,创建子进程之后,子进程会继承父进程的files_struct,也会继承在其中指针指向的文件,这样,父子进程就指向了同一个文件,

 

 

 管道读写规则:

当没有数据可读时

        O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。

        O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。

当管道满的时候

        O_NONBLOCK disable: write调用阻塞,直到有进程读走数据

        O_NONBLOCK enable:调用返回-1,errno值为EAGAIN

如果所有管道写端对应的文件描述符被关闭,则read返回0

如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程退出

当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。

当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。

管道特点

        只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。

        管道提供流式服务

        一般而言,进程退出,管道释放,所以管道的生命周期随进程

        一般而言,内核会对管道操作进行同步与互斥

        管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道

 3.2 命名管道

        管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。

        如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。命名管道是一种特殊类型的文件,这类特殊的文件不会保存数据,只作用于内存中,充当缓冲区的作用。

创建一个命名管道

mkfifo filename

命名管道也可以从程序里创建,相关函数有:

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

创建命名管道

int main(int argc, char *argv[]) 
{
    mkfifo("pipe.p", 0666); 
    return 0;
}

匿名管道与命名管道的区别

        匿名管道由pipe函数创建并打开。

        命名管道由mkfifo函数创建,打开用open

        FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义。

命名管道的打开规则

如果当前打开操作是为读而打开FIFO时

        O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO

        O_NONBLOCK enable:立刻返回成功

如果当前打开操作是为写而打开FIFO时

        O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO

        O_NONBLOCK enable:立刻返回失败,错误码为ENXIO

两个进程间使用命名管道进行通信示例代码

server.cpp

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cstdio>
#include <cstring>
#include <unistd.h>

using namespace std;


int main() {
    if (mkfifo("myfifo", 0666) < 0) {
        perror("mkfifo");
        exit(1);
    }
    int fd = open("myfifo", O_WRONLY);
    if (fd < 0) {
        perror("open");
        exit(1);
    }
    
    char buff[1024];
    while (1) {
        memset(buff, '\0', sizeof(buff));
        fflush(stdout);
        ssize_t n = read(0, buff, sizeof(buff) - 1);
        if (n > 0){
            buff[n] = 0;
            write(fd, buff, strlen(buff));
        }
        else  {
            perror("read");
            break;
        }
    }
    close(fd);
    return 0;
}

user.cpp

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cstring>
#include <unistd.h>

using namespace std;

int main() {
    int fd = open("myfifo", O_RDONLY);
    if (fd < 0) {
        perror("open");
        exit(1);
    }
    char buff[1024];
    while (true) {
        memset(buff, '0', sizeof(buff));
        ssize_t n = read(fd, buff, sizeof(buff) - 1);
        if (n > 0){
            buff[n] = 0;
            cout << "接收到的数据:" << buff;
        }
        else if (n == 0) {
            cout << "接收完毕" << endl;
            break;
        }else {
            perror("read");
            break;
        }
    }
    close(fd);
    return 0;
}

运行结果

4、system V共享内存

        共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据

内存共享示意图(仅为个人理解,如有错误,请私信作者):

4.1  共享内存数据结构

struct shmid_ds {
    struct ipc_perm     shm_perm;   /* operation perms */
    int         shm_segsz;  /* size of segment (bytes) */
    __kernel_time_t     shm_atime;  /* last attach time */
    __kernel_time_t     shm_dtime;  /* last detach time */
    __kernel_time_t     shm_ctime;  /* last change time */
    __kernel_ipc_pid_t  shm_cpid;   /* pid of creator */
    __kernel_ipc_pid_t  shm_lpid;   /* pid of last operator */ 
    unsigned short      shm_nattch; /* no. of current attaches */ 
    unsigned short      shm_unused; /* compatibility */
    void            *shm_unused2;   /* ditto - used by DIPC */ 
    void            *shm_unused3;   /* unused */
};

 Linux命令行中可以使用命令来查看共享内存

ipcs -m #查看共享内存
ipcrm -m #根据shmid释放共享内存

4.2 共享内存函数

shmget函数

int shmget(key_t key, size_t size, int shmflg);

功能:用来创建共享内存

参数

    key:这个共享内存段名字

    size:共享内存大小

    shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的

返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1

shmat函数

void *shmat(int shmid, const void *shmaddr, int shmflg);

功能:将共享内存段连接到进程地址空间

参数

    shmid: 共享内存标识

    shmaddr:指定连接的地址

    shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY

返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1

注意:

        shmaddr为NULL,核心自动选择一个地址

        shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。

        shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr -(shmaddr % SHMLBA)

        shmflg=SHM_RDONLY,表示连接操作用来只读共享内存

shmdt函数

 int shmdt(const void *shmaddr);

功能:将共享内存段与当前进程脱离

参数

    shmaddr: 由shmat所返回的指针

返回值:成功返回0;失败返回-1

注意:将共享内存段与当前进程脱离不等于删除共享内存段

shmctl函数

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

功能:用于控制共享内存

参数

    shmid:由shmget返回的共享内存标识码

    cmd:将要采取的动作(有三个可取值)

    buf:指向一个保存着共享内存的模式状态和访问权限的数据结构

返回值:成功返回0;失败返回-1

 实例代码

comm.hpp

#pragma once

#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <cstdio>

#define PATHNAME "/home/ltx"
#define PROJ_ID 100 
#define SIZE 4096

using namespace std;

static int commshm(int size, int flags){
    key_t _key = ftok(PATHNAME, PROJ_ID);
    if (_key < 0) {
        perror("ftok");
        exit(1);
    }
    int shmid = shmget(_key, size, flags);
    if (shmid < 0) {
        perror("shmget");
        exit(2);
    }
    cout << "共享内存创建/获取成功!" << endl;
    return shmid;
}

int shm_create(){
    return commshm(SIZE, IPC_CREAT | IPC_EXCL | 0666);
}

int shm_get(){
    return commshm(SIZE, IPC_CREAT);
}

int shm_destroy(int shmid){
    if (shmid < 0) {
        return -1;
    }
    return shmctl(shmid,IPC_RMID,nullptr);
}

server.cpp

#include "comm.hpp"


int main() {
    //创建共享内存
    int shmid = shm_create();
    
    //将共享内存挂到进程中
    char* addr = (char*)shmat(shmid, NULL, 0);
    //cout << addr << endl;
    sleep(2);
    
    //char a = 'a';
    //addr[0] = '\0';
    int i = 26;
    while (i--) {
        cout << "循环读取:";
        printf("client# %s\n", addr);
        sleep(1);
    }
    cout << "读取完毕" << endl;
    shmdt((void*)addr);
    sleep(2);
    int n = shm_destroy(shmid);
    if (n < 0) {
        perror("shm_destroy");
        exit(1);
    }
    
    return 0;
}

client.cpp

#include "comm.hpp"


int main() {
    //创建共享内存
    int shmid = shm_get();
    //将共享内存挂到进程中
    char* addr = (char*)shmat(shmid, nullptr, 0);
    sleep(2);

    char a = 'a';
    while (a <= 'z') {
        addr[a - 'a'] = a;
        addr[a - 'a' + 1] = '\0';
        a++;
        sleep(1);
    }
    
    shmdt((void*)addr);
    sleep(2);
    return 0;
}

运行结果:

结语

        目前先介绍这些,有机会以后再分享,如果有错误,那就请私信作者指出,谢谢!

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

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

相关文章

【档案专题】八、电子档案鉴定与销毁

导读&#xff1a;主要针对电子档案鉴定与销毁相关内容介绍。对从事电子档案管理信息化的职业而言&#xff0c;不断夯实电子档案管理相关理论基础是十分重要。只有通过不断梳理相关知识体系和在实际工作当中应用实践&#xff0c;才能走出一条专业化加职业化的道路&#xff0c;从…

Java虚拟机内部组成

1、栈区 public class Math {public int compute(){//一个方法对应一块栈帧内存区域int a l;int b 2;int c (a b)*10;return c; } public static void main(String[] args){Math math new, Math() ;math.compute() ;System.out.println("test");}} 栈是先进后出…

29 - restful - 套叠结构

套叠结构的两种方式: 1. marshal(数据,返回的格式) 函数 2. marshal_with(加工后的返回格式)装饰器 返回格式必须是符合json格式,如果直接返回不能用自定义的对象:User,Friend... 需要 marchal(),marchal_with()帮助进行转换 一. 定义模型类 一. marshal(数据,返回的结构) …

ssh访问远程宿主机的VMWare中NAT模式下的虚拟机

1.虚拟机端配置 1.1设置虚拟机的网络为NAT模式 1.2设置虚拟网络端口映射(NAT) 点击主菜单的编辑-虚拟网络编辑器&#xff1a; 启动如下对话框&#xff0c;选中NAT模式的菜单项&#xff0c;并点击NAT设置&#xff1a; 点击添加&#xff0c;为我们的虚拟机添加一个端口映射。…

java八股文面试[数据库]——MySQL索引的数据结构

知识点&#xff1a; 【2023年面试】mysql索引的基本原理_哔哩哔哩_bilibili 【2023年面试】mysql索引结构有哪些&#xff0c;各自的优劣是什么_哔哩哔哩_bilibili

巨擘科技|国内数据交易现状梳理及典型交易平台对比分析(附建设方案)

近年来,在国家促进要素市场化配置系列政策指导下,各地方政府有关部门单位均在积极探索数据要素市场化运行机制,在推进数据要素价值体系建立、数据要素市场规则构建等工作方面已初具成效。 基于此,本文选取国内相关典型政府主导型的数据交易平台进行案例分析,并揭示其特点…

【高性能计算】opencl语法及相关概念(三)事件,内存

opencl中的事件概念 当谈到OpenCL中的事件时&#xff0c;它们代表了执行的各个阶段或操作的状态信息。通过使用事件&#xff0c;您可以跟踪和管理内核执行以及内存操作的进度和顺序。以下是与OpenCL事件相关的关键概念&#xff1a; 创建事件&#xff1a;您可以使用clCreateUse…

动态数组 Vector(难度1)(V)

C数据结构与算法实现&#xff08;目录&#xff09; 前驱课程 C 精简教程 目录&#xff08;必读&#xff09; 堆数组 heap array 面相对象的堆数组 1 原始堆数组的缺点&#xff1a; 1&#xff09; 原始堆数组 其长度是固定不变的。 2&#xff09; 使用指针管理元素&#…

数据大小无限制!海量倾斜摄影三维模型在线查看及分享

通常&#xff0c;倾斜摄影三维模型数据量都较大&#xff0c;这是由其高精度、对地表全覆盖的真实影像所决定的。如何将海量倾斜摄影模型数据加载到地图中并进行在线查看是行业用户一直关心的内容&#xff0c;现在通过「四维轻云」就可以在线查看及分享倾斜摄影三维模型。 1、倾…

静态类方法的同步

由于在调用静态方法时&#xff0c;对象实例不一定被创建。因此&#xff0c;就不能使用this来同步静态方法&#xff0c;而必须使用Class对象来同步静态方法。代码如下&#xff1a; 通过synchronized块同步静态方法 public class StaticSyncBlock { public static void…

nacos配置超级管理员账户,只能mysql存储数据(或者其他数据库)

nacos本身是不允许授权超级管理员账号的&#xff0c;也就是角色名“ROLE_ADMIN”。作者在页面上试过了&#xff0c;不必再次尝试改的方式是直接改数据库里面的数据

十八、责任链模式

一、什么是责任链模式 责任链&#xff08;Chain of Responsibility&#xff09;模式的定义&#xff1a;为了避免请求发送者与多个请求处理者耦合在一起&#xff0c;于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链&#xff1b;当有请求发生时&#xff0…

element ui-Pagination

页面分为两个表格&#xff0c;当两边的表格数据量大时&#xff0c;分页样式就会受到影响&#xff0c;可以将跳转按钮的个数减少 页面分页代码如下 页面效果

QT基础教程之七Qt消息机制和事件

QT基础教程之七Qt消息机制和事件 事件 事件&#xff08;event&#xff09;是由系统或者 Qt 本身在不同的时刻发出的。当用户按下鼠标、敲下键盘&#xff0c;或者是窗口需要重新绘制的时候&#xff0c;都会发出一个相应的事件。一些事件在对用户操作做出响应时发出&#xff0c…

【YOLOV5】YOLOV5添加SPPCSPC

当前YOLOV5版本为7.0 第一步 在models/common.py添加SPPCSPC class SPPCSPC(nn.Module):# CSP https://github.com/WongKinYiu/CrossStagePartialNetworksdef __init__(self, c1, c2, n1, shortcutFalse, g1, e0.5, k(5, 9, 13)):super(SPPCSPC, self).__init__()c_ int(2 *…

docker安装及使用-Linux

前提 确保docker支持当前系统版本&#xff0c;docker支持centos 7及以上版本&#xff0c;要求Linux内核版本不低于3.10 cat /etc/redhat-release #查看系统版本 查看内核版本三种方式 cat /proc/version uname -a uname -r 一、安装docker 0、卸载docker&#xff08;根…

二十一、中介者模式

一、什么是中介者模式 中介者&#xff08;Mediator&#xff09;模式的定义&#xff1a;定义一个中介对象来封装一系列对象之间的交互&#xff0c;使原有对象之间的耦合松散&#xff0c;且可以独立地改变它们之间的交互。中介者模式又叫调停模式&#xff0c;它是迪米特法则的典型…

Linux:ansible-playbook配置文件(剧本)

如果你还没有配置基础的ansible和一些基础用法可以去下面的链接 playbook是基于ansible的 Linux&#xff1a;ansible自动化运维工具_鲍海超-GNUBHCkalitarro的博客-CSDN博客 Linux&#xff1a;ansible自动化运维工具_鲍海超-GNUBHCkalitarro的博客-CSDN博客 Linux&…

4.5 放映演示文稿

制作完成的演示文稿最终是为了向观众进行展示&#xff0c;因此掌握演示文稿放映的技巧至关重要。本节主要介绍演示文稿放映的相关技巧&#xff0c;包括排练计时、自定义放映、设置放映方式以及墨迹画笔等内容。 4.5.1 排练计时 当进行演讲汇报时&#xff0c;常常要使用到幻灯…

【PHP】文件操作

文章目录 文件编程的必要性目录操作其它目录操作递归遍历目录PHP5常见文件操作函数PHP4常见文件操作函数其他文件操作函数 文件编程的必要性 文件编程指利用PHP代码针对文件&#xff08;文件夹&#xff09;进行增删改查操作。 在实际开发项目中&#xff0c;会有很多内容&…