解码websocket (c++)

来源:互联网 发布:linux jdk 安装bad 编辑:程序博客网 时间:2024/06/03 11:30

websocket 是web常用的协议,可用来支持自定义协议。

以下是用c++根据websocket握手协议websocket协议格式来处理websocket 协议解码

(1)第一步,websocket升级协议

判断是否是http协议,并且是websocket的升级握手协议

E_CODEC_STATUS CodecWebSocketJson::Decode(tagConnectionAttr* pConn,MsgHead& oMsgHead, MsgBody& oMsgBody){    if (eConnectStatus_init == pConn->ucConnectStatus)//必须以http请求初始化握手协议,否则不是WebSocketJson协议    {        if (pConn->pRecvBuff->ReadableBytes() >= 5)//目前只支持Get post 的初始化握手协议        {            //响应的是("HTTP/", 5)            //请求的是("GET ", 4)  ("POST ", 5)            const char* pReadAddr = pConn->pRecvBuff->GetRawReadBuffer();//处理http请求            if ((memcmp(pReadAddr, "GET ", 4) == 0)                            || memcmp(pReadAddr, "POST ", 5) == 0)            {                LOG4_TRACE("%s() pBuff->ReadableBytes() = %u:%s", __FUNCTION__,                                pConn->pRecvBuff->ReadableBytes(), pConn->pRecvBuff->ToString().c_str());                HttpMsg oHttpMsg;                E_CODEC_STATUS eCodecStatus = Decode(pConn->pRecvBuff, oHttpMsg);                if (CODEC_STATUS_OK == eCodecStatus)                {                    std::string upgrade;                    for (int i = 0; i < oHttpMsg.headers_size(); ++i)                    {                        if (std::string("x-cmd")                                        == oHttpMsg.headers(i).header_name()                                        || std::string("x-CMD")                                                        == oHttpMsg.headers(i).header_name()                                        || std::string("x-Cmd")                                                        == oHttpMsg.headers(i).header_name())                        {                            oMsgHead.set_cmd(atoi(oHttpMsg.headers(i).header_value().c_str()));                        }                        else if (std::string("x-seq")                                        == oHttpMsg.headers(i).header_name()                                        || std::string("x-SEQ")                                                        == oHttpMsg.headers(i).header_name()                                        || std::string("x-Seq")                                                        == oHttpMsg.headers(i).header_name())                        {                            oMsgHead.set_seq(strtoul(oHttpMsg.headers(i).header_value().c_str(),NULL, 10));                        }                        else if (std::string("Upgrade") == oHttpMsg.headers(i).header_name())                        {                            upgrade = oHttpMsg.headers(i).header_value();                        }                    }                    if(strcasecmp(upgrade.c_str(),"websocket") == 0)//websocket升级握手消息                    {                        pConn->ucConnectStatus = eConnectStatus_ok;//握手协议接收成功                        oMsgBody.set_body(oHttpMsg.SerializeAsString());                        oMsgHead.set_msgbody_len(oMsgBody.ByteSize());                    }                    else                    {                        LOG4_DEBUG("%s() CodecWebSocketJson need to init connect status with http head Upgrade for websocket",__FUNCTION__);                        return CODEC_STATUS_ERR;                    }                }                return eCodecStatus;            }        }        LOG4_DEBUG("%s() CodecWebSocketJson need to init connect status with http post or get request",__FUNCTION__);        return CODEC_STATUS_ERR;    }    return Decode(pConn->pRecvBuff,oMsgHead,oMsgBody);}

(2)第二部,http协议

解码http

//把缓存pBuff 中的内容换序列化到oHttpMsgE_CODEC_STATUS CodecWebSocketJson::Decode(loss::CBuffer* pBuff,                HttpMsg& oHttpMsg){    LOG4_TRACE("%s()", __FUNCTION__);    if (pBuff->ReadableBytes() == 0)    {        LOG4_DEBUG("no data...");        return (CODEC_STATUS_PAUSE);    }    loss::CBuffer oHttpBuff;        // 用于debug输出Decode前的http协议    oHttpBuff.Write(pBuff->GetRawReadBuffer(), pBuff->ReadableBytes());    oHttpBuff.WriteByte('\0');    LOG4_TRACE("%s", oHttpBuff.GetRawReadBuffer());    //反序列化时构造oHttpMsg    m_parser_setting.on_message_begin = OnMessageBegin;    m_parser_setting.on_url = OnUrl;    m_parser_setting.on_status = OnStatus;    m_parser_setting.on_header_field = OnHeaderField;    m_parser_setting.on_header_value = OnHeaderValue;    m_parser_setting.on_headers_complete = OnHeadersComplete;    m_parser_setting.on_body = OnBody;    m_parser_setting.on_message_complete = OnMessageComplete;    m_parser_setting.on_chunk_header = OnChunkHeader;    m_parser_setting.on_chunk_complete = OnChunkComplete;    m_parser.data = &oHttpMsg;    http_parser_init(&m_parser, HTTP_REQUEST);    int iReadIdx = pBuff->GetReadIndex();    size_t uiReadableBytes = pBuff->ReadableBytes();    size_t uiLen = http_parser_execute(&m_parser, &m_parser_setting,                    pBuff->GetRawReadBuffer(), uiReadableBytes);    if (!oHttpMsg.is_decoding())    {        if (m_parser.http_errno != HPE_OK)        {            LOG4_ERROR("Failed to parse http message for cause:%s",                            http_errno_name((http_errno )m_parser.http_errno));            return (CODEC_STATUS_ERR);        }        pBuff->AdvanceReadIndex(uiLen);    }    else    {        LOG4_TRACE("decoding...");        return (CODEC_STATUS_PAUSE);    }    for (int i = 0; i < oHttpMsg.headers_size(); ++i)    {        if (std::string("Content-Encoding") == oHttpMsg.headers(i).header_name()                        && std::string("gzip")                                        == oHttpMsg.headers(i).header_value())        {            std::string strData;            if (Gunzip(oHttpMsg.body(), strData))            {                oHttpMsg.set_body(strData);            }            else            {                LOG4_ERROR("guzip error!");                return (CODEC_STATUS_ERR);            }        }        else if ("X-Real-IP" == oHttpMsg.headers(i).header_name())        {            LOG4_DEBUG("X-Real-IP: %s",                            oHttpMsg.headers(i).header_value().c_str());        }        else if ("X-Forwarded-For" == oHttpMsg.headers(i).header_name())        {            LOG4_DEBUG("X-Forwarded-For: %s",                            oHttpMsg.headers(i).header_value().c_str());        }    }    pBuff->Compact(8192);    LOG4_DEBUG("%s", ToString(oHttpMsg).c_str());    return (CODEC_STATUS_OK);}


(3)第三步,解码websocket协议

websocket有自带消息头

websocket消息体中可以包含自定义消息头和自定义消息体

websocket协议格式如下:


 前两个固定字节:

1)第一个字节
 FIN:1位
