【Linux】进程间通信

1.进程间通信的目的

进程具有独立性,因此进程间想要通信,成本会非常高,但有时候又需要多个进程协同处理一件事,所以进程间通信是必不可少的

2.管道

从一个进程连接到另一个进程的一个数据流称为一个“管道”

2.1 匿名管道

显示器是缺乏管道控制的,所以父子进程在向显示写入的时候是无序的,而管道是自带访问控制机制的,如果管道内部没有数据,reader就必须阻塞等待;如果管道内部被写满了,writer就必须阻塞等待

代码:

#include<iostream>
#include<unistd.h>
#include<cstring>
#include<sys/types.h>
#include<sys/wait.h>
using namespace std;
int main()
{
    //1.创建管道
    int pipefd[2] = {0};
    if(pipe(pipefd) != 0)//创建一个管道成功返回0
    {
        cerr<<"pipe error\n"<<endl;
        return 1;
    }
    //cout<<pipefd[0]<<" "<<pipefd[1]<<endl;  pipefd[0] 是读端的文件描述符  pipefd[1] 是写端的文件描述符
    //2.创建子进程
    pid_t id = fork();
    if(id < 0)
    {
        cerr<<"fork error" <<endl;
        return 2;
    }
    else if(id == 0)
    {
        //child
        //子进程读取数据,关闭写端
        close(pipefd[1]);
        char buffer[1024];
        while(true)
        {
            //清空缓冲区
            memset(buffer,0,sizeof(buffer));
            ssize_t s = read(pipefd[0],buffer,sizeof(buffer) - 1);//返回读到字节的个数
            if(s > 0)
            {
                //读取成功
                buffer[s] = '\0';
                cout<<"子进程收到消息,内容是:"<<buffer<<endl;
            }
            else if(s == 0)
            {
                cout<<"父进程写完了,我也退出了"<<endl;
                break;
            }
            else
            {}
        }
        close(pipefd[0]);
        exit(0);
    }
    else
    {
        //parent
        //父进程来进行写入,关闭读端
        close(pipefd[0]);
        const char *msg = "你好子进程,我是父进程,这次发送的信息编号是:";
        int cnt = 0;
        while(cnt < 5)
        {
            sleep(1);
            char sendBuffer[1024];
            sprintf(sendBuffer, "%s : %d",msg, cnt);
            write(pipefd[1],sendBuffer,strlen(sendBuffer));
            cnt++;
        }
        close(pipefd[1]);
        cout<<"父进程写完了"<<endl;
    }
    pid_t res = waitpid(id,nullptr,0);
    if(res > 0)
    {
        cout<<"等待子进程成功"<<endl;
    }
    return 0;
}

进程池:通过父进程控制一批子进程

#include<iostream>
#include<unistd.h>
#include<cstring>
#include<sys/types.h>
#include<sys/wait.h>
#include<vector>
#include<ctime>
#include<unordered_map>
#include<cassert>
#include<stdlib.h>

using namespace std;

typedef void (*functor)();//函数指针
vector<functor> functors;//方法集合
unordered_map<uint32_t,string> info; //uint32_t = unsigned int

void f1()
{
    cout<<"这是一个处理日志的任务,执行进程ID ["<<getpid()<<
    "] 执行时间是 [" << time(nullptr) << "]\n"<<endl;
}
void f2()
{
     cout<<"这是一个备份数据任务,执行进程ID ["<<getpid()<<
    "] 执行时间是 [" << time(nullptr) << "]\n"<<endl;
}
void f3()
{
     cout<<"这是一个处理网络链接的任务,执行进程ID ["<<getpid()<<
    "] 执行时间是 [" << time(nullptr) << "]\n"<<endl;
}
void loadFunctor()
{
    info.insert({functors.size(),"处理日志的任务"});
    functors.push_back(f1);
    
    info.insert({functors.size(),"备份数据的任务"});
    functors.push_back(f2);

    info.insert({functors.size(),"处理网络链接的任务"});
    functors.push_back(f3);
}

