看过我开源代码的朋友知道,我这个项目在逻辑服和数据服进行数据同步采用的就是共享内存。其实数据同步的方式有很多种,其中使用较多的一种方式就是tcp网络协议同步,不过这种方式我觉得编码过程略嫌繁琐,使用也不够方便,开发效率不如共享内存。
当然也有不少朋友吐槽共享内存,说共享内存没法跨机器分布式部署,说实话这个要看你的需求,只有根据项目需求制定的合适方案,以我工作这些年经验看,一般的手游项目远远够用了,共享内存除了开发方便还有几个优点,一是传输效率高,二是节约内存,三是服务器挂了之后能快速恢复。
有好多的朋友不太了解,什么是共享内存,我这篇文章就来讲讲共享内存的一些基本知识和共享内存在windows和linux使用的一些异同点。
共享内存是进程间同步数据的一种方式,它允许多个进程访问同一块物理内存,就是一段物理内存被同时映射到不同进程地址空间中,当一个进程修改这块内存的值,其它进程就可以直接读取到值的改变。
对共享内存的操作实际上就五个关键操作:创建、打开、映射、释放、关闭或者删除, 这五个操作在windows和linux下是不一样的,我们先业看windows下的操作方法:
Windows版本:
1.创建共享内存
HANDLE CreateFileMapping(
HANDLE hFile,
LPSECURITY_ATTRIBUTES lpFileMappingAttributes,
DWORD flProtect,
DWORD dwMaximumSizeHigh,
DWORD dwMaximumSizeLow,
LPCWSTR lpName
);
说明:
hFile: 这里是文件句柄,如果传实际的文件句柄就和linux 下的mmp文件映射类似的功能了,我们这里仅仅使用共享内存功能,赋值 INVALID_HANDLE_VALUE(0xFFFFFFFF)就可以了。
dwMaximumSizeLow: 创建的共享内存的大小。
lpName: 这个可以理解为这块共享内存的唯一标识,便于多个进程能访问到同一块共享内存。
2.打开共享内存
HANDLE OpenFileMapping(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
LPCWSTR lpName
);
说明:
dwDesiredAccess: 需的访问权限,FILE_MAP_READ | FILE_MAP_WRITE表示同时需要读和写权限。
lpName:共享内存的唯一标识。
3.映射共享内存到进程空间
LPVOID MapViewOfFile(
HANDLE hFileMappingObject,
DWORD dwDesiredAccess,
DWORD dwFileOffsetHigh,
DWORD dwFileOffsetLow,
SIZE_T dwNumberOfBytesToMap
);
说明:
hFileMappingObject:共享内存句柄,就是上两个方法的返回值。
dwDesiredAccess:需的访问权限,FILE_MAP_READ | FILE_MAP_WRITE表示同时需要读和写权限。
返回值: 共享内存映射到本地进程的地址。
4.释放共享内存
BOOL UnmapViewOfFile(
LPCVOID lpBaseAddress
);
说明:
lpBaseAddress:共享内存印射到本地进程的地址,也是上一个方法的返回值。
5.关闭共享内存
BOOL CloseHandle(
HANDLE hObject
);
说明:
hObject:共享内存的句柄。
Linux:
1. 创建共享内存
int shmget(key_t key, size_t size, int shmflg);
key: 共享内存的key, 唯一标识这块共享内存。
size: 共享内存的大小 。
shmflg: 用于创建共享内存时可填值: 0666 | IPC_CREAT | IPC_EXCL
2. 打开共享内存
int shmget(key_t key, size_t size, int shmflg);
key: 共享内存的id, 唯一标识这块共享内存。
size: 共享内存的大小 ,用于打开共享内存时,可填0。
shmflg: 用于打开共享内存时,可填0。
3.映射共享内存到进程空间
void *shmat(int shm_id, const void *shm_addr, int shmflg);
shm_id:是由shmget()函数返回的共享内存标识。
shm_addr:指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址。
shm_flg:是一组标志位,通常为0。
4.释放共享内存
int shmdt(const void *shmaddr);
shmaddr:是shmat()函数返回的地址指针,调用成功时返回0,失败时返回-1.
5.删除共享内存
int shmctl(int shm_id, int command, struct shmid_ds *buf);
shm_id:是由shmget()函数返回的共享内存标识。
command:命令 IPC_RMID表示删除共享内存, 但实际上并不是真的从内核删除一个段,而是仅仅把这个段标记为删除,实际的删除发生在这块共享内存nattch为0时。
在linux下还有命令用来管理共享内存
1.查看系统中的所有的共享内存
ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x01010000 147226624 root 666 73728 2
0x01020000 147259393 root 666 1687552 2
0x01030000 147292162 root 666 114688 2
0x01040000 147324931 root 666 98304 2
0x01050000 147357700 root 666 73728 2
0x01060000 147390469 root 666 2785280 2
0x01070000 147423238 root 666 360448 2
0x01080000 147456007 root 666 90112 2
2.删除系统中的共享内存
ipcrm -m [shmid]
说明: shmid为共享内存的id
2.查看系统中全部共享内存的统计
ipcs -m -u
在linux下还可自己修改共享内存的内核配置:
#vi /etc/sysctl.conf
kernel.shmmax=15461882265
kernel.shmmin=3774873
kernel.shmall=3774873
shmmax : 配置了单个共享内存段的最大值
shmmin : 配置了单个共享内存段的最小值
shmall: 可用共享内存的总数量(页面)
需要注意的是, 在Window和Linux下共享内存的表现不一样,在Window下,只要关联共享内存的进程全部退出,即便没有正确的释放关闭,操作系统也会全部释放关闭,而在linux下 ,只有关联共享的内存的进程,有一个没有正确的释放关闭,那么这块共享内存就会一直存在,需要手动用ipcrm来删除。