手把手教你實現自定義的應用層協議

1.簡述

  • 互聯網上充斥着各類各樣的網絡服務,在對外提供網絡服務時,服務端和客戶端須要遵循同一套數據通信協議,才能正常的進行通信;就好像你跟臺灣人溝通用閩南語,跟廣東人溝通就用粵語同樣。ios

  • 實現本身的應用功能時,已知的知名協議(http,smtp,ftp等)在安全性、可擴展性等方面不能知足需求,從而須要設計並實現本身的應用層協議。c++

2.協議分類

2.1按編碼方式

  • 二進制協議
    好比網絡通訊運輸層中的tcp協議。redis

  • 明文的文本協議
    好比應用層的http、redis協議。算法

  • 混合協議(二進制+明文)
    好比蘋果公司早期的APNs推送協議。json

2.2按協議邊界

  • 固定邊界協議
    可以明確得知一個協議報文的長度,這樣的協議易於解析,好比tcp協議。安全

  • 模糊邊界協議
    沒法明確得知一個協議報文的長度,這樣的協議解析較爲複雜,一般須要經過某些特定的字節來界定報文是否結束,好比http協議。網絡

3.協議優劣的基本評判標準

  • 高效的
    快速的打包解包減小對cpu的佔用,高數據壓縮率下降對網絡帶寬的佔用。tcp

  • 簡單的
    易於人的理解、程序的解析。函數

  • 易於擴展的
    對可預知的變動,有足夠的彈性用於擴展。佈局

  • 容易兼容的

    • 向前兼容,對於舊協議發出的報文,能使用新協議進行解析,只是新協議支持的新功能不能使用。

    • 向後兼容,對於新協議發出的報文,能使用舊協議進行解析,只是新協議支持的新功能不能使用。

4.自定義應用層協議的優缺點

4.1優勢

  • 非知名協議,數據通訊更安全,黑客若是要分析協議的漏洞就必須先破譯你的通信協議。

  • 擴展性更好,能夠根據業務需求和發展擴展本身的協議,而已知的知名協議很差擴展。

4.2缺點

  • 設計難度高,協議須要易擴展,最好能向後向前兼容。

  • 實現繁瑣,須要本身實現序列化和反序列化。

5.動手前的預備知識

5.1大小端

計算機系統在存儲數據時起始地址是高地址仍是低地址。

  • 大端
    從高地址開始存儲。

  • 小端
    從低地址開始存儲。

  • 圖解

  • 判斷
    這裏以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;
}

 

5.2網絡字節序

顧名思義就是數據在網絡傳送的字節流中的起始地址的高低,爲了不在網絡通訊中引入其餘複雜性,網絡字節序統一是大端的。

5.3本地字節序

本地操做系統的大小端,不一樣操做系統可能採用不一樣的字節序。

5.4內存對象與佈局

任何變量,無論是堆變量仍是棧變量都對應着操做系統中的一塊內存,因爲內存對齊的要求程序中的變量並非緊湊存儲的,例如一個c語言的結構體Test在內存中的佈局可能以下圖所示。

struct Test
{
    char a;
    char b;
    int32_t c;
};

5.5序列化與反序列化

  • 將計算機語言中的內存對象轉換爲網絡字節流,例如把c語言中的結構體Test轉化成uint8_t data[6]字節流。

  • 將網絡字節流轉換爲計算機語言中的內存對象,例如把uint8_t data[6]字節流轉化成c語言中的結構體Test。

6.一個例子

6.1 協議設計

本協議採用固定邊界+混合編碼策略。

  • 協議頭
    8字節的定長協議頭。支持版本號,基於魔數的快速校驗,不一樣服務的複用。定長協議頭使協議易於解析且高效。

  • 協議體
    變長json做爲協議體。json使用明文文本編碼,可讀性強、易於擴展、先後兼容、通用的編解碼算法。json協議體爲協議提供了良好的擴展性和兼容性。

  • 協議可視化圖

6.2 協議實現

talk is easy,just code it,使用c/c++語言來實現。

6.2.1c/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;   //當前解析狀態
    };

    6.2.2打包(序列化)

    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;
    }

    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;
        //解析版本號
        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;
    }

    7.完整源碼與測試

#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;
}

7.2運行測試

$ 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"}

8.總結

不到350行的代碼向咱們展現了一個自定義的應用層協議該如何實現,固然這個協議是不夠完善的,還能夠對其完善,好比對協議體進行加密增強協議的安全性等。

相關文章
相關標籤/搜索