github链接(更新中)
https://github.com/pourtheworld/mydb
大纲(更新中)
(0)mydb的架构设想
本期任务
通讯协议的构建
协议的构建来源于架构的设计;
协议的格式包括头+主体+附加信息;
我们设计协议某个部分长度时需要考虑到内存对齐。
架构设计
- 插入
每次插入一条或者多条记录。(我们暂时设定为一次插一条) - 更改
不支持。 - 查询
仅支持K/V存储,查询提供value作为查询条件。 - 删除
仅支持K/V存储,删除提供value作为删除条件。 - 其他
包含不定信息,具体内容存于信息内。 - 返回
包含返回码、记录数量、数据信息。
协议格式
协议头:包含消息的长度+消息的类型,可能有人会问为什么类型需要4个字节,等会再内容对齐会详述。
协议主体:
协议附加信息:
内存对齐
在消息头的设置上,我们将消息类型置为了4字节,实际上我们能用到的可能1字节都足矣。
但是在CPU读取处理的过程中,往往是以4字节为单位的,如果我们存了1个字节a,系统会一次读取1个字节的a加上3个字节的其他内容b。而b在下一个读取块里还有其他部分,可能会导致需要读两次才能得到完整的b;
甚至这个b由于下一块内容被修改了导致不一致。
在以下InefficientParking结构体中,我们可以看到mF2,mB5虽然只有1个字节,但是编译器会自动进行内存对齐:
自定义消息格式
我们首先介绍msg.hpp中消息格式如何定义、消息如何封装和解封;
再介绍command.cpp中命令的处理函数。
消息头
#define OP_REPLY 1
#define OP_INSERT 2
#define OP_DELETE 3
#define OP_QUERY 4
#define OP_COMMAND 5
//由于在网络层的disconnect需要PD_LOG,我们从通信层定义正常的关闭与连接
#define OP_DISCONNECT 6
#define OP_CONNECT 7
#define OP_SNAPSHOT 8
#define RETURN_CODE_STATE_OK 1
//各类消息的头结构是一样 4bytes消息长度+4bytes消息类型(为了内存对齐)
struct MsgHeader
{
int messageLen;
int opCode;
};
消息主体
回复类消息:
//回复类消息的主体为 返回值 返回的记录数
//data[0]为可变长度数组,本身可以不占内存
//在这里用于记录消息主体开始的位置
struct MsgReply
{
MsgHeader header;
int returnCode;
int numReturn;
char data[0];
};
插入类消息:
//插入类消息的主体为 插入的记录数
struct MsgInsert
{
MsgHeader header;
int numInsert;
char data[0];
};
删除类消息:
//删除类消息没啥主体,不过key对应了待删除消息的键
struct MsgDelete
{
MsgHeader header;
char key[0];
};
查询类消息:
struct MsgQuery
{
MsgHeader header;
char key[0];
};
命令类消息:
//命令类消息要包含命令参数
struct MsgCommand
{
MsgHeader header;
int numArgs;;
char data[0];
};
消息的解封,过程比较类似,我们以回复类消息为准:
//需要返回的当前缓冲区、缓冲区当前长度、给定的返回值、需要返回的数据
int msgBuildReply(char **ppBuffer,int *pBufferSize,int returnCode,BSONObj *objReturn)
{
int rc=EDB_OK;
int size=sizeof(MsgReply); //当前包的长度实际为MsgReply的Header
MsgReply *pReply=NULL;
//如果有需要返回的内容,长度+数据长度
if(objReturn) size+=objReturn->objsize();
//接下来需要重新分配ppBuffer,首先查看当前缓冲区是否够大
rc=msgCheckBuffer(ppBuffer,pBufferSize,size);
PD_RC_CHECK ( rc, PDERROR, "Failed to realloc buffer for %d bytes, rc = %d",
size, rc ) ;
//缓冲区分配好了,我们先把它的指针类型强转成MsgReply
//即-> Header:messengeLen+opCode 、returnCode、numReturn、data[0]
pReply=(MsgReply*)(*ppBuffer);
pReply->header.messageLen=size;
pReply->header.opCode=OP_REPLY;
pReply->returnCode=returnCode;
pReply->numReturn=(objReturn)?1:0;
//从pReply结构体中成员data所占内存的首地址开始,复制data的内容
if(objReturn) memcpy(&pReply->data[0],objReturn->objdata(),objReturn->objsize());
done:
return rc;
error:
goto done;
}
解封信息也以回复信息为准:
//MsgReply的解封
//获得的缓冲区 待接受的返回值 待接受的记录数 待接受的data内容
int msgExtractReply(char *pBuffer,int &returnCode,int &numReturn,const char **ppObjStart)
{
int rc=EDB_OK;
//我们用一个pReply的指针去指向获得的缓冲区
MsgReply *pReply=(MsgReply*