目录
一、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凭借其简洁的双向通信模型和成熟的生态,成为大多数实时应用的不二选择!
·