目录
1.Websocket 协议
1.介绍
- WebSocket是从HTML5开始支持的一种网页端和服务端保持长连接的消息推送机制
- 产生原因 :
- 传统的web程序都是属于"一问一答"的形式
- 即客户端给服务器发送了一个HTTP请求,服务器给客户端返回一个HTTP响应
- 这种情况下服务器是属于被动的一方,如果客户端不主动发起请求服务器就无法主动给客户端响应
- 类似网页即时聊天这样的程序都是非常依赖"消息推送"的
- 即需要服务器主动推动消息到客户端
- 如果只是使用原生的HTTP协议 ,要想实现消息推送一般需要通过"轮询"的方式实现, 而轮询的成本比较高并且也不能及时的获取到消息的响应
- 基于上述两个问题, 就产生了
WebSocket
协议,WebSocket
更接近于TCP
这种级别的通信方式,一旦连接建立完成客户端或者服务器都可以主动的向对方发送数据
- 传统的web程序都是属于"一问一答"的形式
- 本质 :一个应用层的TCP长连接协议
- 搭建一个
Websocket
服务器其实就是搭建了一个TCP服务器,只不过应用层使用Websocket
协议格式进行处理
- 搭建一个
2.原理简介
HTTP
通信支持Websocket
协议的切换-
客户端浏览器首先要向服务器发起一个
HTTP
请求 -
这个请求和通常的
HTTP
请求不同,包含了一些附加头信息 -
通过这个附加头信息完成握手过程并升级协议的过程
-
2.Websocketpp
1.介绍
WebSocketpp
是一个跨平台的开源头部专用C++库- 它实现了
WebSocket
协议和WebSocketCompression Extensions
- 它允许将
WebSocket
客户端和服务器功能集成到C++程序中 - 在最常见的配置中,全功能网络I/O由
Asio
网络库提供
- 它实现了
WebSocketpp
的主要特性 :- 事件驱动的接口
- 支持
HTTTP/HTTPS, WS/WSS, IPv6
- 灵活的依赖管理 ---
Boost
库/C++11标准库 - 可移植性:Posix/Windows,32/64bit,Intel/ARM
- 线程安全
WebSocketpp
同时支持HTTP
和Websocket
两种网络协议, 比较适用于本项目, 所以选用该库作为项目的依赖库用来搭建WebSocket服务- 该项目常用网站 :
2.安装
bash
sudo apt-get install libboost-dev libboost-system-dev libwebsocketpp-dev
3.常用接口
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();
};
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 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 message_buffer
{
/*获取websocket请求中的payload数据类型*/
frame::opcode::value get_opcode();
/*获取websocket中payload数据*/
std::string const & get_payload();
}
namespace log
{
struct alevel
{
static level const none = 0x0;
// ...
};
}
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
};
}
}
namespace frame
{
namespace opcode
{
enum value
{
continuation = 0x0,
text = 0x1,
binary = 0x2,
rsv3 = 0x3,
rsv4 = 0x4,
rsv5 = 0x5,
rsv6 = 0x6,
rsv7 = 0x7,
close = 0x8,
ping = 0x9,
pong = 0xA,
control_rsvb = 0xB,
control_rsvc = 0xC,
control_rsvd = 0xD,
control_rsve = 0xE,
control_rsvf = 0xF,
};
}
}
}
4.使用
-
main.cc
:cpp#include <iostream> #include <websocketpp/config/asio_no_tls.hpp> #include <websocketpp/server.hpp> // 0.定义server_t类型 typedef websocketpp::server<websocketpp::config::asio> server_t; void OnOpen(websocketpp::connection_hdl hdl) { std::cout << "Websocket长连接建立成功" << std::endl; } void OnClose(websocketpp::connection_hdl hdl) { std::cout << "Websocket长连接断开" << std::endl; } void OnMessage(server_t* svr, websocketpp::connection_hdl hdl, server_t::message_ptr msg) { std::string body = msg->get_payload(); std::cout << "Get Msg: " << body << std::endl; auto conn = svr->get_con_from_hdl(hdl); conn->send(body + "-Response", websocketpp::frame::opcode::value::text); } int main() { // 1.实例化服务器对象 server_t svr; // 2.初始化日志输出 --> 关闭日志输出 svr.set_access_channels(websocketpp::log::alevel::none); // 3.初始化ASIO框架 svr.init_asio(); // 4.设置消息处理/连接握手成功/连接关闭回调函数 svr.set_open_handler(OnOpen); svr.set_close_handler(OnClose); auto msg_handler = std::bind(OnMessage, &svr, std::placeholders::_1, std::placeholders::_2); svr.set_message_handler(msg_handler); // 5.启用地址重用 svr.set_reuse_addr(true); // 6.设置监听端口 svr.listen(8686); // 7.开始监听 svr.start_accept(); // 8.启动服务器 svr.run(); return 0; }
-
makefile
:cppmain: main.cc g++ -o $@ $^ -std=c++17 -lboost_system .PHONY:clean clean: rm main
-
WS
客户端html<!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> let websocket = new WebSocket("ws://IP:PORT"); // 处理连接打开的回调函数 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>