表示这是消息的最后一帧(结束帧),一个消息由一个或多个数据帧构成。若消息由一帧构成,起始帧即结束帧。

 RSV1,RSV2,RSV3:3位
如果未定义扩展,各位是0;如果定义了扩展,即为非0值。如果接收的帧此处非0,扩展中却没有该值的定义,那么关闭连接。

 OPCODE:4位
解释PayloadData,如果接收到未知的opcode,接收端必须关闭连接。
 0x0表示附加数据帧
 0x1表示文本数据帧
 0x2表示二进制数据帧
 0x3-7暂时无定义,为以后的非控制帧保留
 0x8表示连接关闭
 0x9表示ping
 0xA表示pong
 0xB-F暂时无定义,为以后的控制帧保留

2) 第二个字节

pay len :

如果值是127,则后面8个字节的无符号整型数的值是payload的真实长度。

如果值是126,则后面2个字节的无符号整型数的值是payload的真实长度。

如果其值在0-125,则是payload的真实长度

3)字节拓展长度

4)掩码

4字节掩码,

5)payload data

负载数据,接收的负载数据payload data 需要异或掩码中的字节才是真正是负载数据


解码websocket协议

 const uint8 WEBSOCKET_OPCODE                = (0x0F);
const uint8 WEBSOCKET_FRAME_CONTINUE        = (0x0);
const uint8 WEBSOCKET_FRAME_TEXT            = (0x1);
const uint8 WEBSOCKET_FRAME_BINARY          = (0x2);
const uint8 WEBSOCKET_FRAME_CLOSE           = (0x8);
const uint8 WEBSOCKET_FRAME_PING            = (0x9);
const uint8 WEBSOCKET_FRAME_PONG            = (0xA);


MASK & PAYLOAD_LEN
const uint8 WEBSOCKET_MASK                  = 0x80;
const uint8 WEBSOCKET_PAYLOAD_LEN           = 0x7F;
const uint8 WEBSOCKET_PAYLOAD_LEN_UINT16    = 126;
const uint8 WEBSOCKET_PAYLOAD_LEN_UINT64    = 127;


