共享内存
使用流程:
1、向内核申请一块内存->指定大小
2、如果有两个进程,需要通信,使用共享内存完成,先创建出两个进程
–进程A
–进程B
3、进程A和进程B分别和共享内存进程关联
拿到共享内存的地址–首地址
4、两个进程通过首地址对共享内存进行读写操作
5、如果两个进程不再使用共享内存,需要和共享内存断开关联
–进程退出对共享内存没有影响
6、当不再使用共享内存时,需要手动将共享内存销毁
#include <sys/ipc.h> #include <sys/shm.h>
共享内存操作函数
1.创建或打开共享内存,可以创建多块共享内存
int shmget(key_t key, size_t size, int shmflag);
key:记录共享内存在内核中的位置,需要一个>0的整数,不能=0,随便指定一个整数。或者用函数ftok
size:指定的共享内存的大小,若打开已经存在的共享内存,size写0
shmflag:类似open的flag,创建时使用
–IPC_CREAT:创建共享内存,创建时需要给权限-IPC_CREAT | 0664 文件所有者,所有组,其它人读写执行
–IPC_CREAT | O_EXCL:创建时检测是否存在 ,存在返回 -1,不存在返回 0。
返回值:成功返回一个整形数对应共享内存,失败返回-1
创建:int shmid = shmget(100, 4096, IPC_CREAT | 0664);
打开:int shmid = shmget(100, 0, 0);打开不在乎size
2.将当前进程和共享内存关联
void *shmat(int shmid, const void *shmaddr, int shmflag);
参数:
shmid:通过id访问共享内存
shmaddr:共享内存在内核中的位置一般不知道置NULL
shmflg:关联成功之后对共享内存的操作权限
–SHM_RDONLY:只读
–0:读写
返回值:成功返回共享内存的首地址,失败返回(void *)-1
函数调用:void ptr = shmat(shmid, NULL, 0);
写内存:memcpy(ptr, “xxxx”, len);
读内存:printf(“%s”, (char)ptr);
3、将共享内存和当前进程分离
void *shmdt(const void *shmaddr)//传入参数
参数:共享内存的起始地址, shmat()返回值
返回值:成功0失败-1
4、共享内存的删除
//fcntl, setsockopt getsockopt
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:
shmid:内存标识
cmd:对共享内存的操作。IPC_STAT:获取共享内存状态 IPC_SET:设置共享内存状态 IPC_RMID:标记共享内存要被销毁
buf:为第二个参数服务,把共享内存的状态读出来放到buf里面,cmd=IPC_STAT获取共享内存cmd=IPC_SET自定义共享内存的状态,设置到内核的共享内存中,cmd=IPC_RMID,第三个参数没有用指定为NULL
<sys/shm.h>
struct shmid_ds {
struct ipc_perm shm_perm; /* Ownership and permissions */权限
size_t shm_segsz; /* Size of segment (bytes) */大小
time_t shm_atime; /* Last attach time */最后一次关联时间
time_t shm_dtime; /* Last detach time */
time_t shm_ctime; /* Last change time */
pid_t shm_cpid; /* PID of creator */
pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */进行关联操作的进程数
shmatt_t shm_nattch; /* No. of current attaches */
...
};
struct ipc_perm {
key_t __key; /* Key supplied to shmget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions + SHM_DEST and
SHM_LOCKED flags */
unsigned short __seq; /* Sequence number */
};
成功0失败-1
shmctl(shmid, IPC_RMID, NULL);
思考:
1、操作系统如何知道一块共享内存被多少进程关联
IPC_STAT shmatt_t shm_nattch; /* No. of current attaches */
2、是不是可以对共享内存进行多次删除
可以,因为shmctl是标记删除,当关联这块共享内存进程个数==0的时候,真正被删除了
共享内存操作命令:
ipcs -a 打印当前系统中所有的进程间通信方式的信息
ipcs -m 打印出使用共享内存进行进程间通信的信息
ipcs -q 使用消息队列进行进程间通信的信息
ipcs -s 使用信号通信信息
ipcrm -M shmkey 移除shmkey创建的共享内存
ipcrm -m shmid 移除shmid标识的共享内存段
ipcrm -Q msgkey 移除用msqkey创建的消息队列
ipcrm -q msqid 移除用msqid标识的消息队列
ipcrm -S semkey 移除用semkey创建的信号
ipcrm -s semid 移除用semid标识的信号
当共享内存被标记删除,并且还有进程关联这块共享内存。共享内存的key0
当key0,没有关联的进程就不能和该共享内存进行关联。只为已经关联的进程服务。
key_t ftok(const char* pathname, int proj_id);
–pathname:对应某个存在的路径或者路径中的对应的文件名-绝对路径
–proj_id:目前只是用了改变量占用的内存的一部分(1个字节)取值范围(0-255)
key_t t = tfork(“/home”, ‘a’);
shmget(t, 0, 0);
shm/mmap的区别
- List itemshm可以直接创建,mmap需要依赖磁盘文件
- shm效率更高,直接对内存操作,mmap需要同步磁盘文件
- 所有的进程操作的是同一块内存,而内存映射区是每个进程都会在自己的虚拟地址空间中有一块独立的内存然后分别映射。
- 进程突然退出,共享内存还在,内存映射区消失了。运行进程的电脑挂了,数据存在共享内存中没有了,内存映射区还在。
- 生命周期,进程退出,内存映射区销毁,共享内存还在,手动删除或者关机。
密钥协商和共享内存
从共享内存中查找密钥方式有两种:密钥id是唯一的,通过客户端id和服务器id查找密钥(属性值唯一)
共享内存对象是类成员,因为很多地方都需要。
主机1
网点0001:业务模块
server:密钥协商
主机2
网点0010:业务模块
client:密钥协商
密钥协商客户端
密钥协商客户端通信流程
-
提供一个能够和用户交互的界面
-
用户选择的密钥协商
-
组织数据,初始化
struct RequestMsg
cmdtype
= 1clientID
= 读配置文件serverID
=读配置文件
data
=非对称加密的公钥
sign
=data的签名
-
对数据
struct Requestsg
进行序列化->字符串protobuf
对应的类
-
数据发送给服务器
-
套接字通信的类封装完成-
TcpSocket
-
创建一个通信对象
-
连接服务器,服务器IP,端口==》来自于配置文件
-
给服务器发送序列化之后的数据
-
等待服务器回复数据,接收数据(默认是阻塞)
-
接收到了服务器回复的数据==》反序列化
- 用封装的序列化的类可以完成反序列化数据,得到原始数据
-
得到了原始数据
- 将对此加密的密钥进行解析—这是公钥加密的数据
- 通过私钥解密—原始的对称加密的密钥
- 将对此加密的密钥进行解析—这是公钥加密的数据
-
-
密钥协商服务器
- 被动接收客户端请求,不需要和用户进行交互,脱离终端称为守护进程
- 接收客户端请求,处理->给客户端回复数据
- 请求的处理状态,处理成功还是失败
- 处理完成后具体的数据,针对业务逻辑处理得到的
- 接收客户端请求,处理->给客户端回复数据
//服务器给客户端回复的数据
struct RespondMsg{
bool status;//客户端请求的处理状态
int seckeyID;
string clientID;
string sercerID;
string data;//实际的业务数据
};
- 服务器进行密钥协商通信流程
- 启动服务器并设置监听
服务端port
= 配置文件中读
- 服务器必须能接收多客户端连接
- 多线程I/O多路转接
- 成功和客户端建立了连接–等待客户端请求的数据
- 收到客户端请求数据–解析
- 序列化之后的数据
- 数据反序列化–结构体
- 序列化之后的数据
- 根据
cmdType
判断客户端想要干什么 - 用户请求密钥协商
- 验证客户端的身份
clientID, serverID
验证是不是有效IDsign
==校验这个签名
- 真正的密钥协商
- 生成一个随机字符串
- 对称加密的密钥
- 使用得到的公钥进行加密==密文
- 初始化回复的数据
struct RespondMsg
status = true/false
data = 密文
clientID,serverID
=通过服务器进行初始化seckeyID
=生成新密钥的时候才需要初始化这个变量
- 序列化要发送的数据
struct RespondMsg
–字符串 - 通过网络通信进行发送到客户端
- 生成一个随机字符串
- 验证客户端的身份
- 启动服务器并设置监听