
于此止?:个人主页
❄️个人专栏传送门:《Linux篇》 《QT百日筑基篇》 《算法篇》
《C++与STL剖析篇》 《数据结构篇》 《Python基础篇》
⭐️纵有狂风拔地起,我亦乘风破万里⭐️

System V 共享内存深度解析
目录/索引:
目录
System V 共享内存深度解析
目录/索引:
一、共享内存的背景:
二、System V共享内存原理
原理图
三、系统调用函数的详解
函数的种类
函数的深度解析
shmget:创建一块共享内存
ftok: 生成一个共享内存的标识符(key值)
shmat:将物理空间的共享区在虚拟空间映射一份
扩展:SHMLAB
shmdt:将当前共享内存和当前进程进行脱离。
shmctl:控制共享内存
四、共享内存为什么是最快的方式
五、创建system V 共享内存
六、删除对应的共享内存
七、代码示例
利用共享内存实现对应的两个进程之间进行通信。
一、共享内存的背景:
System V 共享内存是 Linux 下性能最优的进程间通信(IPC)方式,被称为数字世界最快 IPC 通道。不同于管道、消息队列需要在内核缓冲区与用户空间来回拷贝数据,共享内存直接在物理内存开辟一块公共内存区域,多个进程将同一块内存映射至各自虚拟地址空间,进程读写数据仅需操作虚拟地址,不存在数据复制开销。
整套 API 由ftok、shmget、shmat、shmdt、shmctl构成:ftok 生成唯一键值,shmget 创建或获取共享内存段,shmat 完成地址映射,shmdt 解除映射,shmctl 控制删除、获取状态。
共享内存本身不自带同步机制,多进程并发读写极易引发竞态问题,工程上常搭配信号量实现互斥访问。同时需要注意,进程退出不会自动销毁共享内存段,内核会持续保留资源,忘记清理极易造成 IPC 内存泄漏,开发结束必须主动调用接口释放。共享内存的生命周期是随内核的, 我们不手动释放,即便是进程退了共享内存依旧存在。虽然不带有对应的同步机制,但是我们可以添加一个管道来实现对应的同步机制。(后面进行试验)。
共享内存是最快的IPC形式, 一旦这样的内存映射到共享它的进程的地址空间,这些数据传递不再涉及到内核,换句话说是进程不再执行进入内核的系统调用来传递彼此的数据。
IPC(进程间通信)的本质是让不同的进程看到同一份资源。
二、System V共享内存原理
我们像使用shmget函数创建一块空间这个空间是在我们的物理空间上的, 然后我们要使用shmat用来在虚拟空间进行映射。然后填充我们的页表,建立物理空间和虚拟空间的映射,这样我们就能拿到对应的虚拟地址去访问到我们对应的物理地址, 需要在两个进程之间同时映射,虚拟地址有可能不一样,但是映射到的物理地址一定是一样的。然后我们就实现了对应的不同进程查看同一份Cpu的资源了。然后我们要将它进行销毁,我们物理内存的这块共享空间一定是有一个结构体进行管理的,如果没有不方便我们后续的寻找,我们对应的内部肯定还有对应的引用技术,然后我们需要shmdt将我们的内存段的共享内存与我们当前进行脱离。然后引用计数为0了我们进行对应的释放就可以了,shmctl。
原理图

