计算机网络---WebSocket通信(C++)

WebSocket是HTML5规范定义的基于TCP的全双工、双向、持久化应用层通信协议(RFC 6455),核心解决了HTTP协议"请求-响应"半双工模型无法满足实时通信需求的痛点。

一、WebSocket核心定位:突破HTTP的实时性瓶颈

1.1 HTTP协议的实时性缺陷

HTTP协议自设计之初就围绕"客户端请求、服务端响应"的单向模型,在实时通信场景(如聊天、行情推送、物联网数据上报)中存在致命问题:

  • 半双工通信:服务端无法主动向客户端推送数据,只能被动响应请求;
  • 短连接特性:即使HTTP/1.1引入Keep-Alive实现长连接,本质仍是"请求-响应"周期的延长,连接会因超时被销毁;
  • 轮询/长轮询的弊端:轮询(定时发送HTTP请求)会产生大量无效带宽消耗,长轮询(挂起请求直到有数据)仍有连接建立/销毁开销,且延迟无法低于轮询间隔。

1.2 WebSocket的核心优势

  • 全双工通信:连接建立后,客户端和服务端可随时双向发送数据,无需等待对方请求;
  • 持久化连接:一次TCP握手后,连接持续至主动关闭,避免频繁建连/断连的开销;
  • 轻量级协议:数据帧仅包含2~14字节的头部(HTTP头部通常数百字节),大幅降低传输开销;
  • 兼容性强:基于HTTP升级机制实现,可穿透大部分防火墙和代理服务器;
  • 多数据类型支持:原生支持文本(UTF-8)和二进制数据传输,无需额外封装。

1.3 WebSocket vs HTTP 核心特性对比

特性 HTTP WebSocket
通信方向 客户端主动请求,服务端被动响应 全双工,双向主动通信
连接状态 短连接(Keep-Alive仅延长) 持久连接(主动关闭前一直存活)
头部开销 大(包含Cookie、User-Agent等) 极小(最小2字节帧头)
主动推送 不支持 原生支持
数据格式 需封装HTTP头,仅文本/二进制 帧化数据(文本/二进制/控制帧)
关闭方式 响应完成后自动关闭 协商式关闭(Close帧)

二、WebSocket协议底层原理

2.1 握手流程:基于HTTP的协议升级

WebSocket连接的建立依赖HTTP 101(Switching Protocols)升级机制,全程基于TCP连接(默认端口80,WSS为443),分为"客户端请求"和"服务端验证响应"两步:

(1)客户端发起升级请求

客户端向服务端发送HTTP GET请求,核心头字段决定了升级能否成功:

http 复制代码
GET /chat HTTP/1.1
Host: example.com:8080
Upgrade: websocket          # 声明要升级为WebSocket协议
Connection: Upgrade        # 确认连接升级(固定值)
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==  # 16字节随机数的Base64编码
Sec-WebSocket-Version: 13  # 必须为13(RFC 6455标准版本,其他版本不兼容)
Sec-WebSocket-Protocol: chat  # 可选,协商子协议(如自定义业务协议)
Sec-WebSocket-Extensions: permessage-deflate  # 可选,启用压缩扩展
(2)服务端验证并响应升级

服务端必须完成Sec-WebSocket-Key的验证,否则客户端会拒绝建立连接:

  1. Sec-WebSocket-Key与固定UUID字符串258EAFA5-E914-47DA-95CA-C5AB0DC85B11拼接;
  2. 对拼接结果做SHA-1哈希计算,再将哈希值进行Base64编码,得到Sec-WebSocket-Accept
  3. 返回HTTP 101响应,确认协议升级:
http 复制代码
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=  # 验证后的结果
Sec-WebSocket-Protocol: chat  # 确认使用的子协议

握手成功后,TCP连接从HTTP协议切换为WebSocket协议,后续所有通信均使用WebSocket帧格式。

2.2 数据帧格式:WebSocket的通信最小单位

WebSocket所有数据(文本、二进制、控制指令)均封装为"帧(Frame)"传输,帧格式是协议的核心,每个字段的含义和规则必须严格遵守:

