1. 程式人生 > >diy資料庫(八)--客戶端和伺服器之間的通訊協議

diy資料庫(八)--客戶端和伺服器之間的通訊協議

一、什麼是通訊協議

通訊協議是指雙方實體完成通訊或服務所必須遵循的規則和約定。

二、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       1

struct 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、如果在通訊時直接傳結構體資料,則要注意位元組對齊機制,所以要合理安排結構體中屬性的長度的排列順序。