E_CODEC_STATUS CodecWebSocketJson::Decode(loss::CBuffer* pBuff,                MsgHead& oMsgHead, MsgBody& oMsgBody){    if (pBuff->ReadableBytes() >= 2)//处理websocket    {        int iReadIdx = pBuff->GetReadIndex();        uint8 ucFirstByte = 0;        uint8 ucSecondByte = 0;        pBuff->Read(&ucFirstByte, 1);        pBuff->Read(&ucSecondByte, 1);        if (!(WEBSOCKET_MASK & ucSecondByte))    //0x80 客户端发来的必须包含掩码        {            LOG4_ERROR("a masked frame MUST have the field frame-masked set to 1 when client to server!");            return (CODEC_STATUS_ERR);        }        if (0 == (WEBSOCKET_PAYLOAD_LEN & ucSecondByte)) //0x7F,Payload len长度为0,为ping pong 消息        {            if (WEBSOCKET_FRAME_PING & ucFirstByte)    //9 ping 消息            {                oMsgHead.set_cmd(0);                oMsgHead.set_seq(0);                oMsgHead.set_msgbody_len(0);                LOG4_TRACE("ping msg");            }            else            {                oMsgHead.set_cmd(1);                oMsgHead.set_seq(0);                oMsgHead.set_msgbody_len(0);                LOG4_TRACE("pong msg");            }            return (CODEC_STATUS_PAUSE);//ping pong消息目前服务器不处理        }        uint32 uiPayload = 0;        char szMaskKey[4] = { 0 };        {//Payloadlen  szMaskKey            if (WEBSOCKET_PAYLOAD_LEN_UINT64                            == (WEBSOCKET_PAYLOAD_LEN & ucSecondByte)) //127 == ucSecondByte & 0x7F            {                //如果值是127,则后面8个字节的无符号整型数的值是payload的真实长度。注意,网络字节序,需要转换。                uint64 ullPayload = 0;                if (pBuff->ReadableBytes() <= 12)   // 8 + 4                {                    pBuff->SetReadIndex(iReadIdx);//回退到原来的读坐标                    return (CODEC_STATUS_PAUSE);                }                pBuff->Read(&ullPayload, 8);                pBuff->Read(&szMaskKey, 4);                uiPayload = (uint32) ntohll(ullPayload);            }            else if (WEBSOCKET_PAYLOAD_LEN_UINT16                            == (WEBSOCKET_PAYLOAD_LEN & ucSecondByte)) //126 == ucSecondByte & 0x7F            {                //如果值是126,则后面2个字节的无符号整型数的值是payload的真实长度。注意,网络字节序,需要转换。                uint16 unPayload = 0;                if (pBuff->ReadableBytes() <= 6)   // 2 + 4                {                    pBuff->SetReadIndex(iReadIdx);                    return (CODEC_STATUS_PAUSE);                }                pBuff->Read(&unPayload, 2);                pBuff->Read(&szMaskKey, 4);                uiPayload = (uint32) ntohs(unPayload);            }            else            {                //如果其值在0-125,则是payload的真实长度                uint8 ucPayload = 0;                if (pBuff->ReadableBytes() <= 4)   // 4                {                    pBuff->SetReadIndex(iReadIdx);                    return (CODEC_STATUS_PAUSE);                }                ucPayload = WEBSOCKET_PAYLOAD_LEN & ucSecondByte; // payload len                pBuff->Read(&szMaskKey, 4);                uiPayload = ucPayload;            }            if (pBuff->ReadableBytes() < uiPayload)            {                pBuff->SetReadIndex(iReadIdx);                LOG4_TRACE("wait for data.ReadableBytes:%u,Payload:%u",                                pBuff->ReadableBytes(),uiPayload);                return (CODEC_STATUS_PAUSE);            }            LOG4_TRACE("uiPayload %llu", uiPayload);        }        {//payload data            char cData;            const char* pRawData = pBuff->GetRawReadBuffer();            for (int i = pBuff->GetReadIndex(), j = 0; j < uiPayload; ++i, ++j)            {                cData = pRawData[j] ^ szMaskKey[j % 4];                pBuff->SetBytes(&cData, 1, i);            }        }        //后面开始处理自定义数据包头(如果有包头的则需要处理),和自定义消息包体        。。。。。。    }    else    {        return (CODEC_STATUS_PAUSE);    }}



原创粉丝点击