Websocketpp的介绍与使用

371 阅读19分钟

Websocketpp的介绍与使用

老师讲解

websocket协议

使用: 客户端使用http同服务器发起协议切换请求; 服务器同意切换, 接下来就会转为websocket的长连接通信(不会因为长时间无通信而关闭连接,且随意收发信息)

切换过程:

http request:

​ GET /ws HTTP/1.1

​ Connection: Upgrade

​ Upgrade: websocket

​ Sec-WebSocket-Key: xxxxxxxxxxxxxx密钥字符串

http reponse:

​ HTTP/1.1 101 switching protocol

​ Connection: Upgrade

​ Upgrade: websocket

​ Sec-WebSocket-Accept: xxxxxxxxxxxxxxxx

客户端发送的密钥 + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 进行SHA-1计算

Websocketpp的简单介绍:

基于boost库的asio框架实现

Websocket介绍

WebSocket 是从 HTML5 开始⽀持的⼀种⽹⻚端和服务端保持⻓连接的 消息推送机制。

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

• 像⽹⻚即时聊天或者我们做的五⼦棋游戏这样的程序都是⾮常依赖 "消息推送" 的, 即需要服务器主动推动消息到客⼾端。如果只是使⽤原⽣的 HTTP 协议,要想实现消息推送⼀般需要通过 "轮询" 的⽅式实现, ⽽轮询的成本⽐较⾼并且也不能及时的获取到消息的响应。

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

原理解析

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

image-20240227183627956

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

image-20240227183847348

报文格式

image-20240227183919235

报文字段比较多,我们重点关注这几个字段:

• 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服务器。

下⾯是该项⽬的⼀些常⽤⽹站, ⼤家多去学习。

• github:github.com/zaphoyd/web…

• ⽤⼾⼿册: docs.websocketpp.org/

• 官⽹:www.zaphoyd.com/websocketpp

Websocketpp使用

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*/
		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)
		}
		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;
			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,
};}}
}

Simple http/websocket服务器

使⽤Websocketpp实现⼀个简单的http和websocket服务器

#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);
    /*初始化asio*/
    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;
}    

Http客户端

使⽤浏览器作为http客⼾端即可, 访问服务器的8888端⼝。

image-20240227192141897

WS客户端

<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://192.168.51.100:8888
        // 类⽐http
        // ws表⽰websocket协议
        // 192.168.51.100 表⽰服务器地址
        // 8888表⽰服务器绑定的端⼝
        let websocket = new WebSocket("ws://192.168.51.100: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>

在控制台中我们可以看到连接建⽴、客⼾端和服务器通信以及断开连接的过程(关闭服务器就会看到断开连接的现象)

image-20240227192649725

注: 通过f12 或者 fn + f12打开浏览器的调试模式

Websocketpp的介绍与使用

老师讲解

websocket协议

使用: 客户端使用http同服务器发起协议切换请求; 服务器同意切换, 接下来就会转为websocket的长连接通信(不会因为长时间无通信而关闭连接,且随意收发信息)

切换过程:

http request:

​ GET /ws HTTP/1.1

​ Connection: Upgrade

​ Upgrade: websocket

​ Sec-WebSocket-Key: xxxxxxxxxxxxxx密钥字符串

http reponse:

​ HTTP/1.1 101 switching protocol

​ Connection: Upgrade

​ Upgrade: websocket

​ Sec-WebSocket-Accept: xxxxxxxxxxxxxxxx

客户端发送的密钥 + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 进行SHA-1计算

Websocketpp的简单介绍:

基于boost库的asio框架实现

Websocket介绍

WebSocket 是从 HTML5 开始⽀持的⼀种⽹⻚端和服务端保持⻓连接的 消息推送机制。

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

• 像⽹⻚即时聊天或者我们做的五⼦棋游戏这样的程序都是⾮常依赖 "消息推送" 的, 即需要服务器主动推动消息到客⼾端。如果只是使⽤原⽣的 HTTP 协议,要想实现消息推送⼀般需要通过 "轮询" 的⽅式实现, ⽽轮询的成本⽐较⾼并且也不能及时的获取到消息的响应。

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

原理解析

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

image-20240227183627956

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

image-20240227183847348

报文格式

image-20240227183919235

报文字段比较多,我们重点关注这几个字段:

• 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服务器。

下⾯是该项⽬的⼀些常⽤⽹站, ⼤家多去学习。

• github:github.com/zaphoyd/web…

• ⽤⼾⼿册: docs.websocketpp.org/

• 官⽹:www.zaphoyd.com/websocketpp

Websocketpp使用

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*/
		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)
		}
		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;
			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,
};}}
}

Simple http/websocket服务器

使⽤Websocketpp实现⼀个简单的http和websocket服务器

#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);
    /*初始化asio*/
    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;
}    

Http客户端

使⽤浏览器作为http客⼾端即可, 访问服务器的8888端⼝。

image-20240227192141897

WS客户端

<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://192.168.51.100:8888
        // 类⽐http
        // ws表⽰websocket协议
        // 192.168.51.100 表⽰服务器地址
        // 8888表⽰服务器绑定的端⼝
        let websocket = new WebSocket("ws://192.168.51.100: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>

在控制台中我们可以看到连接建⽴、客⼾端和服务器通信以及断开连接的过程(关闭服务器就会看到断开连接的现象)

image-20240227192649725

注: 通过f12 或者 fn + f12打开浏览器的调试模式