三、系统调用函数的详解
我们在剖析对应的函数之前我们要先看一有几个函数,这几个函数是干啥的,下面给出一张表来进行查看我们对应函数的功能与使用。
函数的种类
| ftok | 根据路径 + 项目 ID 生成IPC 键值 (key_t),用于进程间约定同一个 IPC 对象 key | #include <sys/ipc.h>#include <sys/types.h> | pathname: 存在的文件路径proj_id:0~255 整数 | 成功:key_t 键失败:-1 | 1. 同文件 + 同 id 得到同一个 key2. 文件不要删,文件 inode 变化 key 会变3. 仅用来生成 key,不是创建共享内存 |
| shmget | 创建 / 获取共享内存段,得到共享内存标识符 shmid | #include <sys/ipc.h>#include <sys/shm.h> | key:ftok 生成的 keysize: 共享内存大小 (字节)shmflg: 权限 + 标志 (IPC_CREAT/IPC_EXCL) | 成功:shmid(共享内存 ID)失败:-1 | IPC_CREAT不存在则创建;IPC_CREAT|IPC_EXCL不存在才创建,已存在报错;权限如0666 |
| shmat | 挂载(映射)共享内存到进程虚拟地址空间 | #include <sys/shm.h> | shmid:shmget 返回 idshmaddr: 指定映射地址,一般填NULL由内核分配shmflg:SHM_RDONLY 只读等 | 成功:共享内存起始虚拟地址 (void*)失败:(void*)-1 | 挂载后进程可直接指针读写;无同步机制,需要信号量 / 消息队列配合 |
| shmdt | 卸载(脱离)共享内存,解除地址映射 | #include <sys/shm.h> | shmaddr:shmat 返回的映射地址 | 成功:0失败:-1 | 只是当前进程不再访问这块内存;不会删除共享内存段,内核仍保留 |
| shmctl | 共享内存控制:删除、获取 / 设置属性 | #include <sys/shm.h> | shmid: 共享内存 IDcmd: 命令buf:shmid_ds 结构体指针 | 成功:0(部分命令返回信息)失败:-1 | 常用 cmd:• IPC_RMID:标记删除,所有进程 detach 后真正销毁• IPC_STAT:获取共享内存信息• IPC_SET:修改权限 |
函数的深度解析
shmget:创建一块共享内存
我们查看一对应的官方文档:
man 2 shmget

函数原型:
int shmget(key_t key, size_t size, int shmflg);
参数的解析:
key:
我们的第一个参数是一个key,这个参数需要我们使用函数调用来进行获取。不是内核形成的是我们os传入的。
key的意义: 我们怎么评估共享内存,存不存在呢?。
我们怎么保证两个进程看到的是同一份资源呢?
这两者key都能给我们进行提供。
我们在进行通信的时候,我们需要一个key,如果我们通过手动来设置这个key的话,我们没有办法直接给另一个进程。如果我们能给的话我们进程本身就能进行通信, 所以这里我们需要有一个约定我们之间约定一个key,下一次我们都用这个key来打开我们对应的这块共享内存。

size:
我们设置这个共享内存的大小, 我们一般设置4098,一般是4kb。
shmflg:
我们这个参数还可以加入对应的权限值。
输出型参数。我们也是传入对应的标记位。
有九个权限标志位组成,我们的用法和创建文件的使用open是一样的。
IPC_CREAT: 代表我们有这块共享内存就打开, 没有就新建。
IPC_EXCL: 单独使用没意义。
IPC_CREAT | IPC_EXCL: 是代表我们改内存存在就报错, 不存在就新建。能保证我们每次使用的共享内存都是最新的。

返回值:
成功返回一个共享内存段的标识符,失败返回-1 ,错误码被设置。

当然这里我们发现我们需要key来进行创建我们的共享内存,所以我该怎么获得这个key呢???
这里我们想到了我们有一个函数可以获得对应的key。
ftok: 生成一个共享内存的标识符(key值)
我们先查看一下对应的文档:
man 3 ftok

函数原型:
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
参数解析:
pathname:
我们的第一个参数是一个const char*的类型, 我们的pathname代表一个路径。我们要给出一个路径,表示我们当前要在哪个路径下进行生成。
int proj_id
✅ 作用
公式简单理解:key = (文件 inode 信息) + proj_id 低 8 位
✅ 可以填什么
推荐范围:1 ~ 255
⚠️ 尽量不要使用 0,部分环境下 0 容易产生冲突。
返回值:
成功返回一个key值,失败返回-1,错误码被设置。

shmat:将物理空间的共享区在虚拟空间映射一份
man 2 shmat

函数原型:
SYNOPSIS
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数解析:
shmid:
共享内存的标识符,我们是在创建了一个共享内存后返回了一个对应的标识符。
shmaddr:
我们指定要进行连接到虚拟地址的哪个地方。
如果设置位NULL,那么就表示我们的os自动的进行选择一个地方。
shmflg:
我们对应的输出型参数,
SHM_RDONLY:
只读的形式打开。
我们一般写0,表示以读和写的方式进行打开。
SHM_RND:地址向下对齐(地址圆整)