//pair<进程pid,进程对应的管道写端fd>
typedef pair<int32_t,int32_t> elem;
int processNum = 5;
void work(int blockFd)
{
    cout<<"进程["<<getpid()<<"]"<<"开始工作"<<endl;

    while(true)
    {
        //a.阻塞等待 b.获取任务信息
        uint32_t operatorCode = 0;
        ssize_t s = read(blockFd,&operatorCode,sizeof(uint32_t));
        if(s == 0) break;
        assert(s == sizeof(uint32_t));//读到的不是四个字节报错
        (void)s;
        //c.处理任务
        if(operatorCode < functors.size()) functors[operatorCode]();
    }
    cout<<"进程["<<getpid()<<"]"<<"结束工作"<<endl;
}
void blanceSendTask(const vector<elem> &processFds)
{
    srand((long long)time(nullptr));
    while(true)
    {
        sleep(1);
        //随机选择一个子进程
        //较为均匀的将任务给所有的子进程 ---- 负载均衡
        uint32_t pick = rand() % processFds.size();

        //选择一个任务
        uint32_t task = rand() % functors.size();

        //把任务给一个指定的进程
        write(processFds[pick].second,&task,sizeof(task));
        //打印对应的提示信息
        cout<<"父进程指派任务->"<<info[task]<<"给进程:"<<processFds[pick].first<<"编号:"<<pick<<endl;
    }
}
int main()
{
    loadFunctor();
    vector<elem> assignMap;
    //创建processNum个进程
    for(int i = 0;i < processNum; i++)
    {
        //定义保存管道fd的对象
        int pipefd[2] = {0};
        //创建管道
        pipe(pipefd);
        //创建子进程
        pid_t id = fork();
        if(id == 0)
        {
            //子进程读取,r->pipefd[0]
            close(pipefd[1]);
            //子进程执行
            work(pipefd[0]);
            close(pipefd[0]);
            exit(0);
        }
        //父进程做的事情
        close(pipefd[0]);
        elem e(id,pipefd[1]);
        assignMap.push_back(e);
    }
    cout<<"create all processs success!"<<endl;
    //父进程派发任务
    blanceSendTask(assignMap);
    //回收资源
    for(int i = 0; i < processNum;i++)
    {
        if(waitpid(assignMap[i].first,nullptr,0) > 0)
        {
            cout << "wait for: pid="<<assignMap[i].first<<"wait success!"<<"number:"<<i<<'\n';
            close(assignMap[i].second);
        }
    }


    return 0;   
}

管道的特征

1、管道只能用来进行具有血缘关系的进程之间,进行进程间通信,常用于父子通信

2、管道只能单向通信

3、管道自带同步机制(pipe满,writer等;pipe空,reader等)

4、管道是面向字节流的  ---- 先写的字符一定是先被读取的,没有格式边界,需要用户规定读多少字节

5、管道的生命周期   ---- 随进程

2.2 命名管道

继承了匿名管道的所有特性,并且允许两个没有血缘关系的进程进行通信

匿名管道是通过子进程继承父进程实现的,而命名管道是通过不同进程访问同一文件(fifo文件)实现的

//comm
#pragma once

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

#define IPC_PATH "./.fifo"

//读取 serverFifo
#include "comm.h"
using namespace std;
#define NUM 1024

int main()
{
    umask(0);
    if(mkfifo(IPC_PATH,0600) != 0)//成功返回0
    {
        cerr<<"mkfifo error"<<endl;
        return 1;
    }
    int pipeFd = open(IPC_PATH,O_RDONLY);
    if(pipeFd < 0)
    {
        cerr<<"open fifo error"<<endl;
        return 2;
    }

    char buffer[NUM];
    while(true)
    {
        ssize_t s = read(pipeFd,buffer,sizeof(buffer)-1);
        if(s > 0)
        {
            buffer[s] = '\0';
            cout<<"客户端->服务器#"<<buffer<<endl;
        }
        else if(s == 0)
        {
            cout<<"客户端退出了,我也退出吧"<<endl;
            break;
        }
        else
        {
            cout<<"read: "<<strerror(errno)<<endl;
            break;
        }
    }

    close(pipeFd);
    cout<<"服务器端退出"<<endl;
    unlink(IPC_PATH);//删除创建的.fifo文件
    return 0;
}

//写入 clientFifo
#include "comm.h"
using namespace std;
int main()
{
    int pipeFd = open(IPC_PATH,O_WRONLY);
    if(pipeFd < 0)
    {
        cerr<<"open:"<<strerror(errno)<<endl;
        return 1;
    }
#define NUM 1024
    char line[NUM];
    while(true)
    {
        cout<<"请输入你的信息#";
        fflush(stdout);
        memset(line, 0, sizeof(line));
        if(fgets(line,sizeof(line),stdin) != nullptr)
        {
            line[strlen(line)-1] = '\0'; //去掉输入时的回车
            write(pipeFd,line,strlen(line));
        }
        else{
            break;
        }
    }
    close(pipeFd);
    cout<<"客户端退出"<<endl;
    return 0;

}

3.共享内存

创建共享内存 shmget

key可以自己设置人任意值,只要保证它的唯一性就好,但是一般都是用ftok()来进行设置

删除共享内存 shmctl

shmctl(shmid, IPC_RMID,nullptr)

使用共享内存 shmat 成功返回共享内存的地址,失败返回-1

