10、共享内存数组的操作和注意事项

初级代码游戏的专栏介绍与文章目录-CSDN博客

我的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)。 


(这里是结束)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值