如何实现长连接通信机制?Websocket的最佳实践

📅 2026/8/16 8:24:58
如何实现长连接通信机制?Websocket的最佳实践
目录一、Websocket二、安装三、原理四、报文格式五、Websocketpp六、常用接口1、服务器类2、日志等级3、连接对象4、HTTP相关七、Websocket服务器八、结语一、WebsocketWebsocket是从HTML5开始支持的一种网页端和服务端保持长连接的消息推送机制。传统的web程序都是属于一问一答的形式即客户端给服务器发送了一个HTTP请求服务器给客户端返回一个HTTP响应。这种情况下服务器属于被动的一方如果客户端不主动发起请求服务器就无法主动给客户端响应。例如像网页即时聊天或者五子棋这样的程序都非常依赖消息推送即需要服务器主动推送消息到客户端。如果只使用原生的HTTP协议要想实现消息推送一般需要通过轮询的方式实现而轮询的成本较高且也不能及时的获取到消息的响应。基于上述两个问题就产生了WebSocket协议。WebSocket更接近于TCP这种级别的通信方式一旦连接建立完成客户端或者服务器都可以主动的向对方发送数据。二、安装sudo apt-get install libboost-dev libboost-system-dev libwebsocketpp-dev # 安装Boost基础库、Boost系统库、WebSocket库三、原理WebSocket协议本质上是一个基于TCP的协议为了建立一个WebSocket连接客户端浏览器首先要向服务器发起一个HTTP请求这个请求和通常的HTTP请求不同包含了一些附加头信息通过这个附加头信息完成握手并升级协议的过程。具体协议升级的过程如下四、报文格式FINWebSocket传输数据以消息为概念单位一个消息有可能由一个或多个帧组成FIN字段为1表示末尾帧。RSV1-3:保留字段只在扩展时使用若未启用扩展则应置为1若收到不全为0的数据帧且未协商扩展则立即终止连接。opcode:标志当前数据帧的类型0x0表示这是个延续帧当opcode为0表示本次数据传输采用了数据分片当前收到的帧为其中一个分片。0x1表示这是文本帧。0x2表示这是二进制帧。0x3-0x7保留暂未使用。0x8表示连接断开。0x9表示ping帧。0xa表示pong帧。0xb-0xf保留暂未使用。mask表示Payload数据是否被编码若为1则必有Mask-Key用于解码Payload数据。仅客户端发送给服务端的消息需要设置。Payload length数据载荷的长度单位是字节有可能为7位、716位、764位。假设Payload lengthx。x为0~126数据的长度为x字节x为126后续2个字节代表一个16位的无符号整数该无符号整数的值为数据的长度。x为127后续8个字节代表一个64位的无符号整数(最高位为0)该无符号整数的值为数据的长度。Mask-Key当mask为1时存在长度为4字节解码规则DECODED[i]ENCODED[i]^MASK[i%4]。Payload data报文携带的载荷数据。五、WebsocketppWebSocketpp是一个跨平台的开源(BSD许可证)头部专用C库它实现了RFC6455(WebSocket协议)和RFC7692(WebSocketCompression Extensions)。它允许将WebSocket客户端和服务器功能集成到C程序中。在最常见的配置中全功能网络I/O由Asio网络库提供。WebSocketpp的主要特性包括1、事件驱动的接口2、支持HTTP/HTTPS、WS/WSS、IPv63、灵活的依赖管理—Boost库/C11标准库4、可移植性Posix/Windows、32/64bit、Intel/ARM5、线程安全WebSocketpp同时支持HTTP和Websocket两种网络协议该库可以作为项目的依赖库用来搭建HTTP和WebSocket服务器。以下是WebSocketpp项目的一些常见网站链接如下githubhttps://github.com/zaphoyd/websocketpp??用户手册http://?http://docs.websocketpp.org/?官网http://www.zaphoyd.com/websocketpp?六、常用接口1、服务器类namespace websocketpp { typedef lib::weak_ptrvoid connection_hdl; template typename config class endpoint : public config::socket_type { typedef lib::shared_ptrlib::asio::steady_timer timer_ptr; typedef typename connection_type::ptr connection_ptr; typedef typename connection_type::message_ptr message_ptr; typedef lib::functionvoid(connection_hdl) open_handler; typedef lib::functionvoid(connection_hdl) close_handler; typedef lib::functionvoid(connection_hdl) http_handler; typedef lib::functionvoid(connection_hdl,message_ptr) message_handler; /* websocketpp::log::alevel::none 禁⽌打印所有⽇志*/ void set_access_channels(log::level channels); /*设置⽇志打印等级*/ void clear_access_channels(log::level channels); /*清除指定等级的⽇志*/ /*设置指定事件的回调函数*/ void set_open_handler(open_handler h); /*websocket握⼿成功回调处理函数*/ void set_close_handler(close_handler h); /*websocket连接关闭回调处理函数 */ void set_message_handler(message_handler h); /*websocket消息回调处理函数 */ void set_http_handler(http_handler h); /*http请求回调处理函数*/ /*发送数据接⼝*/ void send(connection_hdl hdl, std::string payload, frame::opcode::value op); void send(connection_hdl hdl, void* payload, size_t len, frame::opcode::value op); /*关闭连接接⼝*/ void close(connection_hdl hdl, close::status::value code, std::string reason); /*获取connection_hdl 对应连接的connection_ptr*/ connection_ptr get_con_from_hdl(connection_hdl hdl); /*websocketpp基于asio框架实现init_asio⽤于初始化asio框架中的io_service调 度器*/ void init_asio(); /*设置是否启⽤地址重⽤*/ void set_reuse_addr(bool value); /*设置endpoint的绑定监听端⼝*/ void listen(uint16_t port); /*对io_service对象的run接⼝封装⽤于启动服务器*/ std::size_t run(); /*websocketpp提供的定时器以毫秒为单位*/ timer_ptr set_timer(long duration, timer_handler callback); }; template typename config class server : public endpointconnectionconfig,config { /*初始化并启动服务端监听连接的accept事件处理*/ void start_accept(); } namespace message_buffer { /*获取websocket请求中的payload数据类型*/ frame::opcode::value get_opcode(); /*获取websocket中payload数据*/ std::string const get_payload(); }; }以上源码是WebSocket库中服务器端类的核心接口定义主要包括了使用这个库时可以用到的各种功能。它把WebSocket服务器封装成了一个端点对象可以通过设置多个回调函数来处理不同的事件比如当客户端成功建立连接、收到新消息或者连接关闭时这些回调函数就会被自动调用。同时源码还提供了发送数据、关闭连接、设置定时器等操作接口。此外源码还包含了服务器的生命周期管理方法例如初始化网络框架、监听端口和启动事件循环。WebSocket的整个设计思路将底层的网络实现细节隐藏起来让开发者只需要关注业务逻辑即可。2、日志等级WebSocketpp库内有相应的日志输出模块以下是WebSocket库的相应日志等级系统定义源码namespace log { struct alevel { static level const none 0x0; static level const connect 0x1; static level const disconnect 0x2; static level const control 0x4; static level const frame_header 0x8; static level const frame_payload 0x10; static level const message_header 0x20; static level const message_payload 0x40; static level const endpoint 0x80; static level const debug_handshake 0x100; static level const debug_close 0x200; static level const devel 0x400; static level const app 0x800; static level const http 0x1000; static level const fail 0x2000; static level const access_core 0x00003003; static level const all 0xffffffff; }; }通过一组十六机制位掩码常量将日志信息划分为多个细粒度的类别比如连接建立、断开、控制帧、帧头帧体、消息头消息体、端点状态、握手调试、HTTP处理等开发者可以通过按位组合这些常量精确控制哪些类型的日志需要输出。这种设计能让开发者灵活地开启或关闭特定模块的调试信息便于在开发和生产环境中根据需求调整日志的详细程度。3、连接对象template typename config class connection : public config::transport_type::transport_con_type , public config::connection_base { /*发送数据接⼝*/ error_code send(std::stringpayload, frame::opcode::value opframe::opcode::text); /*获取http请求头部*/ std::string const get_request_header(std::string const key) /*获取请求正⽂*/ std::string const get_request_body(); /*设置响应状态码*/ void set_status(http::status_code::value code); /*设置http响应正⽂*/ void set_body(std::string const value); /*添加http响应头部字段*/ void append_header(std::string const key, std::string const val); /*获取http请求对象*/ request_type const get_request(); /*获取connection_ptr 对应的 connection_hdl */ connection_hdl get_handle(); }; namespace frame { namespace opcode { enum value { continuation 0x0, text 0x1, binary 0x2, ... close 0x8, ping 0x9, pong 0xA, ... };}}以上源码定义了WebSocket库的connection类它封装了单个WebSocket连接的所有操作。通过这个类可以向客户端发送文本或二进制数据读取HTTP请求的头部和正文设置响应状态码和自定义响应头以及获取连接句柄用于异步管理该类是Websocket处理每个客户端连接时的核心工具负责数据收发和HTTP握手阶段的交互。4、HTTP相关namespace http { namespace parser { class parser { std::string const get_header(std::string const key) std::string const get_body() typedef std::mapstd::string, std::string, utility::ci_less header_list; header_list const get_headers() } class request : public parser { /*获取请求⽅法*/ std::string const get_method() /*获取请求uri接⼝*/ std::string const get_uri() }; }}; namespace http { namespace status_code { enum value { uninitialized 0, continue_code 100, switching_protocols 101, ok 200, created 201, accepted 202, non_authoritative_information 203, no_content 204, reset_content 205, partial_content 206, multiple_choices 300, moved_permanently 301, found 302, see_other 303, not_modified 304, use_proxy 305, temporary_redirect 307, bad_request 400, unauthorized 401, payment_required 402, forbidden 403, not_found 404, method_not_allowed 405, not_acceptable 406, proxy_authentication_required 407, request_timeout 408, conflict 409, gone 410, length_required 411, precondition_failed 412, request_entity_too_large 413, request_uri_too_long 414, unsupported_media_type 415, request_range_not_satisfiable 416, expectation_failed 417, im_a_teapot 418, upgrade_required 426, precondition_required 428, too_many_requests 429, request_header_fields_too_large 431, internal_server_error 500, not_implemented 501, bad_gateway 502, service_unavailable 503, gateway_timeout 504, http_version_not_supported 505, not_extended 510, network_authentication_required 511 };}}以上源码是WebSocket库中HTTP协议解析相关的两个核心组件。一个是parser类和它的子类request提供了获取HTTP请求头部、正文、方法以及URI的接口通常在WebSocket握手阶段使用。另一个是status_code命名空间它完整定义了HTTP状态码枚举涵盖了从1xx信息相应到5xx服务器错误的所有标准状态码。整体来看这部分源码为WebSocket服务器处理初始握手请求和构造HTTP响应提供了完整的工具支持。七、Websocket服务器下面使用Websocket实现一个简单的Websocket服务器。#include iostream #include websocketpp/config/asio_no_tls.hpp #include websocketpp/server.hpp using namespace std; typedef websocketpp::serverwebsocketpp::config::asio websocketsvr; typedef websocketsvr::message_ptr message_ptr; using websocketpp::lib::placeholders::_1; using websocketpp::lib::placeholders::_2; using websocketpp::lib::bind; // websocket连接成功的回调函数 void OnOpen(websocketsvr *server,websocketpp::connection_hdl hdl){ cout连接成功endl; } // websocket连接成功的回调函数 void OnClose(websocketsvr *server,websocketpp::connection_hdl hdl){ cout连接关闭endl; } // websocket连接收到消息的回调函数 void OnMessage(websocketsvr *server,websocketpp::connection_hdl hdl,message_ptr msg){ cout 收到消息 msg-get_payload() endl; // 收到消息将相同的消息发回给websocket客⼾端 server-send(hdl, msg-get_payload(), websocketpp::frame::opcode::text); } // websocket连接异常的回调函数 void OnFail(websocketsvr *server,websocketpp::connection_hdl hdl){ cout连接异常endl; } // 处理http请求的回调函数 返回⼀个html欢迎⻚⾯ void OnHttp(websocketsvr *server,websocketpp::connection_hdl hdl){ cout处理http请求endl; websocketsvr::connection_ptr con server-get_con_from_hdl(hdl); std::stringstream ss; ss !doctype htmlhtmlhead titlehello websocket/titlebody h1hello websocketpp/h1 /body/head/html; con-set_body(ss.str()); con-set_status(websocketpp::http::status_code::ok); } int main(){ // 使⽤websocketpp库创建服务器 websocketsvr server; // 设置websocketpp库的⽇志级别 // all表⽰打印全部级别⽇志 // none表⽰什么⽇志都不打印 server.set_access_channels(websocketpp::log::alevel::none); server.init_asio(); // 注册http请求的处理函数 server.set_http_handler(bind(OnHttp, server, ::_1)); // 注册websocket请求的处理函数 server.set_open_handler(bind(OnOpen, server, ::_1)); server.set_close_handler(bind(OnClose, server, _1)); server.set_message_handler(bind(OnMessage,server,_1,_2)); // 监听8888端⼝ server.listen(8888); // 开始接收tcp连接 server.start_accept(); // 开始运⾏服务器 server.run(); return 0; }以上就是使用WebSocket库实现的WebSocket服务器程序。核心功能是创建一个监听8888端口的服务器能够同时处理HTTP请求和Websocket连接。云服务器开放8888端口程序定义了四个主要的事件回调函数void OnOpen(websocketsvr*serverwebsocketpp::connection_hdl hdl){ cout连接成功endl;}当WebSocket连接建立成功时调用OnOpen打印提示信息void OnClose(websocketsvr* serverwebsocketpp::connection_hdl hdl){ cout连接关闭endl;}连接关闭时OnClose同样打印提示当收到WebSocket消息时void OnMessage(websocketsvr* serverwebsocketpp::connection_hdl hdlmessage_ptr msg){cout收到消息msg-get_payload()endlserver-send(hdl,msg-get_payload(),websocketpp::frame::opcode::text);}OnMessage不仅打印消息内容还会将收到的消息原样回发给客户端实现了一个简单的回显服务此外void OnHttp(websocketsvr* serverwebsocketpp::connection_hdl hdl){ websocketsvr::connection_ptr conserver-get_con_from_hdl(hdl),std::stringstream sscon-set_body(ss.str())con-set_status(websocketpp::http::status_code::ok)OnHttp函数负责处理普通的HTTP请求它会返回一个简单的HTML页面作为欢迎界面。在main函数中服务器先关闭了所有日志输出然后初始化网络框架注册四个回调函数最后绑定端口、开始接受连接并进入事件循环。编译运行g -o server server.cc ./serverg -o server server.cc编译生成server可执行文件./server启动服务器服务器开始运行监听8888端口。使用浏览器作为http客户端即可访问服务器如下图所示WebSocket客户端!DOCTYPE html html langen head meta charsetUTF-8 meta http-equivX-UA-Compatible contentIEedge meta nameviewport contentwidthdevice-width, initial-scale1.0 titleTest Websocket/title /head body input typetext idmessage button idsubmit提交/button script // 创建 websocket 实例 // ws://49.235.100.141:8888 // 类⽐http // ws表⽰websocket协议 // 49.235.100.141 表⽰服务器地址 // 8888表⽰服务器绑定的端⼝ let websocket new WebSocket(ws://49.235.100.141:8888); // 处理连接打开的回调函数 websocket.onopen function() { console.log(连接建⽴); } // 处理收到消息的回调函数 // 控制台打印消息 websocket.onmessage function(e) { console.log(收到消息: e.data); } // 处理连接异常的回调函数 websocket.onerror function() { console.log(连接异常); } // 处理连接关闭的回调函数 websocket.onclose function() { console.log(连接关闭); } // 实现点击按钮后, 通过 websocket实例 向服务器发送请求 let input document.querySelector(#message); let button document.querySelector(#submit); button.onclick function() { console.log(发送消息: input.value); websocket.send(input.value); } /script /body /html这段HTML为WebSocket测试客户端用于实现与上面实现的服务器(49.235.100.141:8888)进行WebSocket通信。页面提供了一个输入框和发送按钮点击按钮会通过WebSocket向服务器发送消息服务器原样回显后收到的消息会打印在浏览器控制台中。同时连接状态(建立、异常、关闭)也会在控制台输出。与服务器程序的对应关系new WebSocket(ws://49.235.100.141:8888)连接到服务器的8888端口会触发服务器的OnOpen回调。websocket.send(input.value)发送消息会触发服务器的OnMessage回调服务器执行server-send回显消息。客户端收到回显时触发onmessage在控制台打印。onerror对应服务器的OnFail。onclose对应服务器的OnClose。运行结果如下图所示可以看到连接建立、客户端和服务器通信以及关闭服务器就会看到断开连接的过程。八、结语Websocket协议的设计思想在于在HTTP之上建立持久化的全双工通道通过轻量级的帧头设计和高效的数据封装实现了低延迟的双向消息传递。通过引入WebSocket库实现了一个轻量级的WebSocket服务器涵盖了协议握手、事件驱动模型、消息收发及HTTP兼容处理等核心环节可以看到WebSocket在双向实时通信场景下的显著优势相较于传统HTTP轮询WebSocket在网络开销、延迟和服务器资源消耗方面均有数量级的改善WebSocket凭借其简洁的双向通信模型和成熟的生态成为大多数实时应用的不二选择·