返回值:
成功返回我们虚拟内存的起始地址, 失败返回-1,错误码被设置。

我们会返回一个其实空间的大小,加上我们的空间的大小我们就能映射到我们的内存的任意一个地方。
注意:
1 shmaddr 为 NULL,核心自动选择一个地址
2 shmaddr 不为 NULL 且 shmflg 无 SHM_RND 标记,则以 shmaddr 为连接地址。
3 shmaddr 不为 NULL 且 shmflg 设置了 SHM_RND 标记,则连接的地址会自动向下调整为 SHMLBA 的整数倍。公式:shmaddr – (shmaddr % SHMLBA)
4 shmflg=SHM_RDONLY,表示连接操作用来只读共享内存
扩展:SHMLAB
是系统的我们不能手动的进行修改。
SHMLBA
全称
Segment Low Boundary Address Multiple 共享内存低边界地址倍数 / 共享内存对齐单位
核心作用(搭配 shmat + SHM_RND 考试重点)
它是 System V 共享内存挂载地址强制对齐粒度
Linux 绝大多数架构下,SHMLBA = 系统页大小(PAGE_SIZE,默认 4096 字节 = 4KB)Arch Linux
和 SHM_RND 配套公式
shmaddr != NULL 且 shmflg | SHM_RND
plaintext
最终挂载地址 = shmaddr − (shmaddr % SHMLBA)
含义:向下取整,对齐到最近的 SHMLBA 整数倍地址
两种场景对比
不带 SHM_RND 你传入的 shmaddr 本身必须已经是 SHMLBA 对齐地址,否则 shmat 直接失败。
带 SHM_RND 不用自己保证对齐,内核自动向下对齐到 SHMLBA 边界。
考试一句话标准答案
SHMLBA:共享内存段低边界地址倍数,是共享内存挂载地址的对齐单位;Linux 下通常等于系统页面大小。搭配 SHM_RND 时,内核自动把 shmaddr 向下调整至 SHMLBA 整数倍。
补充小知识点
-
日常开发尽量 shmaddr=NULL,让内核自动分配地址,几乎不需要手动指定地址、操作 SHM_RND/SHMLBA
-
代码中可以用 getpagesize() 获取当前系统页大小,不要硬写 4096
关联宏速记
-
SHMLBA:对齐粒度(4KB)
-
SHM_RND:开启自动向下对齐(Round down)
-
SHM_RDONLY:只读挂载共享内存
查询:
方法1 getconf PAGE_SIZE
方法2 cat /proc/self/page_size

shmdt:将当前共享内存和当前进程进行脱离。
man 2 stmdt

函数原型:
SYNOPSIS
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
参数解析:
这里我们只要传入, 虚拟地址的起始空间就可以了,我们的shmat返回过一个。
返回值:
成功返回0,失败返回-1.
![]()
注意:我们只是将该内存给脱离我们当前的虚拟地址空间,并不代表我们将他们进行了对应的删除删除还要靠下面的函数。
shmctl:控制共享内存

函数原型:
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数解析:
shmid:第一个参数是我们的对应共享内存的标识符。也就是我们shmget的返回值。
cmd:是我们将要采取的动作。
IPC_STAT:把shmid_ds结构中的数据设置位共享内存当前的关联值。
IPC_SET: 在进程有足够权限的前提下,把共享内存的当前关联值设置为shmid_ds数据结构给出的值。
IPC_输出共享内存段。
| IPC_STAT | 获取共享内存段的当前状态,将内核中 shmid_ds 结构的数据复制到用户提供的结构中 | 内核 → 用户空间 | 需对共享内存段有读权限 |
| IPC_SET | 设置共享内存段的状态,用用户提供的 shmid_ds 结构中的值更新内核中的对应字段 | 用户空间 → 内核 | 需有效用户 ID 等于共享内存所有者或创建者,或具有 CAP_SYS_ADMIN 权限 |
| IPC_RMID | 删除(销毁)共享内存段,标记该段为待删除状态,当最后一个进程脱离后真正释放 | 内核操作 | 需有效用户 ID 等于共享内存所有者或创建者,或具有 CAP_SYS_ADMIN 权限 |
buf:指向一个保存共享内存的模式状态和访问权限的数据结构。
buf参数:
struct shmid_ds buf; // 在栈上创建结构体
// 传 &buf,把结构体地址转成指针
int ret = shmctl(shmid, IPC_STAT, &buf);
if(ret == 0)
{
printf("权限perms = %o\\n", buf.shm_perm.mode); // %o 打印八进制权限
printf("挂载数nattch = %ld\\n", buf.shm_nattch);
}
返回值:
成功返回0,失败返回-1.

