【websocket】安装与使用_websocket安装
websocket安装与使用
- 1. 介绍
- 2. 安装
- 3. websocketpp常用接口
- 4. Websocketpp使用
-
- 4.1 服务端
- 4.2 客户端
1. 介绍
WebSocket 是从 HTML5 开始支持的一种网页端和服务端保持长连接的 消息推送机制。
- 传统的 web 程序都是属于 “一问一答” 的形式,即客户端给服务器发送了一个
HTTP 请求,服务器给客户端返回一个 HTTP 响应。这种情况下服务器是属于被动的一方,如果客户端不主动发起请求服务器就无法主动给客户端响应 - 像网页即时聊天这样的程序非常依赖 “消息推送” 的,即需要服务器主动推动消息到客户端。如果只是使用原生的 HTTP 协议,要想实现消息推送一般需要通过 “轮询” 的方式实现, 而轮询的成本比较高并且也不能及时的获取到消息的响应。
基于上述两个问题, 就产生了 WebSocket 协议。WebSocket 更接近于 TCP 这种级别的通信方式,一旦连接建立完成客户端或者服务器都可以主动的向对方发送数据。
原理解析
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 字节,解码规则: DECODED[i] =
ENCODED[i] ^ MASK[i % 4] -
Payload data: 报文携带的载荷数据
Websocketpp 介绍
WebSocketpp 是一个跨平台的开源(BSD 许可证)头部专用 C++库,它实现了
RFC6455(WebSocket 协议)和 RFC7692(WebSocketCompression Extensions)。它允许将 WebSocket 客户端和服务器功能集成到 C++程序中。在最常见的配置中,全功能网络 I/O 由 Asio 网络库提供。
WebSocketpp 的主要特性包括:
- 事件驱动的接口
- 支持 HTTP/HTTPS、WS/WSS、IPv6
- 灵活的依赖管理 — Boost 库/C++11 标准库
- 可移植性:Posix/Windows、32/64bit、Intel/ARM
- 线程安全
WebSocketpp 同时支持 HTTP 和 Websocket 两种网络协议,可以该库作为项目的依赖库用来搭建 HTTP 和 WebSocket 服务器。
总结
websocket是一个应用层的tcp长连接协议。搭建一个websocket服务器其实就是搭建一个tcp服务器,只不过应用层使用websocket协议格式进行数据处理。
与此对比的就是httplib,它是一个短连接,只是让我快速搭建一个Http服务器,让我们重点关注业务处理。假如在一个项目中,不单单是 请求 - 响应 的业务处理,还包含了数据的主动推送,而这种消息数据的主动推送,是Http协议无法实现的。它只能是客户端发起请求,然后服务器收到给一个响应。因此需要搭建一个长连接的服务器,用于服务端主动向客户端推送数据。
选择websocket协议的考虑:因为Http通信支持websocket协议的切换。
websocket通信框架的选择:websocketpp 即支持websocket通信,也支持http通信。
2. 安装
sudo apt-get install libboost-dev libboost-system-dev libwebsocketpp-dev
3. websocketpp常用接口
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*/ //weak_ptr无法对对象直接操作,必须要获得对应的shared_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_handlercallback); };//继承endpoint 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() }; } }; class 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; 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; }; } 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. Websocketpp使用
4.1 服务端
websocketpp搭建服务器流程:
- 定义server类型
- 实例化服务器对象
- 初始化日志输出 – 关闭日志输出
- 初始化asio框架
- 设置消息处理/连接握手成功/连接关闭回调函数/连接异常回调函数
- 启动地址重用
- 设置监听窗口
- 开始监听
- 启动服务器
#include#include#include#include// 1. 定义server类型typedef websocketpp::server<websocketpp::config::asio> websocketsvr;//websocket握手成功回调函数void onopen(websocketpp::connection_hdl hdl){ std::cout<<\"websocket长连接建立成功\"<<std::endl;}//websocket 连接关闭回调处理函数void onclose(websocketpp::connection_hdl hdl){ std::cout<<\"websocket关闭连接\"<<std::endl;}// websocket 连接异常的回调函数void onfail(websocketsvr *server,websocketpp::connection_hdl hdl){ std::cout<<\"websocket连接异常\"<<std::endl;}//websocket 消息回调处理函数void onmessage(websocketsvr* server,websocketpp::connection_hdl hdl,websocketsvr::message_ptr msg){ //1. 获取有效消息载荷数据,进行业务处理 std::string body = msg->get_payload(); std::cout<<\"收到客户端消息: \"<<body<<std::endl; //2. 对客户端进行响应 //获取通信连接 auto conn = server->get_con_from_hdl(hdl); //发送数据 conn->send(body + \"服务器回复\",websocketpp::frame::opcode::text);}//http 请求回调处理函数void onhttp(websocketsvr* server,websocketpp::connection_hdl hdl){ auto conn = server->get_con_from_hdl(hdl); std::stringstream ss; ss << \"\" << \"hello websocket \" << \"hello websocketpp
\" << \"\"; conn->set_body(ss.str()); conn->set_status(websocketpp::http::status_code::ok);}int main(){ // 2. 实例化服务器对象 websocketsvr server; // 3. 初始化日志输出 -- 关闭日志输出 // all 表示打印全部级别日志 // none 表示什么日志都不打印 server.set_access_channels(websocketpp::log::alevel::none); // 4. 初始化asio框架 server.init_asio(); // 5. 设置消息处理/连接握手成功/连接关闭回调函数/连接异常回调函数 server.set_open_handler(onopen); server.set_close_handler(onclose); server.set_fail_handler(std::bind(onfail,&server,std::placeholders::_1)); server.set_message_handler(std::bind(onmessage,&server,std::placeholders::_1,std::placeholders::_2)); server.set_http_handler(std::bind(onhttp,&server,std::placeholders::_1)); // 6. 启动地址重用 server.set_reuse_addr(true); // 7. 设置监听窗口 server.listen(8080); // 8. 开始监听 server.start_accept(); // 9. 启动服务器 server.run(); return 0;}
server:server.ccg++ -o $@ $^ -std=c++17 -lpthread -lboost_system
4.2 客户端
Http 客户端
使用浏览器作为 http 客户端即可, 访问服务器的 8080 端口。
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> // 创建 websocket 实例 // ws://124.223.54.148:8080 // 类比 http // ws 表示 websocket 协议 // 192.168.51.100 表示服务器地址 // 8888 表示服务器绑定的端口 let websocket = new WebSocket(\"ws://124.223.54.148:8080\"); // 处理连接打开的回调函数 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>