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

目录

一、Websocket

二、安装

三、原理

四、报文格式

五、Websocketpp

六、常用接口

1、服务器类

2、日志等级

3、连接对象

4、HTTP相关

七、Websocket服务器

八、结语


一、Websocket

Websocket是从HTML5开始支持的一种网页端和服务端保持长连接的消息推送机制。

传统的web程序都是属于一问一答的形式,即客户端给服务器发送了一个HTTP请求,服务器给客户端返回一个HTTP响应。这种情况下服务器属于被动的一方,如果客户端不主动发起请求,服务器就无法主动给客户端响应。

例如像网页即时聊天或者五子棋这样的程序都非常依赖消息推送,即需要服务器主动推送消息到客户端。如果只使用原生的HTTP协议,要想实现消息推送一般需要通过轮询的方式实现,而轮询的成本较高且也不能及时的获取到消息的响应。

基于上述两个问题,就产生了WebSocket协议。WebSocket更接近于TCP这种级别的通信方式,一旦连接建立完成客户端或者服务器都可以主动的向对方发送数据。

二、安装

bash 复制代码
sudo apt-get install libboost-dev libboost-system-dev libwebsocketpp-dev # 安装Boost基础库、Boost系统库、WebSocket++库

三、原理

WebSocket协议本质上是一个基于TCP的协议,为了建立一个WebSocket连接,客户端浏览器首先要向服务器发起一个HTTP请求,这个请求和通常的HTTP请求不同,包含了一些附加头信息,通过这个附加头信息完成握手并升级协议的过程。

具体协议升级的过程如下:

四、报文格式

FIN:WebSocket传输数据以消息为概念单位,一个消息有可能由一个或多个帧组成,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位、7+16位、7+64位。假设Payload length=x。

x为0~126:数据的长度为x字节

x为126:后续2个字节代表一个16位的无符号整数,该无符号整数的值为数据的长度。

x为127:后续8个字节代表一个64位的无符号整数(最高位为0),该无符号整数的值为数据的长度。

Mask-Key:当mask为1时存在,长度为4字节,解码规则:DECODEDi=ENCODEDi^MASKi%4

Payload data:报文携带的载荷数据。

五、Websocketpp

WebSocketpp是一个跨平台的开源(BSD许可证)头部专用C++库,它实现了RFC6455(WebSocket协议)和RFC7692(WebSocketCompression Extensions)。它允许将WebSocket客户端和服务器功能集成到C++程序中。在最常见的配置中,全功能网络I/O由Asio网络库提供。

WebSocketpp的主要特性包括:

1、事件驱动的接口

2、支持HTTP/HTTPS、WS/WSS、IPv6

3、灵活的依赖管理---Boost库/C++11标准库

4、可移植性:Posix/Windows、32/64bit、Intel/ARM

5、线程安全

WebSocketpp同时支持HTTP和Websocket两种网络协议,该库可以作为项目的依赖库用来搭建HTTP和WebSocket服务器。

以下是WebSocketpp项目的一些常见网站,链接如下:

github:https://github.com/zaphoyd/websocketpp??

用户手册:http://?http://docs.websocketpp.org/?

官网:http://www.zaphoyd.com/websocketpp?

六、常用接口

1、服务器类

