轴承 网站建设 企炬,建设门户网站的目的和需求,做网站添加本地图片,传奇网站模板使用目录 第三方协议#xff1a;websocket
websocket简介
websocket特点
websocket协议切换 websocket协议格式段
websocketpp库介绍
endpoint
server connection
websocketpp库搭建服务器流程
基本框架实现
业务处理回调函数的实现
http_callback
open_callback …目录 第三方协议websocket
websocket简介
websocket特点
websocket协议切换 websocket协议格式段
websocketpp库介绍
endpoint
server connection
websocketpp库搭建服务器流程
基本框架实现
业务处理回调函数的实现
http_callback
open_callback
close_callback
message_callback 第三方协议websocket
websocket简介 为什么要有websocket websocket协议是应用层协议之一既然这种协议会出现那么也就意味着它是在某些特殊场景下弥补了一些其他应用层协议的缺点。
所以关于为什么要有websocket协议我们首先要聊聊最常见的协议http/https它们没有解决的一些问题
我们都知道http协议是如何建立起连接的呢
http协议规定首先由客户端向服务器发送一个http请求 服务器收到后根据客户端的http请求返回给客户端一个http应答补充来回一次报文发送http双方通信连接就会被关闭这也意味着http协议在通常情况下是无连接或短连接的协议
http协议上述建立连接的方式带来了一个问题必须由客户端主动向服务器发送请求报文服务器无法主动给客户端发送消息这也就意味着http协议是一个单向通信的协议
而网页的即时聊天或者像我们接下来做的项目“五子棋游戏”这样的程序都是非常依赖服务器给客户端发送消息的
若我们想要用http协议来完成这个功能那么只能是客户端等待一段时间之后主动向服务器询问是否有消息。也就是基于轮询的策略。
轮询策略带来了几个弊端
消息不够实时客户端是按一定的时间间隔向服务器发送轮询的那么从消息准备就绪到服务器收到轮询之间肯定有一定的时间间隔。这样的话就影响了整体的效率成本过高轮询意味着客户端需要不断发送请求到网络中而大部分的请求都是无意义的即服务器还未准备就绪的。
基于上述的种种最终产生了websocket协议很明显websocket协议是为了解决http协议的弊端这两种协议具有强相关。所以后面我们说websocket协议通常是由http协议切换过来的 websocket特点
websocket协议与http协议相比主要具有以下特点
websocket协议支持双向通信即服务器可以主动给客户端发送消息客户端也可以主动给服务器发送消息websocket协议是一种长连接协议通常与TCP协议相同websocket会为通信双方建立长时间的连接使得通信双方通信时不再需要频繁建立连接。这降低了延迟提高了实时性使得数据可以更快地传输到客户端。 websocket协议切换
websocket协议是由http协议切换过来的整体切换如下图 整体协议切换一共分为三步
第一步TCP三次握手建立连接 第二步通过http报文由http协议切换到websocket协议第三步websocket协议格式通信 TCP三次握手建立连接 不管是websocket协议还是http协议都是基于TCP协议的应用层协议。
所以对于websocket协议来说它需要为通信双方建立一个长连接最好的方式就是TCP的方式 http协议切换至websocket协议 协议的切换是通过http报文的方式实现的。即客户端向服务器发送http请求服务器给客户端http应答之后进行协议切换。
但不同的是协议切换的请求和应答的格式不再与普通http报文格式相同
协议切换请求报文的格式
GET /ws HTTP/1.1该行表示GET方法ws是websocket的缩写ConnectionUpgrade该行表示客户端希望升级当前的HTTP连接到一个新的协议UpgradeWebSocket该行表示升级的协议名称为WebSocketSec-WebSocket-Versionxxx该行表示升级的协议版本为xxxSec-WebSocket-Keyxxx是一个由客户端通常是浏览器随机生成的Base64编码值。这个值在WebSocket握手请求中发送给服务器用于确保客户端和服务器之间的连接是安全的并且不是由恶意软件或未授权的第三方建立的。
协议切换应答报文的格式
HTTP/1.1 101 xxx其中101是响应状态码即告诉客户端支持WebSocket协议ConnectionUpgrade与请求报文对应字段含义相同UpgradeWebSocket与请求报文对应字段含义相同Sec-WebSocket-AcceptSec-WebSocket-Accept是服务器在WebSocket握手过程中根据客户端发送的Sec-WebSocket-Key字段通过一定的算法计算并返回给客户端的一个响应头字段。它用于验证服务器是否理解并接受客户端发起的WebSocket连接请求同时也作为WebSocket连接安全性的一个基本保障。
接下来的内容就是对websocket协议格式段进行介绍 websocket协议格式段
websocket协议格式段主要如下图 FIN(1bit) WebSocket支持将长消息切割成若干帧发送切分后前边的帧的FIN字段均为0最后一个帧的FIN为1。当消息没有分段时FIN标志位为1。 RSV1-3(各1bit) 保留位一般情况下为全0。当客户端、服务端协商采用WebSocket扩展时这三个标志位可以非0且值的含义由扩展进行定义。如果出现非0值但并未采用WebSocket扩展连接会出错。 Opcode(4bit) 主要用于指定帧类型可以指定的帧类型有以下几种
%x0表示一个延续帧。当Opcode为0时表示本次数据传输采用了数据分片当前收到的数据帧为其中一个数据分片。%x1表示这是一个文本帧。%x2表示这是一个二进制帧。%x3-7保留的操作代码用于后续定义的非控制帧。%x8表示连接断开。%x9表示这是一个ping操作。%xA表示这是一个pong操作。%xB-F保留的操作代码用于后续定义的控制帧。
注意尽管帧类型有很多但我们经常用的主要是文本帧与二进制帧 Payload数据 实际的有效数据载荷部分。如果通信双方约定使用了WebSocket扩展则扩展数据也存放于此并声明扩展长度。如果没有约定使用则扩展数据为0字节。 Payload长度 Payload长度记录的是Payload数据的长度单位字节
在协议格式中有四个Payload长度它们对应着三种不同的场景
7bitsPayload长度若126那么该Payload长度表示的就是有效载荷的长度(0-126字节)7bitsPayload长度若126那么后两个字节(16bitsPayload长度)表示的就是有效载荷的长度(0-65535字节)7bitsPayload长度若126那么后八个字节(163216bitsPayload长度)表示的就是有效载荷的长度(0-2^64-1字节) Mask(1bit)与Mask-Key(可选) Mask表示Payload数据是否被编码若为1则必有Mask-Key⽤于解码Payload数据。仅客户端发送给服务端的消息需要设置。
若Mask标志位为1那么Mask-Key(4bits)一定被设置若Mask标志位为0那么Mask-Key未被设置
Mask-Key
当Mask为1时存在长度为4字节解码规则DECODED[i] ENCODED[i] ^ MASK[i % 4] websocketpp库介绍
WebSocketpp是⼀个跨平台的开源BSD许可证头部专⽤C库它实现了RFC6455WebSocket 协议和RFC7692WebSocketCompression Extensions。它允许将WebSocket客户端和服务器功能集成到C程序中。在最常见的配置中全功能⽹络I/O由Asio⽹络库提供。
如下为它的基本定义混个眼熟就好~后续用了自然就理解了
webscoketpp库 endpoint endpoint中提供了一些供我们使用的方法并且endpoint就是服务器和客户端建立连接时的一个端点。endpoint屏蔽了底层网络通信的细节依赖于boost库中的Asio对底层网络通信的具体实现
具体来说endpoint类提供了如下类型的接口
日志相关接口回调函数相关接口通信连接相关接口其他服务器搭建的接口 日志相关接口 设置日志输出等级
void set_access_channels(log::level channels); /*设置⽇志打印等级*/ 输出等级分为如下 注意websocketpp日志输出较为繁杂后续我们直接设置为none表示不输出日志即可
其他日志接口由于不使用不再过多介绍 回调函数相关接口 websocketpp的回调思想针对特定的事件可以进行设置它的处理函数指针。
websocketpp搭建了服务器之后给不同的事件设置了不同的处理函数指针这些指针可以指向指定的函数当服务器收到了指定的数据触发了指定的事件后就会通过函数指针去调用这些函数这时候我们程序员就可以编写一些业务处理函数将其设置为对应事件的业务处理函数
例如五子棋游戏中当一名用户想进入到某个游戏房间时该用户会向服务器发送websocket连接请求websocket握手连接建立成功该用户进入了房间的消息应该转发给房间内的所有成员对于这种情况我们修改握手成功的回调即可
websocketpp提供了以下事件的回调
set_open_handler设置websocket协议握手成功的回调函数set_close_handler设置websocket连接断开的回调函数set_message_handler设置websocket消息处理函数set_http_handler设置http请求的处理函数 通信连接相关接口 send给客户端发送消息
close关闭连接
get_con_from_hdl通过connection_hdl获取对应的connection_ptr
connection_hdl是一个用于引用和操作WebSocket连接的句柄而不是连接实例本身。它是WebSocket库提供的一种机制允许用户在不直接访问连接实例的情况下与连接进行交互。connection_ptr具体的连接对象是一个智能指针类型当连接被关闭时会自动释放该连接。除此之外也能通过该类型直接访问连接的执行方法 其他服务器搭建接口 init_asio初始化asio框架websocketpp网络通信底层依赖的就是这个框架
set_reuse_addr设置是否启动地址重用
listen设置绑定监听套接字
run启动服务器
set_timer设置定时任务 server
server继承自endpoint而它自己的接口仅有一个start_accept
start_accept初始化并启动服务端监听连接的accept事件处理 connection
connection是连接管理类它是对asio中的底层连接进行再封装 以上就是websocketpp库中的基本介绍 websocketpp库搭建服务器流程
使用websocketpp库搭建一个服务器最主要的逻辑如下
实例化server对象设置日志输出等级初始化asio框架中的调度器设置业务处理回调函数具体业务处理的函数由我们自己实现设置服务器监听端口开始获取新建连接启动服务器
基本框架实现 搭建服务器的前置准备 首先是包含websocketpp服务器对应的头文件和asio框架的头文件
#include websocketpp/config/asio_no_tls.hpp //asio框架头文件
#include websocketpp/server.hpp //
注意asio框架头文件我们采用asio_no_tls.hpp不采用asio.hpp。两者的区别是asio_no_tls.hpp不支持TLS功能TLS是一种用于在两个通信应用程序之间提供保密性和数据完整性的协议。 使用websocketpp中的server实例化对象时需要传入一个底层网络通信模板参数。我们采用的时asio作为模板参数传入同时为了使得代码简短可以对它进行typedef
typedef websocketpp::serverwebsocketpp::config::asio wsserver_t; 实例化server对象 //1、实例化server对象
wsserver_t svr; 设置日志输出等级 由于websocketpp自带的日志输出内容非常多不便于观察我们不使用它的日志把日志输出等级设置为none即可
//2、设置日志输出等级
svr.set_access_channels(websocketpp::log::alevel::none); 初始化asio框架中的调度器 //3、初始化asio框架
svr.init_asio(); 设置业务处理回调函数 业务处理回调函数一共有4个
set_open_handler设置websocket协议握手成功的回调函数set_close_handler设置websocket连接断开的回调函数set_message_handler设置websocket消息处理函数set_http_handler设置http请求的处理函数
这四个函数的函数原型如下
typedef lib::functionvoid(connection_hdl) open_handler;
typedef lib::functionvoid(connection_hdl) close_handler;
typedef lib::functionvoid(connection_hdl) http_handler;
typedef lib::functionvoid(connection_hdl, message_ptr) message_handler;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请求回调处理函数*/
为了后续操作方便我们回调函数的参数中传入一个server对象使用bind把这个server对象绑定回调函数生成一个新的可调用对象传入给这四个函数的参数即可
void http_callback(wsserver_t* svr,websocketpp::connection_hdl)
{}
void open_callback(wsserver_t* svr,websocketpp::connection_hdl)
{}
void close_callback(wsserver_t* svr,websocketpp::connection_hdl)
{}
void message_callback(wsserver_t* svr,websocketpp::connection_hdl hdl,wsserver_t::message_ptr msg)
{}//4、设置业务处理回调函数
svr.set_close_handler(std::bind(close_callback,svr,std::placeholders::_1));
svr.set_open_handler(std::bind(open_callback,svr,std::placeholders::_1));
svr.set_http_handler(std::bind(http_callback,svr,std::placeholders::_1));
svr.set_message_handler(std::bind(message_callback,svr,std::placeholders::_1,std::placeholders::_2)); 设置服务器监听端口 //5、设置服务器监听端口
svr.listen(8888); 开始获取新建连接与启动服务器 //6、开始获取新建连接
svr.start_accept();
svr.run(); 业务处理回调函数的实现
通过上述的几个步骤我们的服务器的框架已经被搭建好了接下来处理4个回调方法中的实现即可 http_callback 对于http_callback我们要实现的是给客户端返回一个Hello World的页面
大体上一共分为两步
处理来自客户端的http请求构建并发送http应答给客户端 1、处理来自客户端的http请求 http请求中最为关键的几个要素请求方法、请求正文、uri
接下来处理http请求就是我们把这几个关键要素获取下来并打印
我们首先获取http请求中的body在websocketpp库中connection类提供了获取body的这个方法
接下来的问题是如何获取connection类对象呢
实际上websocketpp库中endpoint类提供了一个方法get_con_from_hdl即通过一个connection_hdl对象获取一个connection_ptr对象connection_ptr指向的内容就是connection对象
在websocketpp库中http命名空间下的parser命名空间下的request类提供了获取请求方法与uri的方法
接下来的问题是如何获取request类对象
实际上connection对象中提供了get_request方法用于获取一个request对象 2、构建并发送http应答给客户端 构建http应答一共经历如下几个步骤
构建应答正文(Hello World 页面)设置应答正文添加Content-Type为text/html设置状态码为ok
其中我们可以使用string类型构建应答正文 。剩余的方法connection类中都提供了 代码 void http_callback(wsserver_t* svr,websocketpp::connection_hdl hdl)
{//1、处理http请求wsserver_t::connection_ptr conn svr-get_con_from_hdl(hdl);std::cout body: conn-get_request_body() std::endl;websocketpp::http::parser::request req conn-get_request();std::cout method: req.get_method() std::endl;std::cout uri: req.get_uri() std::endl;//2、构建并发送http应答给客户端std::string body htmlbodyh1Hello World/h1/body/html;conn-set_body(body);conn-append_header(Content-Type,text/html);conn-set_status(websocketpp::http::status_code::ok);
} open_callback
对于该回调无其他特殊需求直接打印一行用于观察即可
void open_callback(wsserver_t* svr,websocketpp::connection_hdl)
{std::cout websocket握手成功 std::endl;
} close_callback
与open_callback同理
void close_callback(wsserver_t* svr,websocketpp::connection_hdl)
{std::cout 连接关闭 std::endl;
} message_callback
message_callback被回调时一定是服务器收到了来自客户端的websocket格式的消息。也就是message_callback的msg参数
为方便测试我们实现的是服务器把客户端发来的消息原封不动返回
主要完成两个工作
构建回复消息发送消息
构建回复消息我们可以采用message类中的get_payload接口
发送消息我们使用connection对象中的send接口
send接口需要传入一个字符串与帧格式
帧格式在websocketpp::frame::opcode中我们采用的是为text(文本)帧缺省参数也为文本帧
void message_callback(wsserver_t* svr,websocketpp::connection_hdl hdl,wsserver_t::message_ptr msg)
{wsserver_t::connection_ptr conn svr-get_con_from_hdl(hdl);std::cout client say: msg-get_payload() std::endl;std::string rep server say: msg-get_payload();conn-send(rep);}