取消关联 shmdt                                                                                                                           

ipc 命令:

ipcs -m : 显示当前用户创建的共享内存

ipcrm -m + shmid: 删除共享内存

共享内存属于双方的用户空间(堆、栈),可以直接访问共享内存,不用使用系统接口,但是没有任何访问控制,不安全

用管道来对共享内存增加访问控制

//comm
#pragma once
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cerrno>
#include<cassert>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include"Log.hpp"
#include<sys/stat.h>
#include<fcntl.h>
#include<string>
#include<unistd.h>
#include<sys/shm.h>

#define PATH_NAME "/home/ldx/code/2022_11_10"
#define PROJ_ID 0x14
#define MEM_SIZE 4096
#define  FIFO_FILE ".fifo"

key_t CreateKey()
{
    key_t key = ftok(PATH_NAME,PROJ_ID);
    if(key < 0)
    {
        std::cerr << "ftok:" << strerror(errno) << std::endl;
        exit(1);
    }
    return key;
}
void CreateFifo()
{
    umask(0);
    if(mkfifo(FIFO_FILE,0666) < 0) //创造管道文件 “.fifo”
    {
        Log() << strerror(errno) << '\n';
        exit(2);
    }
}

#define READER O_RDONLY
#define WRITER O_WRONLY

int Open(const std::string& filename,int flags)
{
    return open(filename.c_str(),flags);
}

int Wait(int fd)
{
    uint32_t values = 0;
    ssize_t s = read(fd, &values, sizeof(values));
    return s;
}

int Signal(int fd)
{
    uint32_t cmd = 1;
    write(fd, &cmd, sizeof(cmd));
}

int Close(int fd, const std::string filename)
{
    close(fd);
    unlink(filename.c_str());
}

//Log
#pragma once

#include<iostream>
#include<ctime>
std::ostream& Log()
{
    std::cout<<"for debuge |" <<"timestamp"<<(uint64_t)time(nullptr)<<"|";
    return std::cout;
}

//Cli
#include "Comm.hpp"
#include "Log.hpp"

#include <cstdio>
#include <unistd.h>

using namespace std;
// 充当使用共享内存的角色
int main()
{
    int fd = Open(FIFO_FILE, WRITER);
    // 创建相同的key值
    key_t key = CreateKey();
    Log() << "key: " << key << "\n";

    // 获取共享内存
    int shmid = shmget(key, MEM_SIZE, IPC_CREAT);
    if (shmid < 0)
    {
        Log() << "shmget: " << strerror(errno) << "\n";
        return 2;
    }

    // 挂接
    char *str = (char*)shmat(shmid, nullptr, 0);

    while(true)
    {
        printf("Please Enter# ");
        fflush(stdout);
        ssize_t s = read(0, str, MEM_SIZE);
        if(s > 0)
        {
            str[s] = '\0';
        }
        Signal(fd);//标记写完了
    }

    // 去关联
    shmdt(str);

    return 0;
}

//Ser
#include "Comm.hpp"
#include "Log.hpp"

#include <unistd.h>

using namespace std;

// 我想创建全新的共享内存
const int flags = IPC_CREAT | IPC_EXCL;

// 充当使用共享内存的角色
int main()
{
    CreateFifo();
    int fd = Open(FIFO_FILE, READER);
    assert(fd >= 0);


    key_t key = CreateKey();
    Log() << "key: " << key << "\n";

    Log() << "create share memory begin\n";
    int shmid = shmget(key, MEM_SIZE, flags | 0666);
    if (shmid < 0)
    {
        Log() << "shmget: " << strerror(errno) << "\n";
        return 2;
    }
    Log() << "create shm success, shmid: " << shmid << "\n";

    // 1. 将共享内存和自己的进程产生关联attach
    char *str = (char *)shmat(shmid, nullptr, 0);
    Log() << "attach shm : " << shmid << " success\n";

    // 用它
    while(true)
    {
        // 让读端进行等待
        if(Wait(fd) <= 0) break; 
        printf("%s\n", str);
        sleep(1);
    }

    // 2. 去关联
    shmdt(str);
    Log() << "detach shm : " << shmid << " success\n";
    // sleep(5);

    // 删它
    shmctl(shmid, IPC_RMID, nullptr);

    Log() << "delete shm : " << shmid << " success\n";

    Close(fd, FIFO_FILE);
    // sleep(5);
    return 0;
}

可以被多个程序看到的资源叫临界资源,访问临界资源的代码叫临界区 

信号量就是一个计数器,这个计算器对应的操作是原子的

信号量对应的操作:

sem:-- ,申请资源:P

sem:++,释放资源,V

  • 10
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值