服务端使用c++实现websocket协议解析及通信

       WebSocket 设计出来的目的就是要使客户端浏览器具备像 C/S 架构下桌面系统的实时通讯能力。 浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。因为 WebSocket 连接本质上就是一个 TCP 连接,所以在数据传输的稳定性和数据传输量的大小方面,和轮询以及 Comet 技术比较,具有很大的性能优势。下面是一个简单 Web 应用分别用轮询方式和 WebSocket 方式来实现,下面是测试结果图:

                                                 

       通过这张图可以清楚的看出,在流量和负载增大的情况下,WebSocket 方案相比传统的 Ajax 轮询方案有极大的性能优势。
       好了不过多介绍 WebSocket 了,更多介绍大家可以点击参考资料引用的链接查看,还是回到解析协议及通信上来。解析协议这种事,就得耐着性子,一个字节一个字节解析,按步骤一点一点写程序。不过读懂了文档,知道了每个字节的属性意义后,解析起来还是挺简单的。按照协议说明,一旦完成数据解码,那么编码就稍微容易一些,差不多就是解码的逆向操作了。服务端使用c++完成 WebSocket 通信,主要需要完成以下三方面编程:
       1. 服务端与h5客户端发起的 WebSocket 连接握手:

int wsHandshake(string &request, string &response)
{
    // 解析http请求头信息
    int ret = WS_STATUS_UNCONNECT;
    std::istringstream stream(request.c_str());
    std::string reqType;
    std::getline(stream, reqType);
    if (reqType.substr(0, 4) != "GET ")
    {
        return ret;
    }

    std::string header;
    std::string::size_type pos = 0;
    std::string websocketKey;
    while (std::getline(stream, header) && header != "\r")
    {
        header.erase(header.end() - 1);
        pos = header.find(": ", 0);
        if (pos != std::string::npos)
        {
            std::string key = header.substr(0, pos);
            std::string value = header.substr(pos + 2);
            if (key == "Sec-WebSocket-Key")
            {
                ret = WS_STATUS_CONNECT;
                websocketKey = value;
                break;
            }
        }
    }

    if (ret != WS_STATUS_CONNECT)
    {
        return ret;
    }

    // 填充http响应头信息
    response = "HTTP/1.1 101 Switching Protocols\r\n";
    response += "Upgrade: websocket\r\n";
    response += "Connection: upgrade\r\n";
    response += "Sec-WebSocket-Accept: ";

    const std::string magicKey("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
    std::string serverKey = websocketKey + magicKey;

    char shaHash[32];
    memset(shaHash, 0, sizeof(shaHash));
    sha1::calc(serverKey.c_str(), serverKey.size(), (unsigned char *) shaHash);
    serverKey = base64::base64_encode(std::string(shaHash)) + "\r\n\r\n";
    string strtmp(serverKey.c_str());
    response += strtmp;

    return ret;
}

       2. 完成握手后连接就建立了。然后就是接收h5客户端通过 WebSocket 发过来的数据帧并解码:

int wsDecodeFrame(string inFrame, string &outMessage)
{
    int ret = WS_OPENING_FRAME;
    const char *frameData = inFrame.c_str();
    const int frameLength = inFrame.size();
    if (frameLength < 2)
    {
        ret = WS_ERROR_FRAME;
    }

    // 检查扩展位并忽略
    if ((frameData[0] & 0x70) != 0x0)
    {
        ret = WS_ERROR_FRAME;
    }

    // fin位: 为1表示已接收完整报文, 为0表示继续监听后续报文
    ret = (frameData[0] & 0x80);
    if ((frameData[0] & 0x80) != 0x80)
    {
        ret = WS_ERROR_FRAME;
    }

    // mask位, 为1表示数据被加密
    if ((frameData[1] & 0x80) != 0x80)
    {
        ret = WS_ERROR_FRAME;
    }

    // 操作码
    uint16_t payloadLength = 0;
    uint8_t payloadFieldExtraBytes = 0;
    uint8_t opcode = static_cast<uint8_t >(frameData[0] & 0x0f);
    if (opcode == WS_TEXT_FRAME)
    {
        // 处理utf-8编码的文本帧
        payloadLength = static_cast<uint16_t >(frameData[1] & 0x7f);
        if (payloadLength == 0x7e)
        {
            uint16_t payloadLength16b = 0;
            payloadFieldExtraBytes = 2;
            memcpy(&payloadLength16b, &frameData[2], payloadFieldExtraBytes);
            payloadLength = ntohs(payloadLength16b);
        }
        else if (payloadLength == 0x7f)
        {
            // 数据过长,暂不支持
            ret = WS_ERROR_FRAME;
        }
    }
    else if (opcode == WS_BINARY_FRAME || opcode == WS_PING_FRAME || opcode == WS_PONG_FRAME)
    {
        // 二进制/ping/pong帧暂不处理
    }
    else if (opcode == WS_CLOSING_FRAME)
    {
        ret = WS_CLOSING_FRAME;
    }
    else
    {
        ret = WS_ERROR_FRAME;
    }

    // 数据解码
    if ((ret != WS_ERROR_FRAME) && (payloadLength > 0))
    {
        // header: 2字节, masking key: 4字节
        const char *maskingKey = &frameData[2 + payloadFieldExtraBytes];
        char *payloadData = new char[payloadLength + 1];
        memset(payloadData, 0, payloadLength + 1);
        memcpy(payloadData, &frameData[2 + payloadFieldExtraBytes + 4], payloadLength);
        for (int i = 0; i < payloadLength; i++)
        {
            payloadData[i] = payloadData[i] ^ maskingKey[i % 4];
        }

        outMessage = payloadData;
        delete[] payloadData;
    }

    return ret;
}

       3. 解码完数据帧,服务端做出相应处理后将结果按照 WebSocket 协议编码,然后发给h5客户端:

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

    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);
    frame[frameSize] = '\0';
    outFrame = frame;

    delete[] frame;
    delete[] frameHeader;
    return ret;
}

       4. 握手只需一次,随后反复执行第2步及第3步,就完成了服务端与h5客户端通信。这个只是c++版本的,可以很容易改成java版本的。下面是上述方法用到的一些枚举:

