首页 热点 业界 科技快讯 数码 电子消费 通信 前沿动态 电商

今日播报!手把手教你实现自定义的应用层协议

2023-06-22 09:56:14 来源 : 面包芯语

扫描关注一起学嵌入式,一起学习,一起成长

1.简述

互联网上充斥着各种各样的网络服务,在对外提供网络服务时,服务端和客户端需要遵循同一套数据通讯协议,才能正常的进行通讯;就好像你跟台湾人沟通用闽南语,跟广东人沟通就用粤语一样。


(资料图)

实现自己的应用功能时,已知的知名协议(http,smtp,ftp等)在安全性、可扩展性等方面不能满足需求,从而需要设计并实现自己的应用层协议。

2.1按编码方式

2.2按协议边界

固定边界协议,能够明确得知一个协议报文的长度,这样的协议易于解析,比如tcp协议。

模糊边界协议,无法明确得知一个协议报文的长度,这样的协议解析较为复杂,通常需要通过某些特定的字节来界定报文是否结束,比如http协议。

3.协议优劣的基本评判标准

高效的,快速的打包解包减少对cpu的占用,高数据压缩率降低对网络带宽的占用。

简单的,易于人的理解、程序的解析。

易于扩展的, 对可预知的变更,有足够的弹性用于扩展。

向后兼容,对于新协议发出的报文,能使用旧协议进行解析,只是新协议支持的新功能不能使用。

4.自定义应用层协议的优缺点

扩展性更好,可以根据业务需求和发展扩展自己的协议,而已知的知名协议不好扩展。

设计难度高,协议需要易扩展,最好能向后向前兼容。

实现繁琐,需要自己实现序列化和反序列化。

5.动手前的预备知识

5.1大小端

计算机系统在存储数据时起始地址是高地址还是低地址。大端,从高地址开始存储。小端,从低地址开始存储。

图解

判断方法,这里以c/c++语言代码为例,使用了c语言中联合体的特性。

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

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

5.5 序列化与反序列化

将计算机语言中的内存对象转换为网络字节流,例如把c语言中的结构体Test转化成uint8_t data[6]字节流。

6.一个例子

6.1 协议设计

本协议采用固定边界+混合编码策略。

6.2 协议实现

talk is easy,just code it,使用c/c++语言来实现。

6.2.1 c/c++语言实现

使用结构体 MyProtoHead 来存储协议头

/*    协议头 */struct MyProtoHead{    uint8_t version;    //协议版本号    uint8_t magic;      //协议魔数    uint16_t server;    //协议复用的服务号,标识协议之上的不同服务    uint32_t len;       //协议长度(协议头长度+变长json协议体长度)};

使用开源的Jsoncpp类来存储协议体

协议消息体

/*    协议消息体 */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);};

解包类

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;                     //当前解析中的协议消息体    queuemMsgQ;              //解析好的协议消息队列    vectormCurReserved;           //未解析的网络字节流    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;}

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. 完整源码与测试

code is easy,just run it.

7.1 源码

#include #include #include #include #include #include #include #include using namespace std;const uint8_t MY_PROTO_MAGIC = 88;const uint32_t MY_PROTO_MAX_SIZE = 10 * 1024 * 1024; //10Mconst 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;                     //当前解析中的协议消息体    queuemMsgQ;              //解析好的协议消息队列    vectormCurReserved;           //未解析的网络字节流    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;}

8.总结

原文:https://my.oschina.net/u/2245781/blog/1622414

觉得文章不错,点击“分享”、“赞”、“在看” 呗!

标签:

相关文章

最近更新
取款机能查多久明细 2023-06-22 09:41:42