https://github.com/Chufeng-Jiang/OpenSSL_Secure_Data_Transmission_Platform
https://github.com/Chufeng-Jiang/Linux-System-Programming/tree/main/0110%20Shared%20Memory
// 创建共享内存
// 共享内存已经存在, 打开共享内存
// 可以创建多块共享内存
int shmget(key_t key, size_t size, int shmflg);
参数:
- key: 通过这个key记录共享内存在内核中的位置, 需要是一个>0的整数, ==0不行
随便指定一个数就可以, 后边会介绍一个函数ftok
- size: 创建共享内存的时候, 指定共享内存的大小
- 如果是打开一个已经存在的共享内存, size写0就可以
- shmflg: 创建共享内存的时候使用, 类似于open函数的flag
- IPC_CREAT: 创建共享内存
- 创建的时候需要给共享内存一个操作权限
- IPC_CREAT | 0664
- IPC_CREAT | IPC_EXCL: 检测共享内存是否存在
- 如果存在函数返回-1
- 不存在, 返回0
返回值:
成功: 创建/打开成功, 得到一个整形数 -> 对应这块共享内存
失败: -1
// 应用
// 1. 创建共享内存
int shmid = shmget(100, 4096, IPC_CREAT | 0664);
int shmid = shmget(200, 4096, IPC_CREAT | 0664);
// 2. 打开共享内存
int shmid = shmget(100, 0, 0);
// 进程和共享内存产生关系
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数:
- shmid: 通过这个参数访问共享内存, shmget()函数的返回值
- shmaddr: 指定共享内存在内核中的位置, 写NULL -> 委托内核区指定
- shmflg: 关联成功之后对共享内存的操作权限
- SHM_RDONLY: 只读
- 0: 读写
返回值:
成功: 共享内存的地址 (起始地址)
失败: (void *) -1
// 函数调用:
void* ptr = shmat(shmid, NULL, 0);
// 写内存
memcpy(ptr, "xxxx", len);
// 读内存
printf("%s", (char*)prt);
// 进程和共享内存分离 -> 二者就没有关系了
int shmdt(const void *shmaddr);
参数: 共享内存的起始地址, shmat()返回值
返回值:
- 成功: 0
- 失败: -1
// fcntl
// setsockopt
// getsockopt
// 对共享内存进程操作
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:
- shmid: 通过这个参数访问共享内存, shmget()函数的返回值
- cmd: 对共享内存的操作
- IPC_STAT: 获取共享内存的状态
- IPC_SET: 设置共享内存状态
- IPC_RMID: 标记共享内存要被销毁
- buf: 为第二个参数服务的
cmd==IPC_STAT: 获取共享内存具体状态信息
cmd==IPC_SET: 自定义共享内存状态, 设置到内核的共享内存中
cmd==IPC_RMID: 这个参数没有用了, 指定为NULL
返回值:
成功: 0
失败: -1
// 删除共享内存
shmctl(shmid, IPC_RMID, NULL);
问题1: 操作系统如何知道一块共享内存被多少进程关联?
struct shmid_ds
这个结构体中有一个成员shm_nattch
shm_nattch
中记录了关联的进程的个数问题2: 是不是可以对共享内存进行多次删除 -> 多次调用shmctl
因为shmctl
函数是标记删除共享内存, 部署直接删除
shm_nattch== 0
ftok函数是IPC中常用的一个函数,它是由Unix系统提供的一个应用程序编程接口(API)。它的作用是根据一个指定的文件名和一个整数,生成一个不重复的键值(key)。
key_t ftok(const char *pathname, int proj_id);
首先根据一个任意存在的pathname绝对路径
提取其所属文件系统的特有信息,包括设备号(stat.st_dev)和inode号(stat.st_ino),其获取方法参见上述程序中的sata结构,然后再结合ftok()函数的proj_id参数,按照如下规则计算key:
key1 = stat.st_ino & 0xffff; // 保留低16位
key2 = stat.st_dev & 0xff; // 保留低8位
key2 << = 16; // 左移16位
key3 = proj_id & 0xff; // 保留低8位
key3 << = 24; // 左移24位
key = key1|key2|key3; // 三者进行或运算
本例中,pathname=’/tmp‘,ftok()函数提取的设备号为0x804,inode号为0x280001,proj_id为0x01,根据以上运算过程,key1=0x01,key2=0x40000,key3=0x1000000,三者求或得到key=0x1040001,与程序输出的结果一致。
shm
秘钥协商服务器和协商客户端部署在同一台主机?
- 实际的应用场景是部署在不同的主机上
场景:
- 服务器端发送消息 -> 传智共屏软件 -> 服务器 -> 独立进程
- 加密使用对称加密的秘钥
部署秘钥协商的服务器
端程序
- 独立进程
- 最终的需求:
- 秘钥协商生成的秘钥给到传智共屏服务器
- 这两个进程没关系 -> 没有血缘关系
- 效率最高: 共享内存(shm)
- 客户端接收消息 -> 传智共屏软件 -> 客户端 -> 独立进程
- 解密, 使用对称加密的秘钥
部署秘钥协商的客户端
程序
- 独立进程
- 最终的需求:
- 秘钥协商生成的秘钥给到传智共屏客户端
- 这两个进程没关系 -> 没有血缘关系
- 效率最高: 共享内存(shm)
服务器和客户端共享内存中的秘钥个数?
struct SecKeyInfo
{
// 初始化
SecKeyInfo()
{
key = string();
clientID = string();
...
status = true;
}
// 对称加密的秘钥
string key;
// 如果鉴别这个秘钥属于谁 -> 查询秘钥的时候根据clientID和serverID进行查询
string clientID;
string serverID;
// 可选 -> 秘钥编号ID
int seckeyID;
// 可选 -> 秘钥状态
bool status; // true:可用, false:不可用
}
SecKeyInfo info;
memset(&info, 0, sizeof(info)); // error, 成员是string不能做memset操作
//shmget
int shmget(key_t key, size_t size, int shmflg);
class BaseShm
{
public:
BaseShm(int key); // 根据key打开共享内存
BaseShm(string path); // 根据string path-> int key 打开共享内存
BaseShm(int key, int size); // 根据key创建共享内存
BaseShm(string path, int size); // 根据string path-> int key 创建共享内存
void* mapshm()
{
m_ptr = shmat(shmid);
return m_ptr;
}
int unmapshm()
{
shmdt(m_ptr);
}
int delshm()
{
shmctl(shmid);
}
private:
int m_shmid; // shmget返回值
void* m_ptr;
}
具体代码请查看代码仓库
class BaseShm
{
public:
// 通过key打开共享内存
BaseShm(int key);
// 通过传递进来的key创建/打开共享内存
BaseShm(int key, int size);
// 通过路径打开共享内存
BaseShm(string name);
// 通过路径创建/打开共享内存
BaseShm(string name, int size);
void* mapShm();
int unmapShm();
int delShm();
~BaseShm();
private:
int getShmID(key_t key, int shmSize, int flag);
private:
int m_shmID;
protected:
void* m_shmAddr = NULL;
};
const char RandX = 'x';
BaseShm::BaseShm(int key)
{
getShmID(key, 0, 0);
}
BaseShm::BaseShm(int key, int size)
{
getShmID(key, size, IPC_CREAT | 0664);
}
BaseShm::BaseShm(string name)
{
key_t key = ftok(name.data(), RandX);
getShmID(key, 0, 0);
}
BaseShm::BaseShm(string name, int size)
{
key_t key = ftok(name.data(), RandX);
// 创建共享内存
getShmID(key, size, IPC_CREAT | 0664);
}
void * BaseShm::mapShm()
{
// 关联当前进程和共享内存
m_shmAddr = shmat(m_shmID, NULL, 0);
if (m_shmAddr == (void*)-1)
{
return NULL;
}
return m_shmAddr;
}
int BaseShm::unmapShm()
{
int ret = shmdt(m_shmAddr);
return ret;
}
int BaseShm::delShm()
{
int ret = shmctl(m_shmID, IPC_RMID, NULL);
return ret;
}
BaseShm::~BaseShm()
{
}
int BaseShm::getShmID(key_t key, int shmSize, int flag)
{
cout << "share memory size: " << shmSize << endl;
m_shmID = shmget(key, shmSize, flag);
if (m_shmID == -1)
{
// 写log日志
cout << "shmget 失败" << endl;
}
return m_shmID;
}
class NodeSecKeyInfo
{
public:
NodeSecKeyInfo() : status(0), seckeyID(0)
{
bzero(clientID, sizeof(clientID));
bzero(serverID, sizeof(serverID));
bzero(seckey, sizeof(seckey));
}
int status; // 秘钥状态: 1可用, 0:不可用
int seckeyID; // 秘钥的编号
char clientID[12]; // 客户端ID, 客户端的标识
char serverID[12]; // 服务器ID, 服务器标识
char seckey[128]; // 对称加密的秘钥
};
class SecKeyShm : public BaseShm
{
public:
// 打开或创建一块共享内存
// 这个操作是在父类中做的
SecKeyShm(int key, int maxNode);
//maxNode节点的个数,即存储多少个秘钥,存储结构体NodeSecKeyInfo的个数
SecKeyShm(string pathName, int maxNode);
~SecKeyShm();
void shmInit();
int shmWrite(NodeSecKeyInfo* pNodeInfo);
NodeSecKeyInfo shmRead(string clientID, string serverID);
private:
int m_maxNode;
};
SecKeyShm::SecKeyShm(int key, int maxNode)
: BaseShm(key, maxNode * sizeof(NodeSecKeyInfo))
, m_maxNode(maxNode)
{
}
SecKeyShm::SecKeyShm(string pathName, int maxNode)
: BaseShm(pathName, maxNode * sizeof(NodeSecKeyInfo))
, m_maxNode(maxNode)
{
}
SecKeyShm::~SecKeyShm()
{
}
void SecKeyShm::shmInit()
{
//m_shmAddr在BaseShm.cpp中,是关联当前进程和共享内存时拿到的共享内存的起始地址
if (m_shmAddr != NULL)
{ //共享内存的大小--->参数三:节点个数*节点大小
memset(m_shmAddr, 0, m_maxNode * sizeof(NodeSecKeyInfo));
}
}
int SecKeyShm::shmWrite(NodeSecKeyInfo * pNodeInfo)
{
int ret = -1;
// 关联共享内存
NodeSecKeyInfo* pAddr = static_cast<NodeSecKeyInfo*>(mapShm());
if (pAddr == NULL)
{
return ret;
}
// 判断传入的网点密钥是否已经存在
NodeSecKeyInfo *pNode = NULL;
for (int i = 0; i < m_maxNode; i++)
{
// pNode依次指向每个节点的首地址
pNode = pAddr + i;
cout << i << endl;
cout << "clientID 比较: " << pNode->clientID << ", " << pNodeInfo->clientID << endl;
cout << "serverID 比较: " << pNode->serverID << ", " << pNodeInfo->serverID << endl;
cout << endl;
if (strcmp(pNode->clientID, pNodeInfo->clientID) == 0 &&
strcmp(pNode->serverID, pNodeInfo->serverID) == 0)
{
// 如果找到了该网点秘钥已经存在, 使用新秘钥覆盖旧的值
memcpy(pNode, pNodeInfo, sizeof(NodeSecKeyInfo));
unmapShm();
cout << "写数据成功: 原数据被覆盖!" << endl;
return 0;
}
}
// 若没有找到对应的信息, 找一个空节点将秘钥信息写入
int i = 0;
NodeSecKeyInfo tmpNodeInfo; //空结点
for (i = 0; i < m_maxNode; i++)
{
pNode = pAddr + i;
if (memcmp(&tmpNodeInfo, pNode, sizeof(NodeSecKeyInfo)) == 0)
{
ret = 0;
memcpy(pNode, pNodeInfo, sizeof(NodeSecKeyInfo));
cout << "写数据成功: 在新的节点上添加数据!" << endl;
break;
}
}
if (i == m_maxNode)
{
ret = -1;
}
unmapShm();
return ret;
}
NodeSecKeyInfo SecKeyShm::shmRead(string clientID, string serverID)
{
int ret = 0;
// 关联共享内存
NodeSecKeyInfo *pAddr = NULL;
pAddr = static_cast<NodeSecKeyInfo*>(mapShm());
if (pAddr == NULL)
{
cout << "共享内存关联失败..." << endl;
return NodeSecKeyInfo();
}
cout << "共享内存关联成功..." << endl;
//遍历网点信息
int i = 0;
NodeSecKeyInfo info;
NodeSecKeyInfo *pNode = NULL;
// 通过clientID和serverID查找节点
cout << "maxNode: " << m_maxNode << endl;
for (i = 0; i < m_maxNode; i++)
{
pNode = pAddr + i;
cout << i << endl;
cout << "clientID 比较: " << pNode->clientID << ", " << clientID.data() << endl;
cout << "serverID 比较: " << pNode->serverID << ", " << serverID.data() << endl;
if (strcmp(pNode->clientID, clientID.data()) == 0 &&
strcmp(pNode->serverID, serverID.data()) == 0)
{
// 找到的节点信息, 拷贝到传出参数
info = *pNode;
cout << "++++++++++++++" << endl;
cout << info.clientID << " , " << info.serverID << ", "
<< info.seckeyID << ", " << info.status << ", "
<< info.seckey << endl;
cout << "===============" << endl;
break;
}
}
unmapShm();
return info;
}
ClientOP::ClientOP(string jsonFile)
{
// 解析json文件, 读文件 -> Value
ifstream ifs(jsonFile);
Reader r;
Value root;
r.parse(ifs, root);
// 将root中的键值对value值取出
m_info.ServerID = root["ServerID"].asString();
m_info.ClientID = root["ClientID"].asString();
m_info.ip = root["ServerIP"].asString();
m_info.port = root["Port"].asInt();
// 实例化共享内存对象
// 从配置文件中读 key/pathname
string shmKey = root["ShmKey"].asString();
int maxNode = root["ShmMaxNode"].asInt();
// 客户端存储的秘钥只有一个
m_shm = new SecKeyShm(shmKey, maxNode);
}
ClientOP::~ClientOP()
{
delete m_shm;
}
// 秘钥写入共享内存中
NodeSecKeyInfo info;
strcpy(info.clientID, m_info.ClientID.data());
strcpy(info.serverID, m_info.ServerID.data());
strcpy(info.seckey, key.data());
info.seckeyID = resData->seckeyid();
info.status = true;
m_shm->shmWrite(&info);
delete factory;
delete c;
// 这是一个短连接, 通信完成, 断开连接
tcp->disConnect();
delete tcp;
return true;
}
// 处理客户端请求
class ServerOP
{
public:
enum KeyLen {Len16=16, Len24=24, Len32=32};
ServerOP(string json);
void startServer();
static void* working(void* arg);
friend void* workHard(void* arg);
string seckeyAgree(RequestMsg* reqMsg);
~ServerOP();
private:
string getRandKey(KeyLen len);
private:
string m_serverID; // 当前服务器的ID
string m_dbUser;
string m_dbPwd;
string m_dbConnStr;
unsigned short m_port;
map<pthread_t, TcpSocket*> m_list;
TcpServer *m_server = NULL;
OCCIOP m_occi;
// 创建共享内存对象
SecKeyShm* m_shm;
};
ServerOP::ServerOP(string json)
{
// 解析json文件, 读文件 -> Value
ifstream ifs(json);
Reader r;
Value root;
r.parse(ifs, root);
// 将root中的键值对value值取出
m_port = root["Port"].asInt();
m_serverID = root["ServerID"].asString();
// 数据库相关的信息
m_dbUser = root["UserDB"].asString();
m_dbPwd = root["PwdDB"].asString();
m_dbConnStr = root["ConnStrDB"].asString();
// 实例化一个连接oracle数据的对象
m_occi.connectDB(m_dbUser, m_dbPwd, m_dbConnStr);
// 实例化共享内存对象
// 从配置文件中读 key/pathname
string shmKey = root["ShmKey"].asString();
int maxNode = root["ShmMaxNode"].asInt();
// 客户端存储的秘钥只有一个
m_shm = new SecKeyShm(shmKey, maxNode);
}
// 将生成的新秘钥写入到数据库中 -> 操作 SECKEYINFO
NodeSecKeyInfo node;
strcpy(node.clientID, reqMsg->clientid().data());
strcpy(node.serverID, reqMsg->serverid().data());
strcpy(node.seckey, key.data());
node.seckeyID = m_occi.getKeyID(); // 秘钥的ID
info.seckeyID = node.seckeyID;
node.status = 1;
// 初始化node变量
bool bl = m_occi.writeSecKey(&node);
if(bl)
{
// 成功
m_occi.updataKeyID(node.seckeyID + 1);
// 写共享内存
m_shm->shmWrite(&node);
}
因为客户端和服务端在不同的主机上,所以可以指向不同的内存。