enum WS_Status
{
    WS_STATUS_CONNECT = 0,
    WS_STATUS_UNCONNECT = 1,
};

enum WS_FrameType
{
    WS_EMPTY_FRAME = 0xF0,
    WS_ERROR_FRAME = 0xF1,
    WS_TEXT_FRAME   = 0x01,
    WS_BINARY_FRAME = 0x02,
    WS_PING_FRAME = 0x09,
    WS_PONG_FRAME = 0x0A,
    WS_OPENING_FRAME = 0xF3,
    WS_CLOSING_FRAME = 0x08
};


       参考资料:



  • 6
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 15
    评论
C++ 中,可以使用一些库来实现 WebSocket 服务端。以下是一个基于 Boost.Asio 库的示例代码: ```cpp #include <boost/asio.hpp> #include <websocketpp/config/asio_no_tls.hpp> #include <websocketpp/server.hpp> #include <iostream> typedef websocketpp::server<websocketpp::config::asio> server; int main() { boost::asio::io_service io_service; server web_server; web_server.init_asio(&io_service); web_server.set_message_handler([](websocketpp::connection_hdl hdl, server::message_ptr msg) { std::cout << "Received message: " << msg->get_payload() << std::endl; web_server.send(hdl, "Server received message: " + msg->get_payload(), websocketpp::frame::opcode::text); }); web_server.listen(9002); web_server.start_accept(); io_service.run(); } ``` 在这个示例中,我们使用了 Boost.Asio 库来处理底层的网络 I/O,同时使用WebSocket++ 库来实现 WebSocket 协议的处理。在 `main()` 函数中,我们首先创建了一个 `io_service` 对象,然后初始化了一个 WebSocket 服务器对象 `web_server`,并将 `io_service` 对象传递给 `web_server`,以便它可以利用 Boost.Asio 库进行网络 I/O。 接下来,我们设置了消息处理函数,在收到客户端发来的消息时打印出来,并回复一个消息。最后,我们指定了服务器监听的端口号,并开始接受连接请求。最后,我们调用 `io_service.run()` 来启动事件循环,使服务器可以一直运行下去。 需要注意的是,在这个示例中,我们使用WebSocket++ 库来处理 WebSocket 协议的握手和消息发送/接收等工作。如果你想使用其他 WebSocket 库,可能需要对代码进行相应的修改。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值