diy数据库(八)--客户端和服务器之间的通信协议
来源:互联网 发布:网络测速器手机版 编辑:程序博客网 时间:2024/05/16 19:25
一、什么是通信协议
通信协议是指双方实体完成通信或服务所必须遵循的规则和约定。
二、diydb的协议格式
1、协议格式是通信协议中最重要的部分之一,diydb采用的是自定义格式和BSON格式的混合格式
2、协议格式总览
协议主要分三个大段
(1)协议头:包括消息长度(用于读取一个完整的协议包)和消息类型
(2)协议主体:不同的消息类型对应不同的协议主体,协议主体主要保存的是每种消息类型独有的信息
(3)附加信息:一个或多个bson格式的对象,表示实际操作和返回的数据库中的数据对象
3、协议格式主体
返回消息:一、返回值,用于表示返回成功或失败;二、返回记录数,用于表示返回的数据对象的个数,即附加消息中bson对象的个 数。
插入消息:插入记录数,表示要插入的数据对象的个数。
删除消息:因为每次只能删除一个数据,所以这里没有特殊信息。
查询消息:因为每次只能查询一个数据,所以这里没有特殊信息。
注:1)diydb的数据删除和查询时,客户端必须在附加信息中以bson格式传递一个数据对象的_id信息。
2)另外,quit命令只需要协议头就可以了。
4、附加信息
每个附加记录或插入记录实际上就是一个bson对象,删除条件或查询条件实际上就是{_id:XXX}格式的json对象对应的bson对象(因为diydb实际上没有复杂的条件查询功能,当然可以以后增加)。
注:1)、在直接传递一个结构体对象时,要注意结构体里面的属性的字节对齐。
2)、跨操作系统通信时要注意字节序的问题。
三、源码分析
通讯协议的主要实现(这里主要是数据打包解包)在msg.hpp和msg.cpp两个文件中,比如客户端用msgBuildInsert封装表示插入数据的数据包并存入数据流中,然后服务器端代理线程收到这个数据流后用msgExtractInsert解包这个数据流中的数据。下面是msg.hpp的所有代码。
#ifndef MSG_HPP__#define MSG_HPP__#include "bson.h"#define OP_REPLY 1//返回消息#define OP_INSERT 2//插入消息#define OP_DELETE 3//删除消息#define OP_QUERY 4//查询消息#define OP_DISCONNECT 6//断开连接消息#define OP_CONNECT 7//连接消息#define RETURN_CODE_STATE_OK 1struct MsgHeader/*数据包的头*/{ int messageLen ;//数据包的长 int opCode ;//数据包类型} ;struct MsgReply/*返回数据包*/{ MsgHeader header ;//数据包的头 int returnCode ;//返回值 int numReturn ;//返回的记录数量 char data[0] ;//标记数具体的开始位置} ;struct MsgInsert/*插入数据包*/{ MsgHeader header ; int numInsert ;//插入记录数 char data[0] ;} ;struct MsgDelete/*删除数据包*/{ MsgHeader header ; char key[0] ;//删除条件} ;struct MsgQuery/*查询数据包*/{ MsgHeader header ; char key[0] ;//查询条件} ;/*返回消息的封装*/int msgBuildReply ( char **ppBuffer, int *pBufferSize, int returnCode, bson::BSONObj *objReturn ) ;/*解消息*/int msgExtractReply ( char *pBuffer, int &returnCode, int &numReturn, const char **ppObjStart ) ;/*做一个插入obj的数据包*/int msgBuildInsert ( char **ppBuffer, int *pBufferSize, bson::BSONObj &obj ) ;/*做一个插入多个obj的数据包*/int msgBuildInsert ( char **ppBuffer, int *pBufferSize, vector<bson::BSONObj*> &obj ) ;/*解插入*/int msgExtractInsert ( char *pBuffer, int &numInsert, const char **ppObjStart ) ;/*删除*/int msgBuildDelete ( char **ppBuffer, int *pBufferSize, bson::BSONObj &key ) ;/*解删除*/int msgExtractDelete ( char *pBuffer, bson::BSONObj &key ) ;/*查询*/int msgBuildQuery ( char **ppBuffer, int *pBufferSize, bson::BSONObj &key ) ;/*解查询*/int msgExtractQuery ( char *pBuffer, bson::BSONObj &key ) ;int msgMultiInsert ( char **ppBuffer, int *pBufferSize, bson::BSONObj &obj ) ;/*做一个插入多个obj的数据包*/int msgBuildInsert ( char **ppBuffer, int *pBufferSize, vector<bson::BSONObj*> &obj ) ;#endif
对上面函数的实现,我们以插入数据包的封装和解包为例来分析
int msgBuildInsert ( char **ppBuffer, int *pBufferSize, BSONObj &obj ){//只插入一个数据 int rc = DIY_OK ; int size = sizeof(MsgInsert) + obj.objsize() ;//表示插入数据的数据包的长度 MsgInsert *pInsert = NULL ; rc = msgCheckBuffer ( ppBuffer, pBufferSize, size ) ;//如果*ppBuffer指向的堆空间不够用,则重新分配一个足够的字节数组空间 if ( rc ) { PD_LOG ( PDERROR, "Failed to realloc buffer for %d bytes, rc = %d", size, rc ) ; goto error ; } pInsert = (MsgInsert*)(*ppBuffer) ;//将字节流划分成包格式 // 构建协议头 pInsert->header.messageLen = size ; pInsert->header.opCode = OP_INSERT ; // 构建协议主体 pInsert->numInsert = 1 ; // 构建附加消息,即填bson对象 memcpy ( &pInsert->data[0], obj.objdata(), obj.objsize() ) ;done : return rc ;error : goto done ;}
int msgExtractInsert ( char *pBuffer, int &numInsert, const char **ppObjStart ){ int rc = DIY_OK ; MsgInsert *pInsert = (MsgInsert*)pBuffer ; // 检测数据包的长度是否合法 if ( pInsert->header.messageLen < (int)sizeof(MsgInsert) ) { PD_LOG ( PDERROR, "Invalid length of insert message" ) ; rc = DIY_INVALIDARG ; goto error ; } // 检测数据包的类型对不对 if ( pInsert->header.opCode != OP_INSERT ) { PD_LOG ( PDERROR, "non-insert code is received: %d, expected %d", pInsert->header.opCode, OP_INSERT ) ; rc = DIY_INVALIDARG ; goto error ; } // 解析附加信息,即取出bson对象 numInsert = pInsert->numInsert ; // object if ( 0 == numInsert ) { *ppObjStart = NULL ; } else { *ppObjStart = &pInsert->data[0] ; }done : return rc ;error : goto done ;}注:客户端得到的bson对象的由来:用户在客户端输入命令,这个命令如果包含json格式的文本,则通过json库根据表示json格式的文本构建一个json对象,然后通过bson库将这个json对象转换成一个bson对象。
四、总结
1、因为tcp通信是字节流,并没有边界标识,所以应用层在传数据时,必须通过增加数据包长度字段或者增加数据包边界标识来区分一个完整的包。diydb中用的是数据包长度的方式,这也是实际应用中常用的方式。
2、如果在通信时直接传结构体数据,则要注意字节对齐机制,所以要合理安排结构体中属性的长度的排列顺序。
- diy数据库(八)--客户端和服务器之间的通信协议
- 浏览器,服务器,浏览器和服务器之间的通信协议
- MySQL客户端/服务器通信协议
- diy数据库(三)--客户端框架的搭建
- 淘宝千牛软件服务器fmsas端口与客户端的通信协议(完整版)
- peer之间的通信协议
- 基于UDP的服务器和客户端之间的通信
- AJAX客户端和服务器之间的传值理解
- FMS服务器和客户端之间的远程调用实现
- springmvc服务器和客户端之间的乱码过程分析
- C++利用socket的客户端和服务器之间传输文件
- 电骡协议规范(三):客户端和服务器之间的UDP通讯
- 实现多客户端和服务器之间的通讯(TCP协议下,多进程)
- 实现客户端和服务器之间的通信(TCP协议、多线程)
- 禄来6008机身和镜头之间的通信协议测试
- 客户端和服务器之间通信讲解
- 客户端和服务器之间通信讲解
- diy数据库(四)--锁和队列
- python 面试题 - 知识点整理
- openframeworks学习之路1-环境搭建及人脸识别
- Mac系统下执行hadoop jar 运行在某包内的程序 提示java.lang.ClassNotFoundException的解决方法
- classpath环境变量需要设置吗?
- Oracle VM VirtualBox 下 ubuntu 虚拟机 如何 挂载共享
- diy数据库(八)--客户端和服务器之间的通信协议
- Java函数传递参数:值传递还是引用传递
- Java中 VO、 PO、DO、DTO、 BO、 QO、DAO、POJO的概念
- 新标日使用的语法体系
- linux top命令VIRT,RES,SHR,DATA的含义
- php mysql 操作类
- tftp服务器配置的注意事项
- 第七次实验-杨辉三角
- Android : 颜色设置的几种方法