目录
服务器和客户端在进行HTTP通信时,只有客户端能主动向服务端发送消息 ,而服务端无法主动向指定客户端发送消息。即服务端是被动方
当客户端想给客户端2发送消息时,需要由服务端转发,但服务端此时不能主动向客户端2发数据

需要由客户端2主动向服务端询问是否有新数据

这样就代表每个客户端都需要持续轮询 ,成本较高且不能得到及时响应
而WebSocket协议就是为了解决这个问题所诞生,当双方建立了WebSocket连接后,服务端可以主动向客户端发送消息 (准确来说是双方都可以主动发消息)

升级过程
WebSocket协议是基于HTTP升级上来的
双方首先建立HTTP连接
建立完成后,客户端会向服务端发送WebSocket升级报文:
bash
Request Headers # 请求头信息
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8
Cache-Control: no-cache
Connection: Upgrade # 希望升级协议
Host: ***.***.***.***(打码)
Origin: http://127.0.0.1:8020
Pragma: no-cache
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Sec-WebSocket-Key: 8M2YVNTrYpX1yPCdGhgC+g== # 该WebSocket与服务端通讯的"钥匙"
Sec-WebSocket-Version: 13 # 版本
Upgrade: websocket # 升级协议格式
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)
当服务端判断可行时,会向客户端发送响应:
bash
Response Headers # 返回头信息
Connection: upgrade # 升级协议
Date: Thu, 16 Mar 2017 12:10:42 GMT
Sec-WebSocket-Accept: H4JE024axXy53/RgSfHlmSoMhJo= # 服务端与该客户端通讯的"钥匙"
Sec-WebSocket-Extensions: permessage-deflate;client_max_window_bits=15
Server: Apache-Coyote/1.1
Upgrade: websocket # 升级的协议格式
之后双端就会以WebSocket通信
报文格式

FIN:WebSocket以消息为单位,一个消息会被拆为多个报文 ,FIN为1表示末尾帧
RSV:保留字段,只在扩展时使用
opcode:当前数据帧类型
- 0x0 --> 延续帧
- 0x1 --> 文本帧
- 0x2 --> 二进制帧
- 0x3-0x7 --> 保留位
- 0x9 --> ping帧
- 0xa --> pong帧
- 0xb-0xf --> 保留位
mask:表示payload 数据是否被编码,若为1 ,则有Mask-Key ,用于解码Payload数据(仅客户端发送给服务端的消息需要设置。)
7位Payload长度:7位,范围为0~127
- 若值为<126,该值就是Payload长度。
- 若值=126,表示后面的2字节(16位Payload长度)才是Payload长度。
- 若值=127,表示后面的2字节(16位Payload长度)+4字节(32位Payload长度)+2字节(16位Payload长度)总共8字节才是Payload长度
Payload数据:有效载荷
常用接口
-
日志相关接口:set_access_channels ,例如传入websocketpp::log::alevel::none 代表禁止打印所有日志
-
回调函数相关接口:针对不同事件设置不同的处理函数
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
其他服务器搭建的接口:
- init_asio() 初始化asio框架
- set_reuse_addr 是否启动地址重用
- start_accept 开始获取新建链接
- listen 设置绑定监听端口
- run 启动服务器
- set_timer 设置定时任务
实现服务器
使用websocketpp搭建服务器的流程:
- 实例化server对象
- 设置日志输出等级
- 初始化asio框架中的调度器
- 设置业务处理回调函数(具体业务处理的函数由我们自己实现)
- 设置服务器监听端口
- 开始获取新建连接
- 启动服务器
cpp
#include <iostream>
#include <websocketpp/server.hpp>
#include <websocketpp/config/asio.hpp>
typedef websocketpp::server<websocketpp::config::asio> wssvr_t;
using namespace std;
int main()
{
// 实例化server对象
wssvr_t wssvr;
// 设置日志等级为none
wssvr.set_access_channels(websocketpp::log::alevel::none);
// 初始化asio调度器
wssvr.init_asio();
// 设置回调函数
wssvr.set_open_handler([&wssvr](websocketpp::connection_hdl hdl){ });
wssvr.set_close_handler([&wssvr](websocketpp::connection_hdl hdl){ });
wssvr.set_http_handler([&wssvr](websocketpp::connection_hdl hdl){ });
wssvr.set_message_handler([&wssvr](websocketpp::connection_hdl hdl, wssvr_t::message_ptr msg_ptr){ });
// 设置监听端口
wssvr.listen(8080);
// 开始获取新连接
wssvr.start_accept();
// 启动服务器
wssvr.run();
return 0;
}
通过Lambda表达式 ,将4个handler的参数隐式带上wssvr,这样后面要发消息等操作时才可以找到实例化对象server
例如,一个简单的业务,返回一个html:
cpp
// 设置回调函数
wssvr.set_open_handler([&wssvr](websocketpp::connection_hdl hdl)
{ cout << "客户端加入\n"; });
wssvr.set_close_handler([&wssvr](websocketpp::connection_hdl hdl)
{ cout << "客户端退出\n"; });
wssvr.set_http_handler([&wssvr](websocketpp::connection_hdl hdl)
{
// 返回一个简单http界面
// 获取该连接的connection对象
wssvr_t::connection_ptr conn = wssvr.get_con_from_hdl(hdl);
// 该请求的正文也可以直接用conn获取
// cout << "正文:" << conn->get_request_body() << endl;
// 获取该连接的请求对象
websocketpp::http::parser::request req = conn->get_request();
cout << "正文:" << req.get_body() << endl;
cout << "类型:" << req.get_method() << endl;
cout << "路径:" << req.get_uri() << endl;
// 设置响应正文(html)
string body = "<html><body><h1>Hello World.</h1></body></html>";
// 设置正文
conn->set_body(body);
// 添加报头字段
conn->append_header("Content-Type","text/html");
// 设置http状态码
conn->set_status(websocketpp::http::status_code::ok); });
wssvr.set_message_handler([&wssvr](websocketpp::connection_hdl hdl, wssvr_t::message_ptr msg_ptr)
{
// 当有消息过来时,获取该连接的connection_ptr
wssvr_t::connection_ptr conn = wssvr.get_con_from_hdl(hdl);cout << "消息:" << msg_ptr->get_payload() << endl;
// 要返回的内容
string rsp = "client say:" + msg_ptr->get_payload();
// 发送数据,第二个参数为发送数据的类型
conn->send(rsp, websocketpp::frame::opcode::text); });
实现客户端与服务端通信
服务端写好后,用AI随便写一个html客户端(客户端内的地址要改成自己的云服务器),既可以测试通信了

