一、客户端框架的概述
在diy数据库(一)中,已经对客户端的框架有了一个基本的介绍。他主要负责从命令行读取用户的输入,然后将输入解析成数据库服务器能够识别的命令通过tcp连接发送给数据库服务器,并且处理数据库服务器返回的结果。
值的注意的是,我们的客户端用到了工厂模式。所有已经实现的命令都必须注册到命令工厂中,然后所有命令都必须从命令工厂去get到。
二、类图说明
(1)Diy类是客户端的入口类,她负责从命令行读入用户输入的命令和参数,将命令字段和参数字段都解析出来,然后在prompt方法中以存放命令的字符串为参数从CommandFactory中取出对应的命令对象,然后执行具体命令对象的execute函数。
(2)CommandFactory是命令工厂类,可以向命令工厂注册命令,也可以从里面取一个命令对象。
(3)ICommand是所有命令的接口,所有命令都要继承这个基类。execute负责和数据库引擎进程建立tcp连接,然后发送数据库操作命令。函数handleReply是用来处理数据库引擎服务器返回的数据的。
(4)其他的类是五个具体的命令类,分别是插入、查询、帮助、连接、删除、关闭数据库。
三、主要实现文件
1、主流程框架
diy.cpp/diy.hpp
2.命令的抽象接口和具体实现
command.cpp/command.hpp
3.命令工厂
commandFactory.cpp/commandFactory.hpp
为了测试客户端框架,需要一个临时的服务器程序来与客户端通信pmdTcpListener.cpp
四、源码分析
到这里,我们对客户端有了四层的了解,趁热打铁,马上进入代码,一窥究竟。
1、主流程框架
diy.hpp
#ifndef _DIY_HPP_
#define _DIY_HPP_
#include <string>
#include <vector>
#include "ossSocket.hpp"
#include "commandFactory.hpp"
const int CMD_BUFFER_SIZE = 512;//允许用户输入命令的最大长度
class Diy {
public:
Diy(){}
~Diy(){};
public:
void start(void);//客户端的入口函数,控制客户端关闭
protected:
void prompt(void);//客户端的主要执行函数
private:
void split(const std::string &text, char delim, std::vector<std::string> &result);//将命令行中的输入切分成一个一个的token放到result中去
char* readLine(char *p, int length);
int readInput(const char *pPrompt, int numIndent);
private:
ossSocket _sock;//客户端socket
CommandFactory _cmdFactory;//命令工厂
char _cmdBuffer[CMD_BUFFER_SIZE];//用户输入的缓冲区
};
#endif
下面我们主要关注void prompt(void)的实现
void Diy::prompt(void)
{
int ret = DIY_OK;
ret = readInput("diy", 0);// print "diy> "
if(ret)
{
return;
}
// Input string
std::string textInput = _cmdBuffer;
// Split the inputing sentence.
std::vector<std::string> textVec;
split(textInput, SPACE, textVec);//SPACE就是空格
int count = 0;
std::string cmd = "";
std::vector<std::string> optionVec;
std::vector<std::string>::iterator iter = textVec.begin();
// handle different command here.
ICommand* pCmd = NULL;
for(;iter != textVec.end(); ++iter)
{
std::string str = *iter;
if(0 == count)//读命令
{
cmd = str;
count++;
}
else//读参数
{
optionVec.push_back(str);
}
}
pCmd = _cmdFactory.getCommandProcesser(cmd.c_str());//通多命令名cmd从命令工厂中拿到一个命令对象<span style="font-family: Arial; font-size: 14px;">pCmd </span>
if( NULL != pCmd )
{
pCmd->execute( _sock, optionVec);//以<span style="font-family: Arial; font-size: 14px;">optionVec为参数执行命令pCmd的处理函数</span>
}
}
2.命令的抽象接口和具体实现
command.hpp
#ifndef _COMMAND_HPP_
#define _COMMAND_HPP_
#include <vector>
#include <bson/src/util/json.h>//将用户输入的json转成json
#include "ossSocket.hpp"
//将会使用的命令
#define COMMAND_QUIT "quit"
#define COMMAND_INSERT "insert"
#define COMMAND_QUERY "query"
#define COMMAND_DELETE "delete"
#define COMMAND_HELP "help"
#define COMMAND_CONNECT "connect"
//缓冲区大小
#define RECV_BUF_SIZE 4096
#define SEND_BUF_SIZE 4096
#define DIY_QUERY_INVALID_ARGUMENT -101
#define DIY_INSERT_INVALID_ARGUMENT -102
#define DIY_DELETE_INVALID_ARGUMENT -103
#define DIY_INVALID_RECORD -104
#define DIY_RECV_DATA_LENGTH_ERROR -107
#define DIY_SOCK_INIT_FAILED -113
#define DIY_SOCK_CONNECT_FAILED -114
#define DIY_SOCK_NOT_CONNECT -115
#define DIY_SOCK_REMOTE_CLOSED -116
#define DIY_SOCK_SEND_FAILD -117
#define DIY_MSG_BUILD_FAILED -119
class ICommand
{
typedef int (*OnMsgBuild)( char ** ppBuffer, int * pBufferSize, \
bson::BSONObj &obj);
public:
virtual int execute( ossSocket & sock, std::vector<std::string> & argVec);//命令的执行体
int getError(int code);//得到错误码
protected:
int recvReply(ossSocket & sock );//接收
int sendOrder(ossSocket & sock, OnMsgBuild onMsgBuild );//发送
int sendOrder(ossSocket & sock, int opCode );//发送
protected:
virtual int handleReply() { return DIY_OK; }//<span style="font-family: Arial, Helvetica, sans-serif; font-size: 12px;">处理数据库服务器返回的数据</span>
protected:
char _recvBuf[RECV_BUF_SIZE];//接收缓冲区
char _sendBuf[SEND_BUF_SIZE];//发送缓冲区
std::string _jsonString;
};
//=================================================
//以下是三个实现的命令
//=================================================
class ConnectCommand : public ICommand
{
public:
int execute ( ossSocket & sock, std::vector<std::string> & argVec );
private:
std::string _address;
int _port;
};
class QuitCommand : public ICommand
{
public:
int execute(ossSocket & sock, std::vector<std::string> & argVec );
protected:
int handleReply();
};
class HelpCommand : public ICommand
{
public:
int execute(ossSocket & sock, std::vector<std::string> & argVec );
};
#endif
说明:
(1)ICommand是所有命令的接口,所有命令都要继承这个基类,它主要实现了命令的发送和接收。其中有两个虚函数需要具体命令去实现:execute(客户端命令的执行体),handleReply(对服务器返回的数据的处理)
(2)本数据库用到了bson和json来传输和存储数据,相关的操作,后面到数据的存储时再回来讲
(3)这里只暂时实现了三个命令的部分功能,后续会进一步完善,因为我们享受的就是一个逐步完善的过程,如果一蹴而就就没有更持久的满足感了
command.cpp
extern int gQuit;
int ICommand::execute( ossSocket & sock, std::vector<std::string> & argVec )
{
return DIY_OK;
}
int ICommand::getError(int code)
{
switch(code)
{
case DIY_OK:
break;
case DIY_IO:
std::cout << "io error is occurred" << std::endl;
break;
case DIY_INVALIDARG:
std::cout << "invalid argument" << std::endl;
break;
..............//省略
default :
break;
}
return code;
}
int ICommand::recvReply( ossSocket & sock )
{//接受一个返回的数据包
int length = 0;
int ret = DIY_OK;
// fill receive buffer with 0.
memset(_recvBuf, 0, RECV_BUF_SIZE);
if( !sock.isConnected() )
{
return getError(DIY_SOCK_NOT_CONNECT);
}
while(1)
{
// receive data from the server.first receive the length of the data.
ret = sock.recv(_recvBuf, sizeof(int));//包的长度,规定一个数据包的格式就是最前面的一个int存放的是包的长度
if( DIY_TIMEOUT == ret )
{
continue;
}
else if( DIY_NETWORK_CLOSE == ret )
{
return getError(DIY_SOCK_REMOTE_CLOSED);
}
else
{
break;
}
}
// get the value of length.
length = *(int*)_recvBuf;
// judge the length is valid or not.
if(length > RECV_BUF_SIZE)
{
return getError(DIY_RECV_DATA_LENGTH_ERROR);
}
// receive data from the server.second receive the last data.
while(1)
{
ret = sock.recv(&_recvBuf[sizeof(int)],length-sizeof(int));
if(ret == DIY_TIMEOUT)
{
continue;
}
else if(DIY_NETWORK_CLOSE == ret)
{
return getError(DIY_SOCK_REMOTE_CLOSED);
}
else
{
break;
}
}
return ret;
}
int ICommand::sendOrder( ossSocket & sock, OnMsgBuild onMsgBuild )
{
int ret = DIY_OK;
bson::BSONObj bsonData;
try {
bsonData = bson::fromjson(_jsonString);
} catch( std::exception & e) {
return getError(DIY_INVALID_RECORD);
}
memset(_sendBuf,0, SEND_BUF_SIZE);
int size = SEND_BUF_SIZE;
char * pSendBuf = _sendBuf;
ret = onMsgBuild(&pSendBuf, &size, bsonData);
if(ret)
{
return getError(DIY_MSG_BUILD_FAILED);
}
ret = sock.send( pSendBuf, *(int*)pSendBuf );
if(ret)
{
return getError(DIY_SOCK_SEND_FAILD);
}
return ret;
}
int ICommand::sendOrder( ossSocket & sock, int opCode )
{
int ret = DIY_OK;
memset(_sendBuf, 0, SEND_BUF_SIZE);
char * pSendBuf = _sendBuf;
const char *pStr = "hello world" ;//前期测试用,因为服务端还没有整好
*(int*)pSendBuf=strlen(pStr)+1 + sizeof(int) ;//一个int长度字段,pstr的长度,一个结束符
memcpy ( &pSendBuf[4], pStr, strlen(pStr)+1 ) ;
/*MsgHeader *header = (MsgHeader*)pSendBuf;
header->messageLen = sizeof(MsgHeader);
header->opCode = opCode;*/
ret = sock.send(pSendBuf, *(int*)pSendBuf);
return ret;
}
/******************************ConnectCommand****************************************/
int ConnectCommand::execute( ossSocket & sock, std::vector<std::string> & argVec )
{
int rc = DIY_OK;
_address = argVec[0];
_port = atoi(argVec[1].c_str());
sock.close();
sock.setAddress(_address.c_str(), _port);
rc = sock.initSocket();
if ( rc )
{
printf ( "Failed to init socket, rc = %d", rc ) ;
goto error ;
}
rc = sock.connect();
if ( rc )
{
printf ( "Failed to connect, rc = %d", rc ) ;
goto error ;
}
sock.disableNagle();
done :
return rc ;
error :
goto done ;
}
/******************************QuitCommand**********************************************/
int QuitCommand::handleReply()
{
int ret = DIY_OK;
gQuit = 1;
return ret;
}
int QuitCommand::execute( ossSocket & sock, std::vector<std::string> & argVec )
{
int ret = DIY_OK;
if( !sock.isConnected() )
{
return getError(DIY_SOCK_NOT_CONNECT);
}
ret = sendOrder( sock, 0 );
//sock.close();
ret = handleReply();
return ret;
}
/******************************HelpCommand**********************************************/
int HelpCommand::execute( ossSocket & sock, std::vector<std::string> & argVec )
{
int ret = DIY_OK;
printf("List of classes of commands:\n\n");
printf("%s [server] [port]-- connecting diydb server\n", COMMAND_CONNECT);
printf("%s -- sending a insert command to diydb server\n", COMMAND_INSERT);
printf("%s -- sending a query command to diydb server\n", COMMAND_QUERY);
printf("%s -- sending a delete command to diydb server\n", COMMAND_DELETE);
printf("%s [number]-- sending a test command to diydb server\n", COMMAND_TEST);
printf("%s -- providing current number of record inserting\n", COMMAND_SNAPSHOT);
printf("%s -- quitting command\n\n", COMMAND_QUIT);
printf("Type \"help\" command for help\n");
return ret;
}
命令的实现部分主要在execute函数中,这些实现的命令都在命令工厂的默认构造函数中被注册进了命令工厂。
3.命令工厂
commandFactory.hpp
#ifndef _COMMAND_FACTORY_HPP_
#define _COMMAND_FACTORY_HPP_
#include "command.hpp"
#define COMMAND_ADD(cmdName,cmdClass) { \//将命令名<span style="font-family: Arial, Helvetica, sans-serif; font-size: 12px;">cmdName,和其对应的实现类cmdClass注册到命令工厂中去</span>
ICommand* pObj = new cmdClass(); \
std::string str = cmdName; \
_cmdMap.insert(COMMAND_MAP::value_type(str,pObj)); \
}while(0)
class CommandFactory {
typedef std::map<std::string, ICommand*> COMMAND_MAP;
public:
CommandFactory();
~CommandFactory(){}
void addCommand();
ICommand * getCommandProcesser(const char * pcmd);
private:
COMMAND_MAP _cmdMap;//存放命令名和命令对象的键值对集合
};
#endif
commandFactory.cpp
#include "commandFactory.hpp"
CommandFactory::CommandFactory()
{
addCommand();
}
CommandFactory::addCommand()
{
COMMAND_ADD(COMMAND_INSERT,InsertCommand);
COMMAND_ADD(COMMAND_QUERY,QueryCommand);
COMMAND_ADD(COMMAND_DELETE, DeleteCommand);
COMMAND_ADD(COMMAND_CONNECT,ConnectCommand);
COMMAND_ADD(COMMAND_QUIT, QuitCommand);
COMMAND_ADD(COMMAND_HELP, HelpCommand);
}
CommandFactory::commandAdd(char *cmdName,cmdClass)
{
ICommand* pObj = new cmdClass(); \
std::string str = cmdName; \
_cmdMap.insert(COMMAND_MAP::value_type(str,pObj));
}
//通过用户输入的pCmd来生成一个ICommand
ICommand * CommandFactory::getCommandProcesser(const char * pCmd)
{
ICommand * pProcessor = NULL;
COMMAND_MAP::iterator iter;
iter = _cmdMap.find(pCmd);
if( iter != _cmdMap.end() )
{
pProcessor = iter->second;
}
return pProcessor;
}
五、总结
客户端的框架非常清晰,主要做的事情有:
(1)一个工厂类来管理所有的命令类,任何命令的名字和实现类都以键值对的形式存放在命令工厂的一个map中
(2)所有具体的命令类都继承自ICommand类,这个类主要实现了一些辅助函数,所有具体的命令类都可以重新实现里面的两个方法,execute和handleReply
(3)客户端和服务器之间的数据格式是{int length,bytes data}