ZBLOG

如何实现webscoket

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,适用于实时应用(如聊天、游戏、实时数据推送等)。以下是基于 C++ 实现 WebSocket 的核心步骤和示例代码:


1. 核心实现步骤

(1) 建立 TCP 服务端

  • 使用 Socket API 监听端口(如 8080)。
  • 接受客户端连接,获取文件描述符(fd)。

(2) HTTP 升级到 WebSocket

  • 客户端发送 HTTP Upgrade 请求:
    
    GET /chat HTTP/1.1
    Host: example.com
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
    Sec-WebSocket-Version: 13
    
  • 服务端响应: 计算 Sec-WebSocket-Accept:对客户端 Key + GUID 258EAFA5-E914-47DA-95CA-C5AB0DC85B11做 SHA1+Base64。 返回响应:
HTTP/1.1 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

(3) WebSocket 数据帧解析

WebSocket 数据帧格式(RFC6455):

0                   1                   2                   3
0 |1|2|3|4|5|6|7|8|9|0|1|2|3|4|
+-+-+-+-+-------+-+-------------+
FIN |RSV | OPCODE | MASKED? |
+-+-+-+-+-------+-+-------------+
Payload Length (7/16/64 bits)
Extended Payload Length (if needed)
Masking Key (4 bytes, if MASKED=1)
Payload Data (application data)

关键字段:

  • OPCODE0x01(文本)、0x02(二进制)、0x08(关闭连接)。
  • MASKED:客户端消息必须掩码处理,服务端无需掩码。

2. C++ WebSocket Server Demo

以下是一个简化版实现(需自行处理错误检查和优化):

#include <iostream>
#include <string>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <openssl/sha.h>
#include <base64/base64.h> // Base64库需自行引入

// WebSocket握手响应生成函数
std::string generate_ws_accept(const std::string& client_key) {
    const std::string magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
    std::string combined = client_key + magic;
    
    unsigned char sha_hash[SHA_DIGEST_LENGTH];
    SHA1((const unsigned char*)combined.c_str(), combined.size(), sha_hash);
    
    return base64_encode(sha_hash, SHA_DIGEST_LENGTH);
}

int main() {
    int server_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    sockaddr_in addr{AF_INET, htons(8080), INADDR_ANY};
    bind(server_fd, (sockaddr*)&addr, sizeof(addr));
    listen(server_fd, SOMAXCONN);

    while (true) {
        int client_fd = accept(server_fd, nullptr, nullptr);
        char buffer[1024] = {0};
        read(client_fd, buffer, sizeof(buffer));

        // Step2: HTTP升级为WebSocket协议并回复握手包.
        if (strstr(buffer,"Upgrade: websocket")){
            //提取Sec - WebSoket - Key值.
            std::string key(buffer); 
            size_t start=key.find("Sec - Websoket - Key : ") +19; 
            size_t end=key.find("\r\n",start); 
            std :: string client_key=key.substr(start,end-start); 

            //生成应答并发送HTTP101切换协议.
            std :: string accept_key=generate_ws_accept(client_key); 
            std :: string response="HTTP /1 .1101 Switching Protocols\r\n" 
                                 "Upgrade : websoket\r\n" 
                                 "Connection : Upgrade\r\n" 
                                 "Sec - Websoket - Accept:"+accept_key+"\r\n\r\n"; 

           send(client_fd ,response.c_str(),response.size(),MSG_NOSIGNAL); 

           //Step3 :处理后续的WS数据帧...
           while(true){
               uint8_t ws_header[14];//最大可能头部长度14字节.
               recv(client_fd,&ws_header,sizeof(ws_header),MSG_PEEK);//偷看头部.

               bool is_masked=(ws_header [l] &128)==128;//检查MASK位.
               uint64_t payload_len=(uint8_t)( ws_header[l ] &127);//有效载荷长度.

                if(payload_len ==126){//扩展16位长度.
                   payload_len=ntohs(*reinterpret_cast<uint16_t*>(& ws_header [2]));
                }else if(payload_len ==127){//扩展64位长度.
                   payload_len=ntohll(*reinterpret_cast<uint64_t*>(& ws_header [2]));
                }

                //读取完整帧并处理掩码和数据...
                uint32_t mask_key;
                if(is_masked){
                     memcpy(&mask_key,& ws_header [payload_len >126 ?4 :2],sizeof(mask_key));//获取掩码键值.
                 }
                 ...
          }
       }
       close(client_fd);
   }
}

3.推荐开源库

直接使用现有库更高效:

(a)轻量级方案

-Boost.Asio + Beast:参考文档

websocket::stream<tcp::socket> ws(ioc);
ws.accept(request); //自动完成握手和数据帧解析。

(b)高性能方案

-uWebSocketsGitHub

uWS::App().ws("/*", {...}).listen(9000);

4.Websocket关键点总结

协议升级 通过HTTP101状态码切换协议。
数据分帧 需按RFC6455解析掩码、长度和操作码。
心跳保活 定期发送Ping/Pong帧维持连接。
多线程优化 建议结合IO多路复用(epoll/kqueue)。

如果需要完整项目代码或深入某个环节,可以进一步讨论! 🚀


内容由零声教学AI助手提供,问题来源于学员提问

本站部分文章来源于网络,版权归原作者所有,如有侵权请联系站长删除。
转载请注明出处:https://golang.0voice.com/?id=23085

分享:
扫描分享到社交APP
上一篇
下一篇
发表列表
游客 游客
此处应有掌声~
评论列表

还没有评论,快来说点什么吧~

联系我们

在线咨询: 点击这里给我发消息

微信号:3007537140

上班时间: 10:30-22:30

关注我们
x

注册

已经有帐号?