C++之WebSocket初体验

背景

"你知道websocket吗?"面试官提问到,小H翻遍记忆但是也只找到了比较相关的socket,那他们之间有什么区别呢?疑问只能在面试之后自己去寻找解答了。

本文代码也收录至:futureseek/Cpp_learn: learn some model about C++ 的网络通信部分

WebSocket

Socket 是网络通信的底层端点抽象 ,WebSocket 是基于 TCP 且兼容 HTTP 的**高层应用层协议。**也就是说socket实际上是我们操作系统提供的网络编程接口,而WebSocket是在之上的升级和封装。

对比

Socket WebSocket
本质 网络通信 API(操作系统提供) 应用层协议(基于 TCP 构建)
通信方式 可实现 TCP(面向连接)或 UDP(无连接)通信,需手动处理粘包、分包等问题 仅基于 TCP 全双工通信,协议内置帧机制,自动处理数据边界
连接建立 TCP Socket 需三次握手建立连接,流程由开发者通过 API 控制 先通过 HTTP 握手发起 "协议升级" 请求,服务器同意后转为 WebSocket 连接,兼容 HTTP 端口(80/443)
状态维护 TCP Socket 是有状态连接,但需开发者自行维护连接状态(如心跳检测) 协议本身维护连接状态,自带 ping/pong 心跳机制,自动检测死连接
适用场景 需要底层控制的场景(如自定义协议、高性能传输、游戏服务器) 实时通信场景(如聊天软件、实时推送、在线协作工具)
开发复杂度 高,需手动处理连接管理、数据编解码、粘包等问题 低,框架(如 WebSocket++、Socket.IO)已封装细节,开发者专注业务逻辑

为什么有了socket还要websocket呢?

Socket 是底层通信机制,提供 TCP/UDP 的编程接口;而 WebSocket 是一种基于 HTTP 的应用层协议,通过握手升级后实现全双工通信。它主要解决 HTTP 不能让服务器主动推送数据的问题,在浏览器实时通信、IM、游戏、推送系统中非常常用。

实践

本文将实现一个最小可运行的WebServer服务端并提供测试代码,基于WebSocket++(Boost.Asio)。

环境准备

本次需要用到boost库,doctest进行测试,也可以使用wscat进行简单的访问测试不用真的实现一个客户端。

bash 复制代码
// linux
sudo apt install libboost-system-dev libboost-thread-dev libboost-filesystem-dev
sudo apt install libboost-all-dev cmake python3-pip
pip3 install websockets
sudo apt install wscat

WebSocketServer

直接看代码,代码中提供了详细的注释解释了每一个函数是用来干什么的,可以发现其流程也是相对简单,初始化基本信息之后就是注册回调函数,启动run函数之后是一个阻塞的状态,调用的是底层的io_context.run(),之后线程进入"事件循环"的状态,就是把当前线程交给 asio 的 reactor(默认就是 epoll)来调度,一旦没有活干就睡在 epoll_wait 里;有事件内核唤醒,asio 再帮你把回调排队执行。

asio 在 Linux 下默认后端是 epoll_reactor,它帮你做了:建 epoll fd、把每个 socket/timer 对应的 fd 加进去、在内部循环里调 epoll_wait、拿到事件后还原到哪个 handler 该跑,再扔到执行队列。

cpp 复制代码
class WsServer{
    // msg_ptr is a shared pointer to a message,used to handle incoming messages
    using msg_ptr = websocketpp::server<websocketpp::config::asio>::message_ptr;
    // con_hdl is a weak pointer to a connection,used to identify connections
    using con_hdl = websocketpp::connection_hdl;
private:
    websocketpp::server<websocketpp::config::asio> m_server;
    std::set<con_hdl,std::owner_less<con_hdl>> m_connections;
    mutable std::mutex mtx;

public:
    WsServer(){
        // open all logging channels except frame payload
        m_server.set_access_channels(websocketpp::log::alevel::all);
        m_server.clear_access_channels(websocketpp::log::alevel::frame_payload);
        // initialize the Asio transport policy
        m_server.init_asio();
        // set reuse address option
        m_server.set_reuse_addr(true);
        // set callbacks
        m_server.set_open_handler([this](con_hdl hdl){
            on_open(hdl);
        });
        m_server.set_close_handler([this](con_hdl hdl){
            on_close(hdl);
        });
        m_server.set_message_handler([this](con_hdl hdl, msg_ptr msg){
            on_message(hdl,msg);
        });
    }
    void run(uint16_t port){
        m_server.listen(port);
        m_server.start_accept();
        std::cout<<"[info] server listening on "<<port<<std::endl;
        // this run is blocking
        m_server.run();
    }

    void stop(){
        m_server.stop();
    }
    // send a text message to a specific connection
    void send_text(con_hdl hdl,const std::string& msg){
        std::lock_guard lock(mtx);
        if(m_connections.find(hdl) == m_connections.end()){
            return;
        }
        m_server.send(hdl,msg,websocketpp::frame::opcode::text);
    }
    // broadcast a text message to all connections
    void broadcast(const std::string& msg){
        std::lock_guard lock(mtx);
        for(auto& hdl:m_connections){
            m_server.send(hdl,msg,websocketpp::frame::opcode::text);
        }
    }
    size_t connection_count() const{
        std::lock_guard lock(mtx);
        return m_connections.size();
    }

private:
    // used when a new connection is established
    void on_open(con_hdl hdl){
        std::lock_guard lock(mtx);
        m_connections.insert(hdl);
        std::cout<<"[info] new connection, total: "<<m_connections.size()<<std::endl;
    }
    // used when a connection is closed
    void on_close(con_hdl hdl){
        std::lock_guard lock(mtx);
        m_connections.erase(hdl);
        std::cout<<"[info] connection closed, total: "<<m_connections.size()<<std::endl;
    }
    // used when a message is received
    void on_message(con_hdl hdl, msg_ptr msg){
        std::string payload = msg->get_payload();
        std::cout<<"[msg] "<<payload<<std::endl;
        send_text(hdl,"echo: "+payload); // echo back the message
    }
};

测试代码

cpp 复制代码
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include <doctest/doctest.h>
#include "../WebSocketServer.h"
#include <thread>
#include <websocketpp/client.hpp>
#include <websocketpp/config/asio_no_tls_client.hpp>
using client = websocketpp::client<websocketpp::config::asio_client>;
TEST_CASE("websocket echo") {
    WebSocketServer::WsServer srv;
    std::thread th([&srv](){
        srv.run(9003);   // 测试端口
    });
    // 等服务器启动
    std::this_thread::sleep_for(std::chrono::milliseconds(300));
    // 客户端
    client c;
    c.init_asio();
    websocketpp::connection_hdl hdl;
    std::string received;
    c.set_message_handler([&received](websocketpp::connection_hdl, client::message_ptr msg){
        received = msg->get_payload();
    });
    websocketpp::lib::error_code ec;
    auto con = c.get_connection("ws://localhost:9003", ec);
    REQUIRE(!ec);
    c.connect(con);
    // 再开一条线程跑 io
    std::thread io([&c](){ c.run(); });
    // 发消息
    while (!c.get_con_from_hdl(con)->get_state() == websocketpp::session::state::open)
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    c.send(con, "hello doctest", websocketpp::frame::opcode::text);
    // 等回包
    for (int i = 0; i < 50 && received.empty(); ++i)
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    CHECK(received == "echo: hello doctest");
    c.close(con, websocketpp::close::status::going_away, "");
    io.join();
    srv.stop();
    th.join();
}

项目结构如下:

cpp 复制代码
WebSocketServer.h 
CMakeList.txt
test/test_ws.cpp

CMakeLists.txt:

bash 复制代码
cmake_minimum_required(VERSION 3.14)
project(ws_server LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
find_package(Boost REQUIRED COMPONENTS system)
include_directories(${Boost_INCLUDE_DIRS})
# --------------- 单测可执行文件 -----------------
add_executable(test_ws
    test/test_ws.cpp
)
target_link_libraries(test_ws ${Boost_LIBRARIES} pthread)

启动

bash 复制代码
cmake -B build -DCMAKE_BUILD_TYPE=Debug
cmake --build build
./build/test_ws 

结果

bash 复制代码
ubuntu@hao:~/Cpp_learn/Network_Communication_Component/WebSocketServer/build$ ./test_ws
[doctest] doctest version is "2.4.8"
[doctest] run with "--help" for options
[info] server listening on 9003
[2025-10-16 11:36:14] [connect] Successful connection
[2025-10-16 11:36:14] [connect] WebSocket Connection [::ffff:127.0.0.1]:41769 v13 "WebSocket++/0.8.2" / 101
[info] new connection, total: 1
[2025-10-16 11:36:14] [connect] WebSocket Connection 127.0.0.1:9003 v-2 "WebSocket++/0.8.2" / 101
[2025-10-16 11:36:14] [frame_header] Dispatching write containing 1 message(s) containing 6 header bytes and 13 payload bytes
[2025-10-16 11:36:14] [frame_header] Header Bytes: 
[0] (6) 81 8D 78 D6 9D 74 

[2025-10-16 11:36:14] [frame_payload] Payload Bytes: 
[0] (13) [1] �����


[msg] hello doctest
[2025-10-16 11:36:14] [frame_header] Dispatching write containing 1 message(s) containing 2 header bytes and 19 payload bytes
[2025-10-16 11:36:14] [frame_header] Header Bytes: 
[0] (2) 81 13 

[2025-10-16 11:36:14] [frame_header] Dispatching write containing 1 message(s) containing 6 header bytes and 2 payload bytes
[2025-10-16 11:36:14] [frame_header] Header Bytes: 
[0] (6) 88 82 57 23 46 8A 

[2025-10-16 11:36:14] [frame_payload] Payload Bytes: 
[0] (2) [8] 54 CA 

[2025-10-16 11:36:14] [control] Control frame received with opcode 8
[2025-10-16 11:36:14] [frame_header] Dispatching write containing 1 message(s) containing 2 header bytes and 2 payload bytes
[2025-10-16 11:36:14] [frame_header] Header Bytes: 
[0] (2) 88 02 

[info] connection closed, total: 0
[2025-10-16 11:36:14] [disconnect] Disconnect close local:[1001] remote:[1001]
[2025-10-16 11:36:14] [control] Control frame received with opcode 8
[2025-10-16 11:36:14] [disconnect] Disconnect close local:[1001] remote:[1001]
===============================================================================
[doctest] test cases: 1 | 1 passed | 0 failed | 0 skipped
[doctest] assertions: 2 | 2 passed | 0 failed |
[doctest] Status: SUCCESS!

可以看到一些相关的日志消息,最后是关于doctest给出的测试成功的消息,具体测试了什么内容可以自行查看,或者只使用run,然后使用wscat -c ws://localhost:9002也可以进行简单连接。

结语

总的来说,Socket 是网络通信的 "基础工具",是实现各类网络协议的底层支撑;WebSocket 是基于 Socket(TCP)的 "专用方案",针对实时全双工通信场景优化,且兼容 HTTP 生态。

这下小H下次再遇到有关websocket的问题也能聊上俩句了。

相关推荐
Tipriest_18 小时前
C++ 中 using 的使用方法详解
c++·using
顾晨阳——19 小时前
C/C++字符串
c语言·c++·字符串
深耕AI19 小时前
【MFC实用技巧】对话框“边框”属性四大选项:None、Thin、Resizing、对话框外框,到底怎么选?
c++·mfc
ajassi200019 小时前
开源 C++ QT QML 开发(二十一)多媒体--视频播放
c++·qt·开源
siriuuus19 小时前
Nginx IP 透传
网络·nginx
仰泳的熊猫19 小时前
LeetCode:95. 不同的二叉搜索树 II
数据结构·c++·算法·leetcode
缘友一世19 小时前
漏洞扫描POC和web漏洞扫描工具
网络·安全·web安全
大海里的番茄19 小时前
随时随地看监控:我的UptimeKuma远程访问改造记
linux·网络
ArabySide19 小时前
【计算机网络】HTTP协议核心知识梳理
网络协议·计算机网络·http