1簡(jiǎn)述
互聯(lián)網(wǎng)上充斥著各種各樣的網(wǎng)絡(luò)服務(wù),在對(duì)外提供網(wǎng)絡(luò)服務(wù)時(shí),服務(wù)端和客戶端需要遵循同一套數(shù)據(jù)通訊協(xié)議,才能正常的進(jìn)行通訊;就好像你跟臺(tái)灣人溝通用閩南語(yǔ),跟廣東人溝通就用粵語(yǔ)一樣。
實(shí)現(xiàn)自己的應(yīng)用功能時(shí),已知的知名協(xié)議(http,smtp,ftp等)在安全性、可擴(kuò)展性等方面不能滿足需求,從而需要設(shè)計(jì)并實(shí)現(xiàn)自己的應(yīng)用層協(xié)議。
2.協(xié)議分類
2.1按編碼方式
二進(jìn)制協(xié)議比如網(wǎng)絡(luò)通信運(yùn)輸層中的tcp協(xié)議。
明文的文本協(xié)議比如應(yīng)用層的http、redis協(xié)議。
混合協(xié)議(二進(jìn)制+明文)比如蘋(píng)果公司早期的APNs推送協(xié)議。
2.2按協(xié)議邊界
固定邊界協(xié)議能夠明確得知一個(gè)協(xié)議報(bào)文的長(zhǎng)度,這樣的協(xié)議易于解析,比如tcp協(xié)議。
模糊邊界協(xié)議無(wú)法明確得知一個(gè)協(xié)議報(bào)文的長(zhǎng)度,這樣的協(xié)議解析較為復(fù)雜,通常需要通過(guò)某些特定的字節(jié)來(lái)界定報(bào)文是否結(jié)束,比如http協(xié)議。
3.協(xié)議優(yōu)劣的基本評(píng)判標(biāo)準(zhǔn)
高效的快速的打包解包減少對(duì)cpu的占用,高數(shù)據(jù)壓縮率降低對(duì)網(wǎng)絡(luò)帶寬的占用。
簡(jiǎn)單的易于人的理解、程序的解析。
易于擴(kuò)展的對(duì)可預(yù)知的變更,有足夠的彈性用于擴(kuò)展。
容易兼容的
向前兼容,對(duì)于舊協(xié)議發(fā)出的報(bào)文,能使用新協(xié)議進(jìn)行解析,只是新協(xié)議支持的新功能不能使用。
向后兼容,對(duì)于新協(xié)議發(fā)出的報(bào)文,能使用舊協(xié)議進(jìn)行解析,只是新協(xié)議支持的新功能不能使用。
4.自定義應(yīng)用層協(xié)議的優(yōu)缺點(diǎn)
4.1優(yōu)點(diǎn)
非知名協(xié)議,數(shù)據(jù)通信更安全,黑客如果要分析協(xié)議的漏洞就必須先破譯你的通訊協(xié)議。
擴(kuò)展性更好,可以根據(jù)業(yè)務(wù)需求和發(fā)展擴(kuò)展自己的協(xié)議,而已知的知名協(xié)議不好擴(kuò)展。
4.2缺點(diǎn)
設(shè)計(jì)難度高,協(xié)議需要易擴(kuò)展,最好能向后向前兼容。
實(shí)現(xiàn)繁瑣,需要自己實(shí)現(xiàn)序列化和反序列化。
5.動(dòng)手前的預(yù)備知識(shí)
5.1大小端
計(jì)算機(jī)系統(tǒng)在存儲(chǔ)數(shù)據(jù)時(shí)起始地址是高地址還是低地址。
大端從高地址開(kāi)始存儲(chǔ)。
小端從低地址開(kāi)始存儲(chǔ)。
圖解
判斷這里以c/c++語(yǔ)言代碼為例,使用了c語(yǔ)言中聯(lián)合體的特性。
#include
5.2網(wǎng)絡(luò)字節(jié)序
顧名思義就是數(shù)據(jù)在網(wǎng)絡(luò)傳送的字節(jié)流中的起始地址的高低,為了避免在網(wǎng)絡(luò)通信中引入其他復(fù)雜性,網(wǎng)絡(luò)字節(jié)序統(tǒng)一是大端的。
5.3本地字節(jié)序
本地操作系統(tǒng)的大小端,不同操作系統(tǒng)可能采用不同的字節(jié)序。
5.4內(nèi)存對(duì)象與布局
任何變量,不管是堆變量還是棧變量都對(duì)應(yīng)著操作系統(tǒng)中的一塊內(nèi)存,由于內(nèi)存對(duì)齊的要求程序中的變量并不是緊湊存儲(chǔ)的,例如一個(gè)c語(yǔ)言的結(jié)構(gòu)體Test在內(nèi)存中的布局可能如下圖所示。
struct Test { char a; char b; int32_t c; };
5.5序列化與反序列化
將計(jì)算機(jī)語(yǔ)言中的內(nèi)存對(duì)象轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)流,例如把c語(yǔ)言中的結(jié)構(gòu)體Test轉(zhuǎn)化成uint8_t data[6]字節(jié)流。
將網(wǎng)絡(luò)字節(jié)流轉(zhuǎn)換為計(jì)算機(jī)語(yǔ)言中的內(nèi)存對(duì)象,例如把uint8_t data[6]字節(jié)流轉(zhuǎn)化成c語(yǔ)言中的結(jié)構(gòu)體Test。
6.一個(gè)例子
6.1 協(xié)議設(shè)計(jì)
本協(xié)議采用固定邊界+混合編碼策略。
協(xié)議頭8字節(jié)的定長(zhǎng)協(xié)議頭。支持版本號(hào),基于魔數(shù)的快速校驗(yàn),不同服務(wù)的復(fù)用。定長(zhǎng)協(xié)議頭使協(xié)議易于解析且高效。
協(xié)議體變長(zhǎng)json作為協(xié)議體。json使用明文文本編碼,可讀性強(qiáng)、易于擴(kuò)展、前后兼容、通用的編解碼算法。json協(xié)議體為協(xié)議提供了良好的擴(kuò)展性和兼容性。
協(xié)議可視化圖
6.2 協(xié)議實(shí)現(xiàn)
talk is easy,just code it,使用c/c++語(yǔ)言來(lái)實(shí)現(xiàn)。
6.2.1c/c++語(yǔ)言實(shí)現(xiàn)
使用結(jié)構(gòu)體MyProtoHead來(lái)存儲(chǔ)協(xié)議頭
/* 協(xié)議頭 */ struct MyProtoHead { uint8_t version; //協(xié)議版本號(hào) uint8_t magic; //協(xié)議魔數(shù) uint16_t server; //協(xié)議復(fù)用的服務(wù)號(hào),標(biāo)識(shí)協(xié)議之上的不同服務(wù) uint32_t len; //協(xié)議長(zhǎng)度(協(xié)議頭長(zhǎng)度+變長(zhǎng)json協(xié)議體長(zhǎng)度) };
使用開(kāi)源的Jsoncpp類來(lái)存儲(chǔ)協(xié)議體https://sourceforge.net/proje...
協(xié)議消息體
/* 協(xié)議消息體 */ struct MyProtoMsg { MyProtoHead head; //協(xié)議頭 Json::Value body; //協(xié)議體 };
打包類
/* MyProto打包類 */ class MyProtoEnCode { public: //協(xié)議消息體打包函數(shù) uint8_t * encode(MyProtoMsg * pMsg, uint32_t & len); private: //協(xié)議頭打包函數(shù) void headEncode(uint8_t * pData, MyProtoMsg * pMsg); };
解包類
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; //當(dāng)前解析中的協(xié)議消息體 queue
6.2.2打包(序列化)
void MyProtoEnCode::headEncode(uint8_t * pData, MyProtoMsg * pMsg) { //設(shè)置協(xié)議頭版本號(hào)為1 *pData = 1; ++pData; //設(shè)置協(xié)議頭魔數(shù) *pData = MY_PROTO_MAGIC; ++pData; //設(shè)置協(xié)議服務(wù)號(hào),把head.server本地字節(jié)序轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序 *(uint16_t *)pData = htons(pMsg->head.server); pData += 2; //設(shè)置協(xié)議總長(zhǎng)度,把head.len本地字節(jié)序轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序 *(uint32_t *)pData = htonl(pMsg->head.len); } uint8_t * MyProtoEnCode::encode(MyProtoMsg * pMsg, uint32_t & len) { uint8_t * pData = NULL; Json::FastWriter fWriter; //協(xié)議json體序列化 string bodyStr = fWriter.write(pMsg->body); //計(jì)算協(xié)議消息序列化后的總長(zhǎng)度 len = MY_PROTO_HEAD_SIZE + (uint32_t)bodyStr.size(); pMsg->head.len = len; //申請(qǐng)協(xié)議消息序列化需要的空間 pData = new uint8_t[len]; //打包協(xié)議頭 headEncode(pData, pMsg); //打包協(xié)議體 memcpy(pData + MY_PROTO_HEAD_SIZE, bodyStr.data(), bodyStr.size()); return pData; }
6.2.3解包(反序列化)
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; //解析版本號(hào) mCurMsg.head.version = *pData; pData++; //解析魔數(shù) mCurMsg.head.magic = *pData; pData++; //魔數(shù)不一致,則返回解析失敗 if (MY_PROTO_MAGIC != mCurMsg.head.magic) { return false; } //解析服務(wù)號(hào) mCurMsg.head.server = ntohs(*(uint16_t*)pData); pData+=2; //解析協(xié)議消息體的長(zhǎng)度 mCurMsg.head.len = ntohl(*(uint32_t*)pData); //異常大包,則返回解析失敗 if (mCurMsg.head.len > MY_PROTO_MAX_SIZE) { return false; } //解析指針向前移動(dòng)MY_PROTO_HEAD_SIZE字節(jié) (*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; } //解析指針向前移動(dòng)jsonSize字節(jié) (*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; //把當(dāng)前要解析的網(wǎng)絡(luò)字節(jié)流寫(xiě)入未解析完字節(jié)流之后 while (len--) { mCurReserved.push_back(*curData); ++curData; } curLen = mCurReserved.size(); curData = (uint8_t *)&mCurReserved[0]; //只要還有未解析的網(wǎng)絡(luò)字節(jié)流,就持續(xù)解析 while (curLen > 0) { bool parserBreak = false; //解析協(xié)議頭 if (ON_PARSER_INIT == mCurParserStatus || ON_PARSER_BODY == mCurParserStatus) { if (!parserHead(&curData, curLen, parserLen, parserBreak)) { return false; } if (parserBreak) break; } //解析完協(xié)議頭,解析協(xié)議體 if (ON_PARSER_HAED == mCurParserStatus) { if (!parserBody(&curData, curLen, parserLen, parserBreak)) { return false; } if (parserBreak) break; } if (ON_PARSER_BODY == mCurParserStatus) { //拷貝解析完的消息體放入隊(duì)列中 MyProtoMsg * pMsg = NULL; pMsg = new MyProtoMsg; *pMsg = mCurMsg; mMsgQ.push(pMsg); } } if (parserLen > 0) { //刪除已經(jīng)被解析的網(wǎng)絡(luò)字節(jié)流 mCurReserved.erase(mCurReserved.begin(), mCurReserved.begin() + parserLen); } return true; }
7.完整源碼與測(cè)試
code is easy,just run it.
7.1源碼
#include
7.2運(yùn)行測(cè)試
8.總結(jié)
不到350行的代碼向我們展示了一個(gè)自定義的應(yīng)用層協(xié)議該如何實(shí)現(xiàn),當(dāng)然這個(gè)協(xié)議是不夠完善的,還可以對(duì)其完善,比如對(duì)協(xié)議體進(jìn)行加密加強(qiáng)協(xié)議的安全性等。
-
互聯(lián)網(wǎng)
+關(guān)注
關(guān)注
54文章
11202瀏覽量
104402 -
HTTP
+關(guān)注
關(guān)注
0文章
513瀏覽量
31842 -
TCP
+關(guān)注
關(guān)注
8文章
1390瀏覽量
79653 -
應(yīng)用層
+關(guān)注
關(guān)注
0文章
46瀏覽量
11576
原文標(biāo)題:如何高效實(shí)現(xiàn)自定義的應(yīng)用層協(xié)議
文章出處:【微信號(hào):技術(shù)讓夢(mèng)想更偉大,微信公眾號(hào):技術(shù)讓夢(mèng)想更偉大】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
基于自定義協(xié)議的網(wǎng)絡(luò)地理信息系統(tǒng)
如何在LabVIEW中實(shí)現(xiàn)自定義控件

單片機(jī)學(xué)習(xí)筆記————51單片機(jī)實(shí)現(xiàn)常用的自定義串口通訊協(xié)議

C#與STM32自定義通信協(xié)議

基于HAL庫(kù)的USB自定義HID設(shè)備實(shí)現(xiàn)

自定義視圖組件教程案例
深入理解RPC自定義網(wǎng)絡(luò)協(xié)議
ArkUI如何自定義彈窗(eTS)
ESP32上的自定義UART協(xié)議開(kāi)源

自定義算子開(kāi)發(fā)

TSMaster 自定義 LIN 調(diào)度表編程指導(dǎo)

評(píng)論