字段 长度(位) 核心含义与规则
FIN 1 帧结束标记:1=当前帧是消息最后一帧;0=消息分片,后续还有帧
RSV1/RSV2/RSV3 1*3 保留位,仅启用扩展时非0(如permessage-deflate用RSV1),未启用时必须为0(否则关闭连接)
Opcode 4 帧类型: 0=继续帧(分片消息的后续帧) 1=文本帧(UTF-8编码) 2=二进制帧 8=关闭帧 9=Ping帧(心跳) 10=Pong帧(心跳响应)
Mask 1 掩码标记:客户端发的帧必须为1(需掩码加密),服务端发的帧必须为0(无需掩码)
Payload len 7/7+16/7+64 负载长度: 0~125=直接表示长度; 126=后续2字节(16位无符号整数)表示长度; 127=后续8字节(64位无符号整数)表示长度
Masking-key 0/32 掩码密钥:仅Mask=1时存在(4字节),客户端用于加密负载数据
Payload data 可变 实际传输的数据(文本/二进制/控制指令),Mask=1时需用Masking-key解密
关键规则:掩码计算

客户端发送的所有数据帧必须用Masking-key加密,解密公式为:

复制代码
decoded_byte = encoded_byte ^ masking_key[i % 4]

其中i是负载数据的字节索引,%4表示掩码密钥4字节循环使用。示例:

  • 加密前字节:0x41(字符'A')
  • Masking-key第1字节:0x1F
  • 加密后字节:0x41 ^ 0x1F = 0x5E(字符'^')

服务端接收后需反向解密,而服务端发送的帧无需掩码,客户端可直接解析。

分片传输规则

当消息体积较大时,可拆分为多个帧传输:

  • 首帧:FIN=0,Opcode=1(文本)/2(二进制);
  • 中间帧:FIN=0,Opcode=0(继续帧);
  • 最后一帧:FIN=1,Opcode=0;
  • 控制帧(Ping/Pong/Close)不允许分片,必须是单帧(FIN=1)。

2.3 连接关闭机制:协商式关闭

WebSocket禁止直接断开TCP连接,必须通过"Close帧"完成协商式关闭,避免数据丢失:

  1. 发起方发送Opcode=8的Close帧,负载可携带:
    • 2字节无符号整数状态码(如1000=正常关闭);
    • 可选的UTF-8编码原因文本;
  2. 接收方收到Close帧后,必须立即回复相同的Close帧;
  3. 双方完成Close帧交互后,关闭TCP连接。
常见关闭状态码
状态码 含义 适用场景
1000 正常关闭 业务完成后主动关闭
1001 端点离开 客户端关闭浏览器/服务端停机
1002 协议错误 帧格式非法/Opcode不支持
1003 不支持的数据类型 接收非UTF8的文本帧
1006 连接异常关闭 TCP连接被强制断开(非协商)
1011 服务端内部错误 服务端处理消息时崩溃

2.4 心跳保活机制:避免"假死"连接

由于NAT超时、防火墙清理、网络波动等原因,WebSocket连接可能出现"假死"(TCP连接存在但无法通信),需通过Ping/Pong帧实现心跳:

  • 发起方(通常是服务端)定时发送Opcode=9的Ping帧(可携带少量负载);
  • 接收方必须立即回复Opcode=10的Pong帧,且负载需与Ping帧一致;
  • 若发起方超时(如30秒)未收到Pong帧,判定连接失效,主动发送Close帧关闭连接。

WebSocket的详细原理等可以参考这篇文章websocket万字详解,在此不在过多赘述

三、C++实现WebSocket

C++无原生WebSocket库,主流选择有两个:

  • libwebsockets:轻量、跨平台、无Boost依赖,适合高性能场景;
  • websocketpp:基于Boost.Asio,面向对象设计,开发效率高。

本文以libwebsockets为例(工业界更常用),提供完整的服务端和客户端实现。

3.1 libwebsockets环境搭建

(1)Linux/macOS环境
bash 复制代码
# 安装依赖(SSL/压缩/线程)
sudo apt install libssl-dev libz-dev libpthread-stubs0-dev  # Ubuntu/Debian
brew install openssl zlib                                  # macOS

# 编译安装libwebsockets
git clone https://github.com/warmcat/libwebsockets.git
cd libwebsockets && mkdir build && cd build
cmake -DCMAKE_INSTALL_PREFIX=/usr/local -DLWS_WITH_SSL=ON ..  # 启用SSL(支持WSS)
make -j4 && sudo make install
(2)Windows环境
  1. 下载CMake和Visual Studio 2022;
  2. 编译OpenSSL并配置环境变量;
  3. 通过CMake-GUI生成VS工程,编译安装libwebsockets。

3.2 C++ WebSocket服务端实现(核心功能:回声+心跳+客户端管理)

以下代码实现了一个完整的WebSocket服务端,支持客户端连接管理、消息回声、心跳保活、协商式关闭:

cpp 复制代码
#include <libwebsockets.h>
#include <string.h>
#include <unistd.h>
#include <vector>
#include <mutex>

// 全局变量:客户端连接管理(线程安全)
std::vector<struct lws*> g_clients;
std::mutex g_client_mutex;

// 心跳配置:30秒未收到Pong则关闭连接
#define HEARTBEAT_INTERVAL 30
#define HEARTBEAT_TIMEOUT 10

// 每个客户端的上下文数据(存储心跳时间)
struct PerClientData {
    time_t last_pong_time;  // 最后一次收到Pong的时间
};

/**
 * @brief WebSocket事件回调函数(核心)
 * @param wsi 连接句柄
 * @param reason 事件类型
 * @param user 自定义数据(PerClientData)
 * @param in 输入数据
 * @param len 输入数据长度
 */
static int ws_callback(struct lws *wsi, enum lws_callback_reasons reason,
                       void *user, void *in, size_t len) {
    PerClientData *client_data = (PerClientData*)user;

    switch (reason) {
        // 新客户端连接建立
        case LWS_CALLBACK_ESTABLISHED: {
            std::lock_guard<std::mutex> lock(g_client_mutex);
            g_clients.push_back(wsi);
            client_data->last_pong_time = time(NULL);  // 初始化心跳时间
            lwsl_notice("Client connected: %p, total clients: %zu\n", wsi, g_clients.size());
            break;
        }

        // 收到客户端数据帧
        case LWS_CALLBACK_RECEIVE: {
            // libwebsockets要求数据缓冲区预留LWS_PRE字节(避免内存越界)
            char buf[LWS_PRE + 4096] = {0};
            memcpy(buf + LWS_PRE, in, len);
            lwsl_notice("Received from client %p: %s (len: %zu)\n", wsi, buf + LWS_PRE, len);

            // 回声响应:将收到的消息回发给客户端
            int ret = lws_write(wsi, (unsigned char*)buf + LWS_PRE, len, LWS_WRITE_TEXT);
            if (ret < 0) {
                lwsl_err("Failed to write to client %p\n", wsi);
            }
            break;
        }

        // 收到Ping帧(客户端心跳)
        case LWS_CALLBACK_SERVER_PING: {
            client_data->last_pong_time = time(NULL);  // 更新心跳时间
            lwsl_notice("Received Ping from client %p\n", wsi);
            // libwebsockets自动回复Pong帧,无需手动处理
            break;
        }

        // 定时检查心跳(由lws_service触发)
        case LWS_CALLBACK_SERVER_HEARTBEAT: {
            std::lock_guard<std::mutex> lock(g_client_mutex);
            time_t now = time(NULL);
            for (auto it = g_clients.begin(); it != g_clients.end();) {
                struct lws *client_wsi = *it;
                PerClientData *data = (PerClientData*)lws_wsi_user(client_wsi);
                
                // 心跳超时:关闭连接
                if (now - data->last_pong_time > HEARTBEAT_INTERVAL + HEARTBEAT_TIMEOUT) {
                    lwsl_notice("Client %p heartbeat timeout, closing\n", client_wsi);
                    lws_close_reason(client_wsi, LWS_CLOSE_STATUS_NORMAL, (unsigned char*)"timeout", 7);
                    it = g_clients.erase(it);
                } else {
                    // 发送Ping帧(心跳检测)
                    lws_callback_on_writable(client_wsi);
                    ++it;
                }
            }
            break;
        }

        // 可写事件:发送Ping帧
        case LWS_CALLBACK_SERVER_WRITEABLE: {
            // 发送Ping帧(负载为空)
            lws_write(wsi, NULL, 0, LWS_WRITE_PING);
            break;
        }

        // 客户端连接关闭
        case LWS_CALLBACK_CLOSED: {
            std::lock_guard<std::mutex> lock(g_client_mutex);
            for (auto it = g_clients.begin(); it != g_clients.end(); ++it) {
                if (*it == wsi) {
                    g_clients.erase(it);
                    lwsl_notice("Client disconnected: %p, total clients: %zu\n", wsi, g_clients.size());
                    break;
                }
            }
            break;
        }

        // 其他事件默认处理
        default:
            break;
    }
    return 0;
}

// WebSocket协议配置
static struct lws_protocols ws_protocols[] = {
    {
        "ws-echo-protocol",  // 协议名称(对应客户端Sec-WebSocket-Protocol)
        ws_callback,         // 事件回调函数
        sizeof(PerClientData),// 每个连接的自定义数据大小
        4096,                // 接收缓冲区大小
        0, NULL, 0
    },
    {NULL, NULL, 0, 0}  // 协议列表结束标记
};

int main(int argc, char **argv) {
    // 日志级别:NOTICE及以上
    lws_set_log_level(LLL_NOTICE | LLL_ERR | LLL_WARN, NULL);

    // 上下文配置(服务端核心配置)
    struct lws_context_creation_info ctx_info;
    memset(&ctx_info, 0, sizeof(ctx_info));
    ctx_info.port = 8080;                      // 监听端口
    ctx_info.protocols = ws_protocols;         // 协议列表
    ctx_info.gid = -1;
    ctx_info.uid = -1;
    ctx_info.options = 
        LWS_SERVER_OPTION_VALIDATE_UTF8 |      // 验证UTF8文本帧
        LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;  // 初始化SSL(支持WSS)

    // 创建上下文(WebSocket服务端核心对象)
    struct lws_context *ctx = lws_create_context(&ctx_info);
    if (!ctx) {
        lwsl_err("Failed to create lws context\n");
        return -1;
    }

    lwsl_notice("WebSocket server started on ws://localhost:8080\n");
    lwsl_notice("Heartbeat interval: %d seconds, timeout: %d seconds\n", 
                HEARTBEAT_INTERVAL, HEARTBEAT_TIMEOUT);

    // 事件循环:处理客户端连接和消息
    while (1) {
        // 处理事件(超时50ms,避免CPU占用过高)
        lws_service(ctx, 50);
        usleep(10000);  // 10ms休眠
    }

    // 释放资源(实际不会执行到,需通过信号处理退出)
    lws_context_destroy(ctx);
    return 0;
}
核心代码解释
  1. PerClientData:每个客户端的自定义数据,存储最后一次收到Pong的时间,用于心跳检测;
  2. ws_callback:事件回调函数,处理连接建立、数据接收、心跳、连接关闭等核心事件;
  3. lws_service:事件循环函数,负责处理客户端的IO事件和定时任务(如心跳检查);
  4. 客户端管理:通过全局向量+互斥锁实现线程安全的客户端连接管理,避免多线程竞争。

3.3 C++ WebSocket客户端实现

以下代码实现了WebSocket客户端,支持连接服务端、发送消息、接收响应、心跳处理:

cpp 复制代码
#include <libwebsockets.h>
#include <string.h>
#include <unistd.h>

// 客户端自定义数据
struct PerClientData {
    char send_buf[LWS_PRE + 4096];  // 发送缓冲区
    int send_len;                   // 待发送数据长度
};

/**
 * @brief 客户端事件回调函数
 */
static int ws_client_callback(struct lws *wsi, enum lws_callback_reasons reason,
                              void *user, void *in, size_t len) {
    PerClientData *client_data = (PerClientData*)user;

    switch (reason) {
        // 连接服务端成功
        case LWS_CALLBACK_CLIENT_ESTABLISHED: {
            lwsl_notice("Connected to WebSocket server\n");
            // 准备发送测试消息
            const char *msg = "Hello WebSocket Server (C++)";
            client_data->send_len = strlen(msg);
            memcpy(client_data->send_buf + LWS_PRE, msg, client_data->send_len);
            // 触发可写事件,发送消息
            lws_callback_on_writable(wsi);
            break;
        }

        // 可写事件:发送数据
        case LWS_CALLBACK_CLIENT_WRITEABLE: {
            if (client_data->send_len > 0) {
                // 发送文本帧
                int ret = lws_write(wsi, (unsigned char*)client_data->send_buf + LWS_PRE,
                                    client_data->send_len, LWS_WRITE_TEXT);
                if (ret > 0) {
                    lwsl_notice("Sent to server: %s\n", client_data->send_buf + LWS_PRE);
                    client_data->send_len = 0;  // 清空待发送数据
                }
            }
            break;
        }

        // 收到服务端数据
        case LWS_CALLBACK_CLIENT_RECEIVE: {
            char buf[4096] = {0};
            memcpy(buf, in, len);
            lwsl_notice("Received from server: %s (len: %zu)\n", buf, len);
            break;
        }

        // 收到服务端Ping帧,自动回复Pong
        case LWS_CALLBACK_CLIENT_PING: {
            lwsl_notice("Received Ping from server\n");
            break;
        }

        // 连接关闭
        case LWS_CALLBACK_CLIENT_CLOSED: {
            lwsl_notice("Disconnected from server\n");
            break;
        }

        default:
            break;
    }
    return 0;
}

// 客户端协议配置
static struct lws_protocols ws_client_protocols[] = {
    {
        "ws-echo-protocol",
        ws_client_callback,
        sizeof(PerClientData),
        4096,
        0, NULL, 0
    },
    {NULL, NULL, 0, 0}
};

int main(int argc, char **argv) {
    lws_set_log_level(LLL_NOTICE | LLL_ERR | LLL_WARN, NULL);

    // 上下文配置
    struct lws_context_creation_info ctx_info;
    memset(&ctx_info, 0, sizeof(ctx_info));
    ctx_info.protocols = ws_client_protocols;
    ctx_info.gid = -1;
    ctx_info.uid = -1;

    struct lws_context *ctx = lws_create_context(&ctx_info);
    if (!ctx) {
        lwsl_err("Failed to create client context\n");
        return -1;
    }

    // 客户端连接参数
    struct lws_client_connect_info conn_info;
    memset(&conn_info, 0, sizeof(conn_info));
    conn_info.context = ctx;
    conn_info.address = "localhost";    // 服务端地址
    conn_info.port = 8080;              // 服务端端口
    conn_info.path = "/";               // 服务端路径
    conn_info.host = conn_info.address; // Host头
    conn_info.origin = conn_info.address;
    conn_info.protocol = "ws-echo-protocol";  // 子协议(需与服务端一致)

    // 建立连接
    struct lws *wsi = lws_client_connect_via_info(&conn_info);
    if (!wsi) {
        lwsl_err("Failed to connect to server\n");
        lws_context_destroy(ctx);
        return -1;
    }

    // 事件循环
    while (1) {
        lws_service(ctx, 50);
        usleep(10000);
    }

    lws_context_destroy(ctx);
    return 0;
}

3.4 编译与运行

(1)编译服务端
bash 复制代码
g++ -o ws_server ws_server.cpp -lwebsockets -lpthread -lssl -lcrypto -lz
(2)编译客户端
bash 复制代码
g++ -o ws_client ws_client.cpp -lwebsockets -lpthread -lssl -lcrypto -lz
(3)运行
bash 复制代码
# 启动服务端
./ws_server

# 新开终端启动客户端
./ws_client

运行后客户端会向服务端发送消息,服务端回声响应,同时服务端会定时发送Ping帧检测客户端心跳。

四、WebSocket高级特性与工程实践

4.1 安全加固:WSS(WebSocket Secure)配置

生产环境必须使用WSS(基于TLS/SSL加密),避免数据明文传输。libwebsockets配置WSS只需修改上下文配置:

cpp 复制代码
// 新增SSL配置
ctx_info.ssl_cert_filepath = "/path/to/server.crt";  // 证书文件
ctx_info.ssl_private_key_filepath = "/path/to/server.key";  // 私钥文件
ctx_info.port = 443;  // WSS默认端口
生产环境推荐:Nginx反向代理WSS

直接在应用层配置SSL易出问题,推荐通过Nginx反向代理实现WSS:

nginx 复制代码
server {
    listen 443 ssl;
    server_name example.com;

    # SSL证书配置
    ssl_certificate /path/to/fullchain.pem;
    ssl_certificate_key /path/to/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;

    # 反向代理WebSocket
    location /ws {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_read_timeout 86400;  // 延长超时时间(避免心跳中断)
    }
}

4.2 断线重连机制(工程必备)

网络波动会导致连接断开,需实现断线重连逻辑(指数退避策略,避免频繁重试):

cpp 复制代码
// 客户端重连逻辑示例
int reconnect_count = 0;
const int MAX_RECONNECT = 10;
const int BASE_RECONNECT_INTERVAL = 1;  // 基础重连间隔(秒)

while (reconnect_count < MAX_RECONNECT) {
    // 建立连接
    struct lws *wsi = lws_client_connect_via_info(&conn_info);
    if (wsi) {
        reconnect_count = 0;  // 重连成功,重置计数
        break;
    }

    // 指数退避:1s → 2s → 4s → ... → 512s
    int interval = BASE_RECONNECT_INTERVAL * (1 << reconnect_count);
    interval = std::min(interval, 512);  // 最大间隔512秒
    lwsl_notice("Reconnect failed, retry in %d seconds (count: %d)\n", interval, reconnect_count);
    sleep(interval);
    reconnect_count++;
}

4.3 性能优化策略

  1. 分片传输大数据:将超过125字节的消息拆分为多个帧,避免单次传输阻塞;

  2. 启用压缩扩展 :配置permessage-deflate扩展,压缩文本数据(减少带宽):

    cpp 复制代码
    // 启用压缩扩展
    ctx_info.extensions = lws_get_internal_extensions();
  3. 连接池限制:服务端限制最大连接数(如10000),避免资源耗尽;

  4. 异步IO优化:结合epoll/kqueue(Linux/macOS)实现高并发,libwebsockets已内置异步IO,无需手动实现。

4.4 常见问题与排错

问题 根因 解决方案
握手失败(400错误) Sec-WebSocket-Version≠13或Key验证失败 确保客户端使用版本13,服务端正确计算Accept
连接立即关闭 子协议不匹配 客户端和服务端Sec-WebSocket-Protocol一致
数据乱码 文本帧非UTF8编码 强制使用UTF8编码,验证数据格式
心跳超时 防火墙拦截Ping/Pong帧 调整心跳间隔,通过Nginx转发时延长超时时间
高并发下连接不稳定 文件描述符耗尽 调整系统最大文件描述符(ulimit -n 65535)

  1. 协议本质:WebSocket是基于HTTP升级的全双工持久化协议,通过轻量级帧格式实现高效双向通信,核心解决HTTP实时性差的问题;
  2. 核心规则:握手需验证Sec-WebSocket-Key、客户端帧必须掩码、控制帧(Ping/Pong/Close)需遵守单帧规则、连接需协商式关闭;
  3. C++实现:优先选择libwebsockets库,核心是事件回调函数+上下文事件循环,需实现客户端管理、心跳保活、断线重连,生产环境必须配置WSS;
  4. 工程要点:心跳保活避免连接假死、指数退避实现重连、Nginx反向代理优化WSS配置、限制连接数避免性能瓶颈。
相关推荐
REDcker6 小时前
HTTP 协议发展详解:从 HTTP/1 到 HTTP/3
网络·网络协议·http
乾元7 小时前
数据投毒:如何通过训练数据污染埋下“后门”
运维·人工智能·网络协议·安全·网络安全·系统架构·自动化
吨吨不打野8 小时前
error: RPC failed; HTTP 400 curl 22 The requested URL returned error: 400
网络协议·http·rpc
君鼎8 小时前
计算机网络第十章:下一代互联网与前沿技术
计算机网络
hoududubaba14 小时前
ORAN压缩之块浮点压缩
网络·网络协议
tryxr18 小时前
如何基于UDP实现可靠传输
网络·网络协议·udp·tcp
~央千澈~19 小时前
抖音弹幕游戏开发之第8集:pyautogui基础 - 模拟键盘操作·优雅草云桧·卓伊凡
网络·python·websocket·网络协议
yukai080081 天前
【203篇系列】041 Websocket Server
网络·websocket·网络协议
切糕师学AI1 天前
堡垒机(Bastion Host)详解
计算机网络·网络安全