• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

c++服务器websocket支持

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

介绍

目前公司服务器是c++ tcp的网络架构,现在想用这套做h5游戏,所以要扩展支持websocket通信。

那么什么是websocket?它和tcp有什么区别?这些随便一搜一大把,这里就不再科普达。通俗简单点讲websocket就是山寨版的tcp,它底层实现就是tcp,唯一的区别就是网络传输时websocket协议前面多了个标志它的包头信息。去掉前面这部分包头剩下的就和普通tcp一样了。

那么讲到这里,在现有tcp上怎么扩展支持websocket?其实就很简单呢!既然它和tcp协议上就只是多了个包头部分,那么我们的任务其实主要就是怎么解析这个包头信息了。

最后,还有一个需要注意的细节是websocket协议前后端建立连接前需要一次握手协议,触发时机是client发起connet连接请求时,会向server发送这条握手的协议,server收到后要回复client,这样就建立了连接了。

好~闲话少说,下面直接上实现代码…

实现

1.握手。 
client第一次connet连接会发起握手协议,server在recv接收处解析,判断如果是websocket的握手协议,那么同样组装好特定格式包头回复给client,建立连接。

  • 判断是不是websocket协议
bool isWSHandShake(std::string &request)
{
    size_t i = request.find("GET");
    if(i == std::string::npos){
        return false;
    }
    return true;
}
  • 如果是,解析握手协议重新组装准备send回复给client
bool wsHandshake(std::string &request, std::string &response)
{
    //得到客户端请求信息的key
    std::string tempKey = request;
    size_t i = tempKey.find("Sec-WebSocket-Key");
    if(i == std::string::npos){
        return false;
    }
    tempKey = tempKey.substr(i + 19, 24);

    //拼接协议返回给客户端
    response = "HTTP/1.1 101 Switching Protocols\r\n";  
    response += "Connection: upgrade\r\n";  
    response += "Sec-WebSocket-Accept: ";  

    std::string realKey = tempKey;
    realKey += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
    SHA1 sha;
    unsigned int message_digest[5];
    sha.Reset();
    sha << realKey.c_str();
    sha.Result(message_digest);
    for (int i = 0; i < 5; i++) {
        message_digest[i] = htonl(message_digest[i]);
    }
    realKey = base64_encode(reinterpret_cast<const unsigned char*>(message_digest), 20);
    realKey += "\r\n";
    response += realKey.c_str();
    response += "Upgrade: websocket\r\n\r\n";
    return true;
}

2.接收client协议解析

  • 首先解析包头信息
bool wsReadHeader(const unsigned char* cData, WebSocketStreamHeader* header)  
{  
    if (cData == NULL) return false;  

    const unsigned char *buf = cData;  
    header->fin = buf[0] & 0x80;  
    header->masked = buf[1] & 0x80;  
    unsigned char stream_size = buf[1] & 0x7F;  

    header->opcode = buf[0] & 0x0F;  
    if (header->opcode == WS_FrameType::WS_CONTINUATION_FRAME) {  
        //连续帧  
        return false;  
    }
    else if (header->opcode == WS_FrameType::WS_TEXT_FRAME) {  
        //文本帧  
    }  
    else if (header->opcode == WS_FrameType::WS_BINARY_FRAME) {  
        //二进制帧
    }  
    else if (header->opcode == WS_FrameType::WS_CLOSING_FRAME) {  
        //连接关闭消息  
        return true;  
    }
    else if (header->opcode == WS_FrameType::WS_PING_FRAME) {  
        //  ping  
        return false;  
    }  
    else if (header->opcode == WS_FrameType::WS_PONG_FRAME) {  
        // pong  
        return false;  
    }  
    else {  
        //非法帧  
        return false;  
    }

    if (stream_size <= 125) {  
        //  small stream  
        header->header_size =6;  
        header->payload_size = stream_size;  
        header->mask_offset = 2;  
    }  
    else if (stream_size == 126) {  
        //  medium stream   
        header->header_size = 8;  
        unsigned short s = 0;  
        memcpy(&s, (const char*)&buf[2], 2);  
        header->payload_size = ntohs(s);  
        header->mask_offset = 4;  
    }  
    else if (stream_size == 127) {  

        unsigned long long l = 0;  
        memcpy(&l, (const char*)&buf[2], 8);  

        header->payload_size = l;  
        header->mask_offset = 10;  
    }  
    else {  
        //Couldnt decode stream size 非法大小数据包  
        return false;  
    }

    /*  if (header->payload_size > MAX_WEBSOCKET_BUFFER) {  
    return false;  
    } */ 

    return true;  
}
  • 然后根据包头解析出真是数据
bool wsDecodeFrame(const WebSocketStreamHeader& header, unsigned char cbSrcData[], unsigned short wSrcLen, unsigned char cbTagData[])
{  
    const unsigned char *final_buf = cbSrcData;  
    if (wSrcLen < header.header_size + 1) {  
        return false;
    }  

    char masks[4];  
    memcpy(masks, final_buf + header.mask_offset, 4);  
    memcpy(cbTagData, final_buf + header.mask_offset + 4, header.payload_size);  

    for (INT_PTR i = 0; i < header.payload_size; ++i){  
        cbTagData[i] = (cbTagData[i] ^ masks[i % 4]);  
    }  
    //如果是文本包,在数据最后加一个结束字符“\0”
    if (header.opcode == WS_FrameType::WS_TEXT_FRAME)
        cbTagData[header.payload_size] = '\0';  

    return true;  
}  

3.组装server发给client协议

bool wsEncodeFrame(std::string inMessage, std::string &outFrame, enum WS_FrameType frameType)  
{  
    const uint32_t messageLength = inMessage.size();  
    if (messageLength > 32767)  
    {  
        // 暂不支持这么长的数据  
        return false;
    }  

    uint8_t payloadFieldExtraBytes = (messageLength <= 0x7d) ? 0 : 2;
    // header: 2字节, mask位设置为0(不加密), 则后面的masking key无须填写, 省略4字节  
    uint8_t frameHeaderSize = 2 + payloadFieldExtraBytes;  
    uint8_t *frameHeader = new uint8_t[frameHeaderSize];
    memset(frameHeader, 0, frameHeaderSize);  

    // fin位为1, 扩展位为0, 操作位为frameType  
    frameHeader[0] = static_cast<uint8_t>(0x80 | frameType);  

    // 填充数据长度
    if (messageLength <= 0x7d)  
    {  
        frameHeader[1] = static_cast<uint8_t>(messageLength);  
    }  
    else  
    {  
        frameHeader[1] = 0x7e;
        uint16_t len = htons(messageLength);
        memcpy(&frameHeader[2], &len, payloadFieldExtraBytes);
    }  

    // 填充数据  
    uint32_t frameSize = frameHeaderSize + messageLength;
    char *frame = new char[frameSize + 1];
    memcpy(frame, frameHeader, frameHeaderSize);  
    memcpy(frame + frameHeaderSize, inMessage.c_str(), messageLength);  
    outFrame = std::string(frame, frameSize);
    delete[] frame;
    delete[] frameHeader;
    return true;
}

至此,tcp上扩展websocket所需要处理的3大块就都完成了,即握手、接收解析、发送组装。


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
ZookeeperCAPI之接口描述发布时间:2022-07-13
下一篇:
C#反射取得类的字段与方法信息发布时间:2022-07-13
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap