背景
"你知道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的问题也能聊上俩句了。