四、共享内存为什么是最快的方式
1.映射之后, 读写都能被对方看到。
2.不需要进行系统之间调用获取或写入内容,通信的双方没有所谓的同步机制。
五、创建system V 共享内存
我们这里创建了对应的共享内存, 我们可以使用指令来进行产看对应的共享内存。
#include<iostream>
#include<sys/shm.h>
#include<sys/ipc.h>
#include<sys/types.h>
#define SYMAX 4098
int main()
{
key_t key = ftok(".", 0);
int shmid = shmget(key,SYMAX,IPC_CREAT | IPC_EXCL);
return 0;
}
我们使用指令进行查看:
ipcs:代表查看所有的共享资源,-m :代表指定只查看对应共享内存。
ipcs -m
我们能看到对应共享内存的所有的属性。
参数的介绍:
key: 我们ftok申请出我们对应约定出来的哪个key。shmid:这个共享内存的标识符。
owner:是我们的拥有者是谁。bytes是我们这块共享内存的大小。
nattch:是我们进行链接的虚拟空间的个数有几个。status表示我们当前的一个状态。
| key | 共享内存键值(ftok 生成,进程用来找到这块共享内存) |
| shmid | 内核给共享内存分配 ID(shmget/shmctl 使用) |
| owner | 共享内存所有者用户名 |
| perms | 访问权限(八进制) |
| bytes | 共享内存大小(字节,这里 40998) |
| nattch | 当前挂载 (attach) 连接这块共享内存的进程数量(现在是 0,没有进程挂载) |
| status | 状态(空代表正常;如果显示dest代表已经执行 IPC_RMID 标记删除,等待所有进程脱离后销毁) |

我又创建了一个不同的共享共享内存,我们能观察到对应的shmid成递增式增加。

六、删除对应的共享内存
1.指令的方法进行删除:
ipcrm:删除共享资源, -m :共享内存 shmid:要删除共享内存的id。
ipcrm -m shmid

2.创建后使用完就进行删除:
我们发现我们使用完直接shmctl就能将对应的共享内存进行删除。
#include<iostream>
#include<sys/shm.h>
#include<sys/ipc.h>
#include<sys/types.h>
#define SYMAX 5099
int main()
{
key_t key = ftok(".", 1);
int shmid = shmget(key,SYMAX,IPC_CREAT | IPC_EXCL);
//删除
struct shmid_ds buf;
int ctl = shmctl(shmid, IPC_RMID,&buf);
return 0;
}

注意: 在用户层我们删除不能使用key, key只能给内核在未来区分唯一性。需要用shmid删除我们对应的共享资源。
七、代码示例
利用共享内存实现对应的两个进程之间进行通信。
我们对应的基础框架对应的构造和析构函数:
这里要注意我们对应的serve是创建我们的共享内存,我们的client是使用我们对应的共享内存,在这里我们进行判断,避免报错。我们程序结束的时候会自动的调用我们对应的析构函数,会释放我们对应的共享内存的。
#pragma once
#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <cstring>
#define SIZE_MAX 4096
#define PATH "."
#define SER "serve"
#define CLI "client"
#define EXIT(m)\\
do\\
{\\
perror(m);\\
exit(EXIT_FAILURE);\\
}while(0);
class Commit {
public:
Commit(const char* user)
{
_key = ftok(PATH, 0);
_user = user;
if(strcmp(_user.c_str() ,SER))
{
//创建共享内存
_shmid = shmget(_key,SIZE_MAX , 0666 | IPC_CREAT | IPC_EXCL);
}
//客户端只要打开就可以了
_shmid = shmget(_key,SIZE_MAX,IPC_CREAT);
}
~Commit() {
if(_user.c_str() == SER)
{
//删除我们的共享内存
int retctl = shmctl(_shmid,IPC_RMID,NULL);
if(retctl < 0) EXIT("shmctl fail!");
}
}
private:
int _shmid;
key_t _key;
std::string _user;
};
我们在创建完成后我们还要进行我们对应的链接,我们可以想一下我们的链接可以放在构造函数中,让我们打开了我们对应的共享内存的时候直接进行shmat进行链接, 所以我们可以将他们直接放在构造函数中, 当然我们结束的时候也得移除的时也的放入我们的析构函数析构函数,让它的引用计数变为0,好在后续的内容中进行释放。
#pragma once
#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <cstring>
#include<cstdio>
#include <unistd.h>
#define SIZEMAX 4096
#define CON_MAX 1024
#define PATH "."
#define SER "serve"
#define CLI "client"
#define EXIT(m) \\
do \\
{ \\
perror(m); \\
exit(EXIT_FAILURE); \\
}while(0);
class Commit {
private:
void dt()
{
//将他进行删除。
int retdt = shmdt(_address);
if(retdt< 0) EXIT("shmit fail!");
}
//共享内存进行链接到对应的虚拟地址空间中
void attend()
{
void*ret = shmat(_shmid, nullptr, 0);
if(ret == (void*)-1) EXIT("shmat fail!!!");
_address = (char*) ret;
}
public:
Commit(const char* user)
{
_key = ftok(PATH, 0);
_user = user;
if(strcmp(_user.c_str() , SER)==0)
{
//创建共享内存
_shmid = shmget(_key,SIZEMAX , IPC_CREAT | IPC_EXCL| 0666);
}
//客户端只要打开就可以了
else{
_shmid = shmget(_key,SIZEMAX,IPC_CREAT);
}
attend();
}
~Commit() {
dt();
if(strcmp(_user.c_str() , SER)==0)
{
//删除我们的共享内存
int retctl = shmctl(_shmid,IPC_RMID,NULL);
if(retctl < 0) EXIT("shmctl fail!");
}
}
private:
int _shmid;
key_t _key;
char* _address;
std::string _user;
};
当然我们还得让我们的两个进程进行对应的读写, 所以这里我们还要分别的实现我们的读写功能。
我们的写直接将我们写的内容写道我们的对应的全局的buffer中, 然后将buffer的内容拷贝给我们获得的地址就可以了。
然后我们读的时候直接%s读取一个字符串就可以了。
void write()
{
fgets(buffer,sizeof(buffer), stdin);
//处理\\n
buffer[strlen(buffer)-1]=0;
strcpy(_address,buffer);
}
void Read()
{
sleep(2);
printf("%s\\n", _address);
}
我们对应的serve我们进行写。我们的client进行读。
serve:
#include "commit_system.hpp"
int main()
{
Commit cc("serve");
while(1)
{
cc.Read();
}
return 0;
}
client:
#include "commit_system.hpp"
int main()
{
Commit cc("client");
while(1)
{
cc.write();
}
return 0;
}
commit.hpp
#pragma once
#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <cstring>
#include<cstdio>
#include <unistd.h>
#define SIZEMAX 4096
#define CON_MAX 1024
#define PATH "."
#define SER "serve"
#define CLI "client"
#define EXIT(m) \\
do \\
{ \\
perror(m); \\
exit(EXIT_FAILURE); \\
}while(0);
char buffer[CON_MAX];
class Commit {
private:
void dt()
{
//将他进行删除。
int retdt = shmdt(_address);
if(retdt< 0) EXIT("shmit fail!");
}
//共享内存进行链接到对应的虚拟地址空间中
void attend()
{
void*ret = shmat(_shmid, nullptr, 0);
if(ret == (void*)-1) EXIT("shmat fail!!!");
_address = (char*) ret;
}
public:
Commit(const char* user)
{
_key = ftok(PATH, 0);
_user = user;
if(strcmp(_user.c_str() , SER)==0)
{
//创建共享内存
_shmid = shmget(_key,SIZEMAX , IPC_CREAT | IPC_EXCL| 0666);
}
//客户端只要打开就可以了
else{
_shmid = shmget(_key,SIZEMAX,IPC_CREAT);
}
attend();
}
void write()
{
fgets(buffer,sizeof(buffer), stdin);
//处理\\n
buffer[strlen(buffer)-1]=0;
strcpy(_address,buffer);
}
void Read()
{
sleep(2);
printf("%s\\n", _address);
}
~Commit() {
dt();
if(strcmp(_user.c_str() , SER)==0)
{
//删除我们的共享内存
int retctl = shmctl(_shmid,IPC_RMID,NULL);
if(retctl < 0) EXIT("shmctl fail!");
}
}
private:
int _shmid;
key_t _key;
char* _address;
std::string _user;
};
当然这里我们可以进行一下小优化,当我们写入quit的时候我们就进行退出。
这里我们将我们对应的代码进行对应的优化:
这里我们简单的讲解一下细节:
1.我们如果创建一个int类型他会直接映射在我们栈区,我们对应的栈区不共享。
2.我们malloc创建一个引用计数也没用,堆区也是不共享的。
3.我们创建一个结构体, 将我们shmat的地址给我们对应的结构体,那么我们对应的结构体的第一个变量的起始地址就是我们对应的虚拟地址空间的地址, 所以这里我们两个是共同享有的, 我们创建一个计数器, 我们能同时的访问我们对应的计数器。当我们对应的计数器变值的时候我们的read端就会要求我们对应的进程退出。
#pragma once
#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <cstring>
#include <cstdio>
#include <unistd.h>
#include <string>
#include <cstdlib>
#define SIZEMAX 4096
#define CON_MAX 1024
#define PATH "."
#define SER "serve"
#define CLI "client"
#define EXIT(m) \\
do \\
{ \\
perror(m); \\
exit(EXIT_FAILURE); \\
}while(0);
char buffer[CON_MAX];
// 共享内存统一结构:两个进程看到同一份
struct ShmData
{
int exit_flag; // 0运行,1请求全部退出
char buf[SIZEMAX – sizeof(int)];
};
class Commit {
private:
void dt()
{
int retdt = shmdt(_address);
if(retdt < 0) EXIT("shmdt fail!");
}
void attend()
{
void* ret = shmat(_shmid, nullptr, 0);
if(ret == (void*)-1) EXIT("shmat fail!!!");
_address = static_cast<ShmData*>(ret);
}
public:
Commit(const char* user)
{
_key = ftok(PATH, 0);
_user = user;
if(strcmp(_user.c_str(), SER) == 0)
{
// 服务端创建共享内存
_shmid = shmget(_key, SIZEMAX , IPC_CREAT | IPC_EXCL | 0666);
}
else
{
// 客户端只打开,不创建
_shmid = shmget(_key, SIZEMAX, 0666);
}
attend();
// 服务端初始化标志
if(strcmp(_user.c_str(), SER) == 0)
{
_address->exit_flag = 0;
memset(_address->buf, 0, sizeof(_address->buf));
}
}
bool write()
{
fgets(buffer,sizeof(buffer), stdin);
buffer[strlen(buffer) – 1] = '\\0';
if(strcmp(buffer, "quit") == 0)
{
// 设置全局退出标记,两个进程都能看到
_address->exit_flag = 1;
return false;
}
strcpy(_address->buf, buffer);
return true;
}
bool Read()
{
sleep(2);
// 检测退出标记,标记置1直接退出循环
if (_address->exit_flag == 1)
{
printf("【通知】对方发起退出,读进程结束\\n");
return false;
}
printf("收到:%s\\n", _address->buf);
return true;
}
~Commit() {
dt();
// 只有服务端负责销毁共享内存
if(strcmp(_user.c_str(), SER)==0)
{
int retctl = shmctl(_shmid,IPC_RMID,NULL);
if(retctl < 0)
{
perror("shmctl");
}
}
}
private:
int _shmid;
key_t _key;
ShmData* _address;
std::string _user;
};
serve:
#include "commit_system.hpp"
int main()
{
Commit cc("serve");
while( cc.Read());
std::cout<<"client quit"<<std::endl;
return 0;
}
client:
#include "commit_system.hpp"
int main()
{
Commit cc("client");
while(cc.write());
std::cout<<"写端结束"<<std::endl;
return 0;
}





