互聯網上充斥着各類各樣的網絡服務,在對外提供網絡服務時,服務端和客戶端須要遵循同一套數據通信協議,才能正常的進行通信;就好像你跟臺灣人溝通用閩南語,跟廣東人溝通就用粵語同樣。ios
實現本身的應用功能時,已知的知名協議(http,smtp,ftp等)在安全性、可擴展性等方面不能知足需求,從而須要設計並實現本身的應用層協議。c++
二進制協議
好比網絡通訊運輸層中的tcp協議。redis
明文的文本協議
好比應用層的http、redis協議。算法
混合協議(二進制+明文)
好比蘋果公司早期的APNs推送協議。json
固定邊界協議
可以明確得知一個協議報文的長度,這樣的協議易於解析,好比tcp協議。安全
模糊邊界協議
沒法明確得知一個協議報文的長度,這樣的協議解析較爲複雜,一般須要經過某些特定的字節來界定報文是否結束,好比http協議。網絡
高效的
快速的打包解包減小對cpu的佔用,高數據壓縮率下降對網絡帶寬的佔用。tcp
簡單的
易於人的理解、程序的解析。函數
易於擴展的
對可預知的變動,有足夠的彈性用於擴展。佈局
容易兼容的
向前兼容,對於舊協議發出的報文,能使用新協議進行解析,只是新協議支持的新功能不能使用。
向後兼容,對於新協議發出的報文,能使用舊協議進行解析,只是新協議支持的新功能不能使用。
非知名協議,數據通訊更安全,黑客若是要分析協議的漏洞就必須先破譯你的通信協議。
擴展性更好,能夠根據業務需求和發展擴展本身的協議,而已知的知名協議很差擴展。
設計難度高,協議須要易擴展,最好能向後向前兼容。
實現繁瑣,須要本身實現序列化和反序列化。
計算機系統在存儲數據時起始地址是高地址仍是低地址。
大端
從高地址開始存儲。
小端
從低地址開始存儲。
圖解
判斷
這裏以c/c++語言代碼爲例,使用了c語言中聯合體的特性。
#include <stdint.h> #include <iostream> using namespace std; bool bigCheck() { union Check { char a; uint32_t data; }; Check c; c.data = 1; if (1 == c.a) { return false; } return true; } int main() { if (bigCheck()) { cout << "big" << endl; } else { cout << "small" << endl; } return 0; }
顧名思義就是數據在網絡傳送的字節流中的起始地址的高低,爲了不在網絡通訊中引入其餘複雜性,網絡字節序統一是大端的。
本地操做系統的大小端,不一樣操做系統可能採用不一樣的字節序。
任何變量,無論是堆變量仍是棧變量都對應着操做系統中的一塊內存,因爲內存對齊的要求程序中的變量並非緊湊存儲的,例如一個c語言的結構體Test在內存中的佈局可能以下圖所示。
struct Test { char a; char b; int32_t c; };
將計算機語言中的內存對象轉換爲網絡字節流,例如把c語言中的結構體Test轉化成uint8_t data[6]字節流。
將網絡字節流轉換爲計算機語言中的內存對象,例如把uint8_t data[6]字節流轉化成c語言中的結構體Test。
6.一個例子
本協議採用固定邊界+混合編碼策略。
協議頭
8字節的定長協議頭。支持版本號,基於魔數的快速校驗,不一樣服務的複用。定長協議頭使協議易於解析且高效。
協議體
變長json做爲協議體。json使用明文文本編碼,可讀性強、易於擴展、先後兼容、通用的編解碼算法。json協議體爲協議提供了良好的擴展性和兼容性。
協議可視化圖
6.2 協議實現
talk is easy,just code it,使用c/c++語言來實現。
使用結構體MyProtoHead來存儲協議頭
/* 協議頭 */ struct MyProtoHead { uint8_t version; //協議版本號 uint8_t magic; //協議魔數 uint16_t server; //協議複用的服務號,標識協議之上的不一樣服務 uint32_t len; //協議長度(協議頭長度+變長json協議體長度) };
使用開源的Jsoncpp類來存儲協議體
https://sourceforge.net/proje...
協議消息體
/* 協議消息體 */ struct MyProtoMsg { MyProtoHead head; //協議頭 Json::Value body; //協議體 };
打包類
/* MyProto打包類 */ class MyProtoEnCode { public: //協議消息體打包函數 uint8_t * encode(MyProtoMsg * pMsg, uint32_t & len); private: //協議頭打包函數 void headEncode(uint8_t * pData, MyProtoMsg * pMsg); };
解包類
code is easy,just run it.
typedef enum MyProtoParserStatus { ON_PARSER_INIT = 0, ON_PARSER_HAED = 1, ON_PARSER_BODY = 2, }MyProtoParserStatus; /* MyProto解包類 */ class MyProtoDeCode { public: void init(); void clear(); bool parser(void * data, size_t len); bool empty(); MyProtoMsg * front(); void pop(); private: bool parserHead(uint8_t ** curData, uint32_t & curLen, uint32_t & parserLen, bool & parserBreak); bool parserBody(uint8_t ** curData, uint32_t & curLen, uint32_t & parserLen, bool & parserBreak); private: MyProtoMsg mCurMsg; //當前解析中的協議消息體 queue<MyProtoMsg *> mMsgQ; //解析好的協議消息隊列 vector<uint8_t> mCurReserved; //未解析的網絡字節流 MyProtoParserStatus mCurParserStatus; //當前解析狀態 };
void MyProtoEnCode::headEncode(uint8_t * pData, MyProtoMsg * pMsg) { //設置協議頭版本號爲1 *pData = 1; ++pData; //設置協議頭魔數 *pData = MY_PROTO_MAGIC; ++pData; //設置協議服務號,把head.server本地字節序轉換爲網絡字節序 *(uint16_t *)pData = htons(pMsg->head.server); pData += 2; //設置協議總長度,把head.len本地字節序轉換爲網絡字節序 *(uint32_t *)pData = htonl(pMsg->head.len); } uint8_t * MyProtoEnCode::encode(MyProtoMsg * pMsg, uint32_t & len) { uint8_t * pData = NULL; Json::FastWriter fWriter; //協議json體序列化 string bodyStr = fWriter.write(pMsg->body); //計算協議消息序列化後的總長度 len = MY_PROTO_HEAD_SIZE + (uint32_t)bodyStr.size(); pMsg->head.len = len; //申請協議消息序列化須要的空間 pData = new uint8_t[len]; //打包協議頭 headEncode(pData, pMsg); //打包協議體 memcpy(pData + MY_PROTO_HEAD_SIZE, bodyStr.data(), bodyStr.size()); return pData; }
bool MyProtoDeCode::parserHead(uint8_t ** curData, uint32_t & curLen, uint32_t & parserLen, bool & parserBreak) { parserBreak = false; if (curLen < MY_PROTO_HEAD_SIZE) { parserBreak = true; //終止解析 return true; } uint8_t * pData = *curData; //解析版本號 mCurMsg.head.version = *pData; pData++; //解析魔數 mCurMsg.head.magic = *pData; pData++; //魔數不一致,則返回解析失敗 if (MY_PROTO_MAGIC != mCurMsg.head.magic) { return false; } //解析服務號 mCurMsg.head.server = ntohs(*(uint16_t*)pData); pData+=2; //解析協議消息體的長度 mCurMsg.head.len = ntohl(*(uint32_t*)pData); //異常大包,則返回解析失敗 if (mCurMsg.head.len > MY_PROTO_MAX_SIZE) { return false; } //解析指針向前移動MY_PROTO_HEAD_SIZE字節 (*curData) += MY_PROTO_HEAD_SIZE; curLen -= MY_PROTO_HEAD_SIZE; parserLen += MY_PROTO_HEAD_SIZE; mCurParserStatus = ON_PARSER_HAED; return true; } bool MyProtoDeCode::parserBody(uint8_t ** curData, uint32_t & curLen, uint32_t & parserLen, bool & parserBreak) { parserBreak = false; uint32_t jsonSize = mCurMsg.head.len - MY_PROTO_HEAD_SIZE; if (curLen < jsonSize) { parserBreak = true; //終止解析 return true; } Json::Reader reader; //json解析類 if (!reader.parse((char *)(*curData), (char *)((*curData) + jsonSize), mCurMsg.body, false)) { return false; } //解析指針向前移動jsonSize字節 (*curData) += jsonSize; curLen -= jsonSize; parserLen += jsonSize; mCurParserStatus = ON_PARSER_BODY; return true; } bool MyProtoDeCode::parser(void * data, size_t len) { if (len <= 0) { return false; } uint32_t curLen = 0; uint32_t parserLen = 0; uint8_t * curData = NULL; curData = (uint8_t *)data; //把當前要解析的網絡字節流寫入未解析完字節流以後 while (len--) { mCurReserved.push_back(*curData); ++curData; } curLen = mCurReserved.size(); curData = (uint8_t *)&mCurReserved[0]; //只要還有未解析的網絡字節流,就持續解析 while (curLen > 0) { bool parserBreak = false; //解析協議頭 if (ON_PARSER_INIT == mCurParserStatus || ON_PARSER_BODY == mCurParserStatus) { if (!parserHead(&curData, curLen, parserLen, parserBreak)) { return false; } if (parserBreak) break; } //解析完協議頭,解析協議體 if (ON_PARSER_HAED == mCurParserStatus) { if (!parserBody(&curData, curLen, parserLen, parserBreak)) { return false; } if (parserBreak) break; } if (ON_PARSER_BODY == mCurParserStatus) { //拷貝解析完的消息體放入隊列中 MyProtoMsg * pMsg = NULL; pMsg = new MyProtoMsg; *pMsg = mCurMsg; mMsgQ.push(pMsg); } } if (parserLen > 0) { //刪除已經被解析的網絡字節流 mCurReserved.erase(mCurReserved.begin(), mCurReserved.begin() + parserLen); } return true; }
#include <stdint.h> #include <stdio.h> #include <queue> #include <vector> #include <iostream> #include <string.h> #include <jsoncpp/json/json.h> #include <arpa/inet.h> using namespace std; const uint8_t MY_PROTO_MAGIC = 88; const uint32_t MY_PROTO_MAX_SIZE = 10 * 1024 * 1024; //10M const uint32_t MY_PROTO_HEAD_SIZE = 8; typedef enum MyProtoParserStatus { ON_PARSER_INIT = 0, ON_PARSER_HAED = 1, ON_PARSER_BODY = 2, }MyProtoParserStatus; /* 協議頭 */ struct MyProtoHead { uint8_t version; //協議版本號 uint8_t magic; //協議魔數 uint16_t server; //協議複用的服務號,標識協議之上的不一樣服務 uint32_t len; //協議長度(協議頭長度+變長json協議體長度) }; /* 協議消息體 */ struct MyProtoMsg { MyProtoHead head; //協議頭 Json::Value body; //協議體 }; void myProtoMsgPrint(MyProtoMsg & msg) { string jsonStr = ""; Json::FastWriter fWriter; jsonStr = fWriter.write(msg.body); printf("Head[version=%d,magic=%d,server=%d,len=%d]\n" "Body:%s", msg.head.version, msg.head.magic, msg.head.server, msg.head.len, jsonStr.c_str()); } /* MyProto打包類 */ class MyProtoEnCode { public: //協議消息體打包函數 uint8_t * encode(MyProtoMsg * pMsg, uint32_t & len); private: //協議頭打包函數 void headEncode(uint8_t * pData, MyProtoMsg * pMsg); }; void MyProtoEnCode::headEncode(uint8_t * pData, MyProtoMsg * pMsg) { //設置協議頭版本號爲1 *pData = 1; ++pData; //設置協議頭魔數 *pData = MY_PROTO_MAGIC; ++pData; //設置協議服務號,把head.server本地字節序轉換爲網絡字節序 *(uint16_t *)pData = htons(pMsg->head.server); pData += 2; //設置協議總長度,把head.len本地字節序轉換爲網絡字節序 *(uint32_t *)pData = htonl(pMsg->head.len); } uint8_t * MyProtoEnCode::encode(MyProtoMsg * pMsg, uint32_t & len) { uint8_t * pData = NULL; Json::FastWriter fWriter; //協議json體序列化 string bodyStr = fWriter.write(pMsg->body); //計算協議消息序列化後的總長度 len = MY_PROTO_HEAD_SIZE + (uint32_t)bodyStr.size(); pMsg->head.len = len; //申請協議消息序列化須要的空間 pData = new uint8_t[len]; //打包協議頭 headEncode(pData, pMsg); //打包協議體 memcpy(pData + MY_PROTO_HEAD_SIZE, bodyStr.data(), bodyStr.size()); return pData; } /* MyProto解包類 */ class MyProtoDeCode { public: void init(); void clear(); bool parser(void * data, size_t len); bool empty(); MyProtoMsg * front(); void pop(); private: bool parserHead(uint8_t ** curData, uint32_t & curLen, uint32_t & parserLen, bool & parserBreak); bool parserBody(uint8_t ** curData, uint32_t & curLen, uint32_t & parserLen, bool & parserBreak); private: MyProtoMsg mCurMsg; //當前解析中的協議消息體 queue<MyProtoMsg *> mMsgQ; //解析好的協議消息隊列 vector<uint8_t> mCurReserved; //未解析的網絡字節流 MyProtoParserStatus mCurParserStatus; //當前解析狀態 }; void MyProtoDeCode::init() { mCurParserStatus = ON_PARSER_INIT; } void MyProtoDeCode::clear() { MyProtoMsg * pMsg = NULL; while (!mMsgQ.empty()) { pMsg = mMsgQ.front(); delete pMsg; mMsgQ.pop(); } } bool MyProtoDeCode::parserHead(uint8_t ** curData, uint32_t & curLen, uint32_t & parserLen, bool & parserBreak) { parserBreak = false; if (curLen < MY_PROTO_HEAD_SIZE) { parserBreak = true; //終止解析 return true; } uint8_t * pData = *curData; //解析版本號 mCurMsg.head.version = *pData; pData++; //解析魔數 mCurMsg.head.magic = *pData; pData++; //魔數不一致,則返回解析失敗 if (MY_PROTO_MAGIC != mCurMsg.head.magic) { return false; } //解析服務號 mCurMsg.head.server = ntohs(*(uint16_t*)pData); pData+=2; //解析協議消息體的長度 mCurMsg.head.len = ntohl(*(uint32_t*)pData); //異常大包,則返回解析失敗 if (mCurMsg.head.len > MY_PROTO_MAX_SIZE) { return false; } //解析指針向前移動MY_PROTO_HEAD_SIZE字節 (*curData) += MY_PROTO_HEAD_SIZE; curLen -= MY_PROTO_HEAD_SIZE; parserLen += MY_PROTO_HEAD_SIZE; mCurParserStatus = ON_PARSER_HAED; return true; } bool MyProtoDeCode::parserBody(uint8_t ** curData, uint32_t & curLen, uint32_t & parserLen, bool & parserBreak) { parserBreak = false; uint32_t jsonSize = mCurMsg.head.len - MY_PROTO_HEAD_SIZE; if (curLen < jsonSize) { parserBreak = true; //終止解析 return true; } Json::Reader reader; //json解析類 if (!reader.parse((char *)(*curData), (char *)((*curData) + jsonSize), mCurMsg.body, false)) { return false; } //解析指針向前移動jsonSize字節 (*curData) += jsonSize; curLen -= jsonSize; parserLen += jsonSize; mCurParserStatus = ON_PARSER_BODY; return true; } bool MyProtoDeCode::parser(void * data, size_t len) { if (len <= 0) { return false; } uint32_t curLen = 0; uint32_t parserLen = 0; uint8_t * curData = NULL; curData = (uint8_t *)data; //把當前要解析的網絡字節流寫入未解析完字節流以後 while (len--) { mCurReserved.push_back(*curData); ++curData; } curLen = mCurReserved.size(); curData = (uint8_t *)&mCurReserved[0]; //只要還有未解析的網絡字節流,就持續解析 while (curLen > 0) { bool parserBreak = false; //解析協議頭 if (ON_PARSER_INIT == mCurParserStatus || ON_PARSER_BODY == mCurParserStatus) { if (!parserHead(&curData, curLen, parserLen, parserBreak)) { return false; } if (parserBreak) break; } //解析完協議頭,解析協議體 if (ON_PARSER_HAED == mCurParserStatus) { if (!parserBody(&curData, curLen, parserLen, parserBreak)) { return false; } if (parserBreak) break; } if (ON_PARSER_BODY == mCurParserStatus) { //拷貝解析完的消息體放入隊列中 MyProtoMsg * pMsg = NULL; pMsg = new MyProtoMsg; *pMsg = mCurMsg; mMsgQ.push(pMsg); } } if (parserLen > 0) { //刪除已經被解析的網絡字節流 mCurReserved.erase(mCurReserved.begin(), mCurReserved.begin() + parserLen); } return true; } bool MyProtoDeCode::empty() { return mMsgQ.empty(); } MyProtoMsg * MyProtoDeCode::front() { MyProtoMsg * pMsg = NULL; pMsg = mMsgQ.front(); return pMsg; } void MyProtoDeCode::pop() { mMsgQ.pop(); } int main() { uint32_t len = 0; uint8_t * pData = NULL; MyProtoMsg msg1; MyProtoMsg msg2; MyProtoDeCode myDecode; MyProtoEnCode myEncode; msg1.head.server = 1; msg1.body["op"] = "set"; msg1.body["key"] = "id"; msg1.body["value"] = "9856"; msg2.head.server = 2; msg2.body["op"] = "get"; msg2.body["key"] = "id"; myDecode.init(); pData = myEncode.encode(&msg1, len); if (!myDecode.parser(pData, len)) { cout << "parser falied!" << endl; } else { cout << "msg1 parser successful!" << endl; } pData = myEncode.encode(&msg2, len); if (!myDecode.parser(pData, len)) { cout << "parser falied!" << endl; } else { cout << "msg2 parser successful!" << endl; } MyProtoMsg * pMsg = NULL; while (!myDecode.empty()) { pMsg = myDecode.front(); myProtoMsgPrint(*pMsg); myDecode.pop(); } return 0; }
$ sudo apt-get install libjsoncpp-dev libjsoncpp1 //安裝json開發庫 $ g++ -o myprotest myprotest.cpp -ljsoncpp $ ./myprotest msg1 parser successful! msg2 parser successful! Head[version=1,magic=88,server=1,len=47] Body:{"key":"id","op":"set","value":"9856"} Head[version=1,magic=88,server=2,len=32] Body:{"key":"id","op":"get"}
不到350行的代碼向咱們展現了一個自定義的應用層協議該如何實現,固然這個協議是不夠完善的,還能夠對其完善,好比對協議體進行加密增強協議的安全性等。