cpp 复制代码
namespace websocketpp {
    typedef lib::weak_ptr<void> connection_hdl;
    template <typename config>
    class endpoint : public config::socket_type {
    typedef lib::shared_ptr<lib::asio::steady_timer> timer_ptr;
    typedef typename connection_type::ptr connection_ptr;
    typedef typename connection_type::message_ptr message_ptr;
    typedef lib::function<void(connection_hdl)> open_handler;
    typedef lib::function<void(connection_hdl)> close_handler;
    typedef lib::function<void(connection_hdl)> http_handler;
    typedef lib::function<void(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 endpoint<connection<config>,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++库的相应日志等级系统定义源码:

cpp 复制代码
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、连接对象

cpp 复制代码
template <typename config>
class connection
: public config::transport_type::transport_con_type
, public config::connection_base
{
    /*发送数据接⼝*/
    error_code send(std::string&payload, frame::opcode::value
    op=frame::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相关

cpp 复制代码
namespace http {
    namespace parser {
    class parser {
        std::string const & get_header(std::string const & key)
        std::string const & get_body()
        typedef std::map<std::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服务器。

cpp 复制代码
#include <iostream>
#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/server.hpp>
using namespace std;
typedef websocketpp::server<websocketpp::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 html><html><head>"
    << "<title>hello websocket</title><body>"
    << "<h1>hello 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*server,websocketpp::connection_hdl hdl){ cout<<"连接成功"<<endl;},当WebSocket连接建立成功时调用OnOpen打印提示信息,void OnClose(websocketsvr* server,websocketpp::connection_hdl hdl){ cout<<"连接关闭"<<endl;},连接关闭时OnClose同样打印提示,当收到WebSocket消息时,void OnMessage(websocketsvr* server,websocketpp::connection_hdl hdl,message_ptr msg){cout<<"收到消息"<<msg->get_payload()<<endl;server->send(hdl,msg->get_payload(),websocketpp::frame::opcode::text);},OnMessage不仅打印消息内容,还会将收到的消息原样回发给客户端,实现了一个简单的回显服务;此外,void OnHttp(websocketsvr* server,websocketpp::connection_hdl hdl){ websocketsvr::connection_ptr con=server->get_con_from_hdl(hdl),std::stringstream ss,con->set_body(ss.str()),con->set_status(websocketpp::http::status_code::ok),OnHttp函数负责处理普通的HTTP请求,它会返回一个简单的HTML页面作为欢迎界面。在main函数中,服务器先关闭了所有日志输出,然后初始化网络框架,注册四个回调函数,最后绑定端口、开始接受连接并进入事件循环。

编译运行:

bash 复制代码
g++ -o server server.cc
./server

g++ -o server server.cc,编译生成server可执行文件,./server启动服务器,服务器开始运行,监听8888端口。

使用浏览器作为http客户端,即可访问服务器,如下图所示:

WebSocket客户端:

cpp 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Test Websocket</title>
</head>
<body>
<input type="text" id="message">
<button id="submit">提交</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凭借其简洁的双向通信模型和成熟的生态,成为大多数实时应用的不二选择!

·

相关推荐
winfreedoms2 小时前
VMware 桥接模式无法联网:从 DHCPDISCOVER 到静态 IP 的完整排障实战
网络协议·tcp/ip·ubuntu·桥接模式·vmware·嵌入式学习
JAVA面经实录9172 小时前
网络编程基础(Java Web/分布式前置·完整版)(十一)
java·前端·网络
記億揺晃着的那天13 小时前
NAS 内网域名访问为什么需要浏览器授权
网络·nginx·js·nas
chunmiao303214 小时前
DeepSeek V4-Pro 正式版上线,API 8月17日起分时调价
网络·安全
迪康Defender16 小时前
AI 重构终端安全运营:智能分析中枢 AI Insight 模块架构与落地场景深度解析
运维·网络·人工智能·其他·安全·重构·架构
0x5316 小时前
Java网络编程(二)
java·服务器·网络
AI领路人.17 小时前
[特殊字符] 用 OpenClaw 实现一个 C++ 复刻版 QuantClaw
websocket·单元测试·discord·rest api·内存占用·c++17
微硬创新17 小时前
老旧产线改造:耐达讯自动化16路4‑20mA转PROFINET的工程实践
人工智能·网络协议·自动化·信息与通信
西安景驰电子18 小时前
《PTP精确时间协议系列》第一篇:从原理到应用,全面解读IEEE 1588
linux·运维·服务器·开发语言·网络·windows·php