我的github:codetoys,所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。
这些代码大部分以Linux为目标但部分代码是纯C++的,可以在任何平台上使用。
魔鬼在细节处。
目录
一、计算大小
为了申请共享内存,要根据所需容量计算大小。
//计算共享内存大小,比实际容量多申请一个单元
static T_SHM_SIZE _CalcShmSize(T_SHM_SIZE size)
{
T_SHM_SIZE ret = sizeof(array_head) + size * sizeof(T);
if (sizeof(T) > 1024)ret += 1024;
else ret += sizeof(T);
//thelog<<"计算出的大小 "<<ret<<endi;
return ret;
}
准确的大小是数据头大小加上记录长度*容量。这里搞得这么复杂是别的地方因为有BUG,为了解决BUG,添了一些(添加一个单元但限制最长1024字节)。
这种计算内存的方法依赖对齐方式,因此数据头里面有个long的无意义字段,可以确保至少是按照long来对齐的(不要额外设定的情形下——非常不建议用编译指令设定,容易扩大影响范围导致难以解决的BUG)。
二、设定元数据
元数据包含数据格式的基本信息,元数据匹配起码能读出些数据。
void _makemeta(CMeta& meta, int version)const
{
int Tsize[7] = { sizeof(array_head),sizeof(T_USER_HEAD),sizeof(T),version,998,999,1000 };
meta.Set(GUID_T_ARRAY, Tsize, 7);
}
元数据里面添加了数据头长度、用户头长度、记录长度、版本(此版本是模板的版本,不是用户的版本,用户的版本应该放在用户头里面)。
元数据检查很简单,私有内存构造一个新的元数据,与共享内存里面的做二进制比较,完全相同即可。例如:
bool _CheckMeta()const
{
if (NULL == pHead)return false;
CMeta meta;
string msg;
_makemeta(meta, version);
if (!pHead->meta.Compare(meta, msg))
{
string str;
thelog << msg << ende;
thelog << "数据格式不匹配" << endl << "数据的元数据信息" << endl << pHead->meta.toString(str) << ende;
thelog << "应用的元数据信息" << endl << meta.toString(str) << ende;
return false;
}
return true;
}
由于三个长度在元数据里面,所以如果修改了用户头或记录格式,都会导致匹配失败,如果需要兼容不同的格式,就需要代码逐个处理(这意味着你需要写一个新的接口层,底层使用不同结构的共享内存对象)。
三、连接到共享内存
大部分情况是连接到已经存在的共享内存。由于我们不选择使用KEY的方式来寻找共享内存,我们要有自己的一套记录共享内存ID的方法,有数据库的时候可以存在数据库,没有数据库可以存在一个公开的文件里面,我后来直接使用了一块共享内存在存储共享内存信息,而这块共享内存的ID则存在一个文件里面(这个文件只存一个ID,没有多进程修改的问题)。
连接的代码:
bool AttachToShm(bool _isReadOnly)
{
if (IsConnected())
{
if (!DetachFromShm())return false;
}
//获取注册信息
ShmRegInfo reg(GetShmSysOfName(name.c_str()), name.c_str(), PART);
if (!reg.GetRegFromDb())return false;
shmid = reg.shmid;
m_isReadOnly = _isReadOnly;
char* p = CShmMan::ConnectByID(shmid, m_isReadOnly, (SHM_NAME_SHMPOOL == name ? (void*)ADDR_SHM_POOL : NULL));
if (NULL == p)
{
thelog << "连接共享内存失败 shmid = " << shmid << " 错误信息:" << strerror(errno) << ende;
return false;
}
thelog << name << " 连接共享内存成功 PI_N " << PI_N << " PART " << PART << " shmid = " << shmid << " p " << (void*)p << endi;
if (((unsigned long)p) % 8 != 0)
{
thelog << "地址对齐错误" << ende;
DetachFromShm();
return false;
}
this->pHead = (array_head*)p;
this->pData = (T*)(p + sizeof(array_head));
this->isPrivate = false;
if (!_CheckMeta())
{
DetachFromShm();
return false;
}
if (pHead->name != name)
{
thelog << "数据名称不匹配 " << pHead->name.c_str() << " " << name << ende;
DetachFromShm();
return false;
}
GET_SHM_PRIVATE_DATA(PI_N).clearShmPrivateData();
GET_SHM_PRIVATE_DATA(PI_N).AddShmMap(shmid, (char*)(this->pHead) + sizeof(array_head));
GET_PP_VMAP(PI_N) = &pHead->vmaps;
//string str;
//thelog<<ReportHead(str)<<endi;
return true;
}
我这里检查了连接地址是不是8字节对齐,其实应该是按照long对齐(因为结构是用long来约束的,而long并一定是8字节)。
四、初始化私有数据
创建完共享内存后要初始化私有内存数据以便后续的访问(与上面代码的类似)。这部分可以自由设计,比如我的实现就是奇奇怪怪的:
//初始化第一个共享内存分块影射表(存储于head)和地址映射表(存储于私有数据)
void _InitFirstSPD()
{
this->pHead->vmaps.clearVMAP();
this->pHead->vmaps.AddVMAP(shmid, 0, this->pHead->capacity);
GET_SHM_PRIVATE_DATA(PI_N).clearShmPrivateData();
GET_SHM_PRIVATE_DATA(PI_N).AddShmMap(shmid, (char*)(this->pHead) + sizeof(array_head));
GET_PP_VMAP(PI_N) = &pHead->vmaps;
}
私有数据是全局变量,通过模板参数PI_N就可以直接定位。
扩展块的信息会在使用到的时候动态添加进去。
一再强调,一定要分清楚哪些数据在私有内存、哪些数据在共享内存。
五、排序和搜索
因为是数组,可以使用STL算法(这就是为什么一定要把HANDLE伪装成指针的原因——其实是随机迭代器)。
单一快获取指针后直接使用STL当然是没有问题的,但是多个块不连续就不行了。这个HANDLE提供的解决方案也适用于任何需要对不连续存储的数据操作的场合。
//排序,速度比较快,但只能是单一块
template <typename T_COMP >
void Sort_fast(T_COMP comp)
{
if (!IsOneBlock())
{
thelog << "共享内存 " << name << " 不是单一块,不能调用此功能" << endi;
return;
}
sort(pData, pData + pHead->size, comp);
}
//排序,速度比较慢,但不必是单一块
template <typename T_COMP>
void Sort_slow(T_COMP comp)
{
sort(Begin(), End(), comp);
}
HANDLE& LowerBound(T const& data, HANDLE& h)const
{
if (!IsOneBlock())
{
thelog << "共享内存 " << name << " 不是单一块,不能调用此功能" << endi;
return h;
}
T* it = lower_bound(pData, pData + pHead->size, data);
h.handle = it - pData;
return h;
}
HANDLE& LowerBound(T const& data, HANDLE& h, bool (*less)(T const&, T const&))const
{
if (!IsOneBlock())
{
thelog << "共享内存 " << name << " 不是单一块,不能调用此功能" << endi;
return h;
}
T* it = lower_bound(pData, pData + pHead->size, data, less);
h.handle = it - pData;
return h;
}
lower_bound也可以用HANDLE做参数来支持多个块,这里没有写是因为这部分功能主要set里面用(后面会介绍二叉树set)。
(这里是结束)