Openssl数据安全传输平台007:共享内存代码的设计与实现

0. 代码仓库

https://github.com/Chufeng-Jiang/OpenSSL_Secure_Data_Transmission_Platform

1. 共享内存基础知识-使用流程

  1. 向内核申请一块内存 -> 指定大小
  2. 如果有两个进程, 需要通信, 可以使用这块共享内存来完成, 先创建出这两个进程
    • 进程A
    • 进程B
  3. 进程A和进程B分别和共享内存进行关联
    • 拿到共享内存的地址 -> 首地址
  4. 两个进程可以通过这个首地址对共享内存进行读/写操作
  5. 如果这个进程不再使用这块共享内存, 需要和共享内存断开关联
    • 进程退出, 对共享内存是没有任何影响的
  6. 当不再使用共享内存的时候, 需要将共享内存销毁

案例代码:

https://github.com/Chufeng-Jiang/Linux-System-Programming/tree/main/0110%20Shared%20Memory

在这里插入图片描述

2. API解析

2.1 创建或打开一块共享内存区

// 创建共享内存
// 共享内存已经存在, 打开共享内存
// 可以创建多块共享内存

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);  

2.2 将当前进程和共享内存关联到一起

// 进程和共享内存产生关系
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);

2.3 将共享内存和当前进程分离

// 进程和共享内存分离 -> 二者就没有关系了
int shmdt(const void *shmaddr);
    参数: 共享内存的起始地址, shmat()返回值
    返回值:
        - 成功: 0
        - 失败: -1

2.4 共享内存操作+删除共享内存

// 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);

3. 思考问题

  • 问题1: 操作系统如何知道一块共享内存被多少进程关联?

    • 共享内存维护了一个结构体struct shmid_ds这个结构体中有一个成员shm_nattch
    • shm_nattch中记录了关联的进程的个数
  • 问题2: 是不是可以对共享内存进行多次删除 -> 多次调用shmctl

    • 可以多次操作
  • 因为shmctl函数是标记删除共享内存, 部署直接删除

    • 什么时候被真正删除了?
    • 当关联这块共享内存进程个数 == 0 的时候, 真正被删除了
      • shm_nattch== 0

3. ftok函数

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,与程序输出的结果一致。

4. 本项目共享内存的设计与实现

4.1 设计思路

  • shm
    在这里插入图片描述

  • 秘钥协商服务器和协商客户端部署在同一台主机?
    - 实际的应用场景是部署在不同的主机上

  • 场景:

  • 服务器端发送消息 -> 传智共屏软件 -> 服务器 -> 独立进程
    • 加密使用对称加密的秘钥
    • 部署秘钥协商的服务器端程序
      • 独立进程
    • 最终的需求:
      • 秘钥协商生成的秘钥给到传智共屏服务器
        • 这两个进程没关系 -> 没有血缘关系
        • 效率最高: 共享内存(shm)
  • 客户端接收消息 -> 传智共屏软件 -> 客户端 -> 独立进程
    • 解密, 使用对称加密的秘钥
    • 部署秘钥协商的客户端程序
      • 独立进程
    • 最终的需求:
      • 秘钥协商生成的秘钥给到传智共屏客户端
        • 这两个进程没关系 -> 没有血缘关系
        • 效率最高: 共享内存(shm)
  • 服务器和客户端共享内存中的秘钥个数?

    • 服务器端:
      • 秘钥多个, 应服务器需要和多客户端连接
        • 每个客户端对应一个加密的秘钥, 这些秘钥都是不同的
    • 客户端:
      • 需要秘钥1个
        • 一个客户端只需要和一个服务器进行连接

4.2 共享内存中存储的秘钥信息都有什么?

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操作

4.3 共享内存API封装-以本项目为例

//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;
}

在这里插入图片描述

4.4 共享内存的创建和使用-客户端为例

具体代码请查看代码仓库
在这里插入图片描述

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;
}

4.5 共享内存的创建和使用-服务端

// 处理客户端请求
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);
}

4.6 客户端和服务端内存位置

因为客户端和服务端在不同的主机上,所以可以指向不同的内存。

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大大枫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值