10.进程间通信(四)
一.上集回顾
建议先学上篇博客,再向下学习,上篇博客的链接如下:
https://blog.csdn.net/weixin_60668256/article/details/154407557?fromshare=blogdetail&sharetype=blogdetail&sharerId=154407557&sharerefer=PC&sharesource=weixin_60668256&sharefrom=from_link
二.共享内存
1.回顾
共享内存,就是操作系统在物理内存开辟的一块空间,根据页表的映射,到我们对应的进程地址空间的共享区,所以操作系统提供的资源,能被所有的进程看到
a.ftok()生成唯一key值

b.创建并获取共享内存

c.释放对应共享内存

2.将共享内存挂接到自己的地址空间
我们前期将共享内存的创建和获取已经做好了
#include <iostream>
#include "Comm.hpp"int main()
{//1.创建keykey_t k = ::ftok(gpath.c_str(),gprojId);if(k < 0){std::cerr << "ftok error" << std::endl;return 1;}std::cout << "k : " << k << std::endl;//2.创建共享内存 && 获取int shmid = ::shmget(k,gshmsize,IPC_CREAT | IPC_EXCL);if(shmid < 0){std::cerr << "shmget error" << std::endl;return 2;}std::cout << "shmid : " << shmid << std::endl;//3.//n.删除共享内存shmctl(shmid,IPC_RMID,nullptr);return 0;
}



返回值就是该地址(和malloc是一样的)
#include <iostream>
#include "Comm.hpp"int main()
{//1.创建keykey_t k = ::ftok(gpath.c_str(),gprojId);if(k < 0){std::cerr << "ftok error" << std::endl;return 1;}std::cout << "k : " << k << std::endl;//2.创建共享内存 && 获取int shmid = ::shmget(k,gshmsize,IPC_CREAT | IPC_EXCL);if(shmid < 0){std::cerr << "shmget error" << std::endl;return 2;}std::cout << "shmid : " << shmid << std::endl;sleep(5);//3.共享内存挂接到自己的地址空间中void* ret = shmat(shmid,nullptr,0);std::cout << "attach done: " << (long long)ret << std::endl;sleep(5);//n.删除共享内存shmctl(shmid,IPC_RMID,nullptr);std::cout << "deete shm done" << std::endl;sleep(5);return 0;
}

所以为啥会失败呢?

共享内存也有权限!所以我们要对其进行权限的设置



3.去关联

#include <iostream>
#include "Comm.hpp"int main()
{//1.创建keykey_t k = ::ftok(gpath.c_str(),gprojId);if(k < 0){std::cerr << "ftok error" << std::endl;return 1;}std::cout << "k : " << k << std::endl;//2.创建共享内存 && 获取// 注意: 共享内存也有权限!int shmid = ::shmget(k,gshmsize,IPC_CREAT | IPC_EXCL | gmode);if(shmid < 0){std::cerr << "shmget error" << std::endl;return 2;}std::cout << "shmid : " << shmid << std::endl;sleep(5);//3.共享内存挂接到自己的地址空间中void* ret = shmat(shmid,nullptr,0);std::cout << "attach done: " << (long long)ret << std::endl;sleep(5);::shmdt(ret);std::cout << "detach done..." << std::endl;//n.删除共享内存shmctl(shmid,IPC_RMID,nullptr);std::cout << "deete shm done" << std::endl;sleep(5);return 0;
}

目前,我们还只是进行我们对应的创建共享内存,并进行挂接,还没有进行通信操作
操作系统,申请空间是按照块为单位的: 4KB(4096)
但是如果我们申请4097个字节呢?

操作系统给你分配的是4096*2,但是你要的是4097,就给你显示4097(用户要啥就给你啥)(建议使用4096的整数倍)
4.代码拆分
"ShareMemory.hpp"#pragma once#include <iostream>
#include <string>
#include <cstdio>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>const std::string gpath = "/home/ltw";
int gprojId = 0x6666;
//操作系统,申请空间是按照块为单位的: 4KB
int gshmsize = 4096;
mode_t gmode = 0600;std::string ToHex(key_t k)
{char buffer[64];snprintf(buffer,sizeof(buffer),"0x%x",k);return buffer;
}class ShareMemory
{
private:void CreateShmHelper(int shmflg){_key = ::ftok(gpath.c_str(),gprojId);if(_key < 0){std::cerr << "ftok error" << std::endl;return;}_shmid = ::shmget(_key,gshmsize,shmflg);if(_shmid < 0){std::cerr << "shmget error" << std::endl;return;}}
public:ShareMemory():_shmid(-1),_key(0),_addr(nullptr){}~ShareMemory(){}void CreateShm(){if(_shmid == -1){CreateShmHelper(IPC_CREAT | IPC_EXCL | gmode);}}void GetShm(){CreateShmHelper(IPC_CREAT);}void AttachShm(int shmid){_addr = shmat(shmid,nullptr,0);if((long long)_addr == -1){std::cout << "attach error" << std::endl;}}void DetachShm(){if(_addr != nullptr){::shmdt(_addr);}}void DeleteShm(){shmctl(_shmid,IPC_RMID,nullptr);}void ShmMeta(){}
private:int _shmid;key_t _key;void* _addr;
};ShareMemory shm;
"Server.cc"#include <iostream>
#include "ShareMemory.hpp"int main()
{shm.CreateShm();shm.AttachShm();shm.DetachShm();shm.DeleteShm();return 0;
}
"Client.cc"#include <iostream>
#include "ShareMemory.hpp"int main()
{shm.GetShm();shm.AttachShm();shm.DetachShm();return 0;
}
我们创建了一个ShareMemory的类,让其来对我们进行管理共享内存

5.共享内存进行通信
void* GetAddr(){return _addr;}
我们对外提供一个获取虚拟地址的接口
让Server和Client获取我们对应的虚拟地址的接口

但是共享内存是一样的
"Server.cc"#include <iostream>
#include "ShareMemory.hpp"int main()
{shm.CreateShm();shm.AttachShm();char* strinfo = (char*)shm.GetAddr();while(true){printf("%s\n",strinfo);sleep(1);}shm.DetachShm();shm.DeleteShm();return 0;
}
"Client.cc"#include <iostream>
#include "ShareMemory.hpp"int main()
{shm.GetShm();shm.AttachShm();char* strinfo = (char*)shm.GetAddr();char ch = 'A';while(ch <= 'Z'){sleep(3);strinfo[ch - 'A'] = ch;ch++;}shm.DetachShm();return 0;
}

为啥我们这里写入,为啥没有用系统调用呢?(直接用printf等)
当我们将共享内存挂接到用户空间,我们能直接进行使用
6.共享内存的特点
![]()

![]()
![]()


管道有自己的保护机制,而共享内存是没有对应的保护机制的
所以,我们在写入数据的时候,安全性就没有办法进行保障
三.共享内存测试
先构建一个结构体(这个就是我们要发送的数据):
struct data
{char status[32];char lasttime[48];char image[1024];
};
设计一个获取时间的函数
#pragma once#include <iostream>
#include <string.h>
#include <time.h>std::string GetCurrTime()
{time_t t = time(nullptr);struct tm* curr = ::localtime(&t);char currtime[32];snprintf(currtime,sizeof(currtime),"%d-%d-%d %d:%d:%d",curr->tm_year + 1900,curr->tm_mon + 1,curr->tm_mday,curr->tm_hour,curr->tm_min,curr->tm_sec);return currtime;
}

"Client.cc"#include <iostream>
#include "ShareMemory.hpp"
#include "test.hpp"
#include <string.h>
#include "Time.hpp"int main()
{shm.GetShm();shm.AttachShm();struct data* image = (struct data*)shm.GetAddr();char ch = 'A';while(ch <= 'Z'){strcpy(image->status,"最新");strcpy(image->lasttime,GetCurrTime().c_str());strcpy(image->image,"xxxxxx");sleep(3);}shm.DetachShm();return 0;
}
"Server.cc"#include <iostream>
#include <string.h>
#include "ShareMemory.hpp"
#include "test.hpp"
#include "Time.hpp"int main()
{shm.CreateShm();shm.AttachShm();struct data* image = (struct data*)shm.GetAddr();while(true){printf("status: %s\n",image->status);printf("lasttime: %s\n",image->lasttime);printf("image: %s\n",image->image);strcpy(image->status,"过期");sleep(5);}shm.DetachShm();shm.DeleteShm();return 0;
}
这里我们可以进行传输结构化字段
但是这个目前还不是安全的,因为我们的并没有加保护,可能消息还没有写完,就被读走了

这里我们用管道传输数据时的拷贝是非常小的,主要还是通过我们的共享内存来进行我们对应的数据交互
不能通过标记位的方式进行修改,因为标记位本质上也是共享资源(也应该被保护)
我们基于fifo进行增加适当的保护
"Fifo.hpp"#pragma once#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>// 模拟进程间同步的过程!const std::string gpipeFile = "./fifo";
const mode_t gfifomode = 0600;
const int gdefultfd = -1;
const int gsize = 1024;
const int gForRead = O_RDONLY;
const int gForWrite = O_WRONLY;class Fifo
{
private:void OpenFifo(int flag){// 如果读端打开文件时,写端还没打开,读端对用的open就会阻塞_fd = ::open(gpipeFile.c_str(), flag);if (_fd < 0){std::cerr << "open error" << std::endl;}}public:Fifo() : _fd(-1){umask(0);int n = ::mkfifo(gpipeFile.c_str(), gfifomode);if (n < 0){// std::cerr << "mkfifo error" << std::endl;return;}std::cout << "mkfifo success" << std::endl;}bool OpenPipeForWrite(){OpenFifo(gForWrite);if (_fd < 0)return false;return true;}bool OpenPipeForRead(){OpenFifo(gForRead);if (_fd < 0)return false;return true;}int Wait(){int code = 0;ssize_t n = ::read(_fd, &code, sizeof(code));if (n == sizeof(code)){return 0;}else if (n == 0){return 1;}else{return 2;}}void Signal(){int code = 1;::write(_fd, &code, sizeof(code));}~Fifo(){if (_fd >= 0)::close(_fd);int n = ::unlink(gpipeFile.c_str());if (n < 0){std::cerr << "unlink error" << std::endl;return;}std::cout << "unlink success" << std::endl;}private:int _fd;
};Fifo gpipe;

我们现在就有一个Fifo的类了

#include <iostream>
#include "ShareMemory.hpp"
#include "test.hpp"
#include <string.h>
#include "Time.hpp"
#include "Fifo.hpp"int main()
{shm.GetShm();shm.AttachShm();gpipe.OpenPipeForWrite();struct data* image = (struct data*)shm.GetAddr();char ch = 'A';while(ch <= 'Z'){strcpy(image->status,"最新");strcpy(image->lasttime,GetCurrTime().c_str());strcpy(image->image,"xxxxxx");gpipe.Signal();sleep(3);}shm.DetachShm();return 0;
}

#include <iostream>
#include <string.h>
#include "ShareMemory.hpp"
#include "test.hpp"
#include "Time.hpp"
#include "Fifo.hpp"int main()
{shm.CreateShm();shm.AttachShm();gpipe.OpenPipeForRead();struct data* image = (struct data*)shm.GetAddr();while(true){gpipe.Wait();printf("status: %s\n",image->status);printf("lasttime: %s\n",image->lasttime);printf("image: %s\n",image->image);strcpy(image->status,"过期");}shm.DetachShm();shm.DeleteShm();return 0;
}

这样我们就用管道,在模拟进程同步
四.消息队列


A和B都可以向队列里面发生消息,我们用type来标识是A进程发的还是B进程发送的
![]()
