Websocketpp的使用
websocketpp库是依赖于boost库与openssl库的(如果不需要加密算法可以不使用openssl库) websocket实现,功能相对完善
为什么使用websocket
websocket基于tcp实现,较之http是全双工的,服务器可以主动向客户端推送数据,也可以通过请求获得。
Boost库系列:asio
因为websocketpp依赖于boost的asio,所以简单介绍一下
asio的使用条件:
-
核心类io_service,asio的任何操作都需要io_service的参与
同步模式下,程序流水执行,需要io时等待io返回再执行下一步。
异步模式下,由io_service提交io异步执行,程序不需要等待io返回直接执行下一条语句。异步执行中由io_service的run()函数负责检测io返回并通知预先注册的回调句柄(可理解为回调函数),由回调句柄执行io返回的后续动作。
-
handler回调句柄,异步模式下io_service::run()函数负责检查IO返回并触发句柄函数的调用
-
io_service::strand类。io_service::run()在多个线程中被调用时,怎么保证handler回调句柄函数正确执行?这时strand类就派上用场了。通常用成员函数wrap()函数对一个handler函数进行包装,保证线程安全地在strand中执行。
-
io_service::work类,主要目的是阻止io_service::run()函数的返回,不使用work的情况下,io_service::run()函数检测(并提交相应的回调句柄执行)完所有已注册的IO事件后,run()本身函数也执行完毕并返回。使用work之后,run()函数不返回而继续等待将来可能发生的异步事件
-
错误处理,有两种方式。一是在异步操作函数中提供error_code&的输出参数,调用后检查该参数是否发生了错误。另一种方式是try-catch块捕获抛出的错误
-
跟踪日志。异步执行流程正常很跟踪。asio提供了handler的跟踪机制。在头文件<boost/asio.hpp>前定义宏BOOST_ASIO_ENABLE_HANDLER_TRACKING,它将向标准流cerr输出日志。可以使用操作系统的重定向写入到文件
websocketpp使用流程
要开发程序时,基本上要include两种文件,一种是用来做组态设置的(config)的,一种是用来决定开发程序的角色类型(Role)的。
Role主要分成Server(用来做开发端)和Client(用来开发客户端)两种
- 如果建立Server端程序的话,要#include<websocketpp/server.hpp>然后建立websocketpp::server<>的物件,拿来做操作
- 如果建立Client端程序的话,要#include<websocketpp/client.hpp>然后建立websocketpp::client<>的物件做连线
但是WebSocket++的server和client这两种类别,都是模板的class,在建立是需要指定使用的config才可以。
Config主要提供三种类型:
- config::core:提供有限功能的设置,只使用C++11.
- config::asio:使用BoostASIO做基础来提供完整的功能
- config::asio_tls:是config::asio再加上TLS的连线加密功能
不同的config需要include websocketpp/config的目录下不同的文件:
- core: core.hpp(Server)和core_client.hpp(Client)
- asio: asio_no_tls.hpp(Server)和asio_no_tls_client.hpp(Client)
- asio_tls:asio.hpp(Server)和asio_client.hpp(Client)
最后,如果要建立使用BoostASIO,没有TSL加密的Server的话,基本要include的头文件就是:
#include <websocketpp/server.hpp>
#include <websocketpp/config/asio_no_tls.hpp>
而在控制用的物件的部分,则就是:
websocketpp::server< websocketpp::config::asio > mServer;
之后,所有的功能就都是针对mServer这个物件进行操作。而在WebSocket++里面,则是把它称为「endpoint」
初始设置和基础知识
步骤1
添加websocket++包并设置服务器endpoint类型
websocket++主要包含两种对象类型:端点和连接
端点创建并启动新连接,并维护这些连接的默认设置,端点还管理任何共享网络资源。
连接存储每个Websocket会话的独特信息。
启动连接后,端点和连接没有联系,端点将所有默认设置复制到新连接中,更改端点设置只会影响将来的连接
Websocket++端点是通过端点角色与端点配置相结合来构建的,有两种不同的端点角色Websocket会话中服务端和客户端角色各一种,服务端使用标头websocketpp::server<websocketpp/server.hpp>
端点配置
-
生成一个Server对象
-
设置日志级别:
set_access_channels(websocketpp::log::alevel::all);
-
屏蔽某个级别的日志:
clear_access_channels(websocketpp::log::alevel::frame_payload);
-
调用init_asio()这个函数,初始化内部的Boost ASIO的io_service,作为后续网络连线等功能之使用:
init_asio();
-
设置收到消息时的回调函数:
set_message_handler(bind(&on_message, &myserver, ::_1, ::_2));
myserver.set_http_handler(bind(&on_http, &myserver, ::_1));
-
设置是否开启SO_REUSEADDR TCP套接字选项:
set_reuse_addr(true);
-
设置关闭时回调函数:
myserver.set_close_handler(bind(&on_close, &myserver, ::_1));
-
设置打开连接时的回调函数
myserver.set_open_handler(bind(&on_open, &myserver, ::_1));
-
调用listen函数指定监听端口
listen(9002);
-
调用start_accept()开始接收输入
start_accept();
-
调用run(),进入 Server的主循环,直到Server 被停下来
-
run();
我们可以看一下服务器的源代码:
#define BOOST_CONFIG_SUPPRESS_OUTDATED_MESSAGE
#include <boost/algorithm/string.hpp>
#include <string>
#include <vector>
#include <list>
#include <iostream>
#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/server.hpp>
//名称和值的数据对
struct NameAndValue
{
std::string strName;
std::string strValue;
};
//字符串分割
int StringSplit(std::vector<std::string>& dst, const std::string& src, const std::string& separator);
std::string& StringTrim(std::string& str);
bool GetReqeustCommandAndRarmeter(std::string strUri, std::string& strRequestOperateCommand, std::list<NameAndValue>& listRequestOperateParameter);
typedef websocketpp::server<websocketpp::config::asio>server;
using websocketpp::lib::placeholders::_1;
using websocketpp::lib::placeholders::_2;
using websocketpp::lib::bind;
typedef server::message_ptr message_ptr;
bool validate(server*, websocketpp::connection_hdl) {
return true;
}
void on_http(server* s,websocketpp::connection_hdl hdl) {
server::connection_ptr con = s->get_con_from_hdl(hdl);
std::string res = con->get_request_body();
std::stringstream ss;
ss << "got HTTP request with" << res.size() << "bytes of body data";
con->set_body(ss.str());
con->set_status(websocketpp::http::status_code::ok);
}
void on_fail(server* s, websocketpp::connection_hdl hdl) {
server::connection_ptr con = s->get_con_from_hdl(hdl);
std::cout << "Fail handler: " << con->get_ec() << " " << con->get_ec().message() << std::endl;
}
void on_open(server* s, websocketpp::connection_hdl hdl) {
//申请websocket upgrade成功之后,调用open_handler函数,回调on_open
//在这里,可以获取http请求的地址,参数信息、
std::cout << "open handler" << std::endl;
server::connection_ptr con = s->get_con_from_hdl(hdl);
websocketpp::config::core::request_type requestClient = con->get_request();
std::string strMethod = requestClient.get_method();//请求方法
std::string strUri = requestClient.get_uri();//请求uri地址
std::string strRequestOperateCommand = "";//操作类型
std::list<NameAndValue> listRequestOperateParameter;//操作参数列表
GetReqeustCommandAndRarmeter(strUri,strRequestOperateCommand, listRequestOperateParameter);
std::cout << "command:" << strRequestOperateCommand << std::endl;
}
void on_close(websocketpp::connection_hdl(hdl)) {
std::cout << "Close handler" << std::endl;
}
//Callback to handle incoming messages
void on_message(server* s, websocketpp::connection_hdl hdl, message_ptr msg) {
/*
hdl.lock().get()连接标识
msg->get_payload() 收到消息内容
msg->get_opcode() 收到消息的类型
*/
std::cout << "on_message called with hdl:" << hdl.lock().get()
<< "and message:" << msg->get_payload() << std::endl;
try {
/*
发送消息
s->send(
hdl,//连接
msg->get_payload(),//消息
msg->get_opcode());//消息类型
*/
s->send(hdl, msg->get_payload(), msg->get_opcode());
}
catch (websocketpp::exception const& e) {
std::cout << "Echo failed because:" << "(" << e.what() << ")" << std::endl;
}
}
int main() {
server print_server;
try
{
//set logging settings
print_server.set_access_channels(websocketpp::log::alevel::all);
print_server.set_error_channels(websocketpp::log::alevel::all);
//register our message handler
print_server.set_message_handler(bind(&on_message, &print_server, ::_1, ::_2));
print_server.set_http_handler(bind(&on_http, &print_server, ::_1));
print_server.set_fail_handler(bind(&on_fail, &print_server, ::_1));
print_server.set_open_handler(bind(&on_open, &print_server, ::_1));
print_server.set_close_handler(bind(&on_close, ::_1));
print_server.set_validate_handler(bind(&validate, &print_server, ::_1));
//Initialize ASIO
print_server.init_asio();
print_server.set_reuse_addr(true);
//listen on port 9100
print_server.listen(9100);
//Start the server accept loop
print_server.start_accept();
//Start the ASIO io_service run loop
print_server.run();
print_server.stop();
}
catch (websocketpp::exception const & e)
{
std::cout << e.what() << std::endl;
}
catch (const std::exception& e)
{
std::cout << e.what() << std::endl;
}
catch (...)
{
std::cout << "other exception" << std::endl;
}
}
//字符串分割
int StringSplite(std::vector<std::string>& dst, const std::string& src, const std::string& separator)
{
if (src.empty() || separator.empty()) { return 0; }
int nCount = 0;
std::string temp;
size_t pos = 0, offset = 0;
//分割第1~n-1个
while ((pos=src.find_first_of(separator,offset))!=std::string::npos)
{
temp = src.substr(offset, src.length() - offset);
if (temp.length() > 0) {
dst.push_back(temp);
nCount++;
}
offset = pos + 1;
}
//分割第n个
temp = src.substr(offset, src.length() - offset);
if (temp.length() > 0) {
dst.push_back(temp);
nCount++;
}
return nCount;
}
//去前后空格
std::string& StringTrim(std::string& str)
{
if (str.empty()) {
return str;
}
str.erase(0, str.find_first_not_of(" "));
str.erase(str.find_last_not_of(" ") + 1);
return str;
}
//获取请求命令与参数
bool GetReqeustCommandAndParmeter(std::string strUri, std::string& strRequestOperateCommand, std::list<NameAndValue>& listRequestOperateParameter)
{
bool bRet = false;
std::vector<std::string> vecRequest;
int nRetSplit = StringSplit(vecRequest, strUri, "?");
if (nRetSplit > 0)
{
if (vecRequest.size() == 1)
{
strRequestOperateCommand = vecRequest[0];
}
else if (vecRequest.size() > 1)
{
strRequestOperateCommand = vecRequest[0];
std::string strRequestParameter = vecRequest[1];
std::vector<std::string> vecParams;
nRetSplit = StringSplit(vecParams, strRequestParameter, "&");
if (nRetSplit > 0)
{
std::vector<std::string>::iterator iter, iterEnd;
iter = vecParams.begin();
iterEnd = vecParams.end();
for (iter; iter != iterEnd; iter++)
{
std::vector<std::string> vecNameOrValue;
nRetSplit = StringSplit(vecNameOrValue, *iter, "=");
if (nRetSplit > 0)
{
NameAndValue nvNameAndValue;
nvNameAndValue.strName = vecNameOrValue[0];
nvNameAndValue.strValue = "";
if (vecNameOrValue.size() > 1)
{
nvNameAndValue.strValue = vecNameOrValue[1];
}
//insert
listRequestOperateParameter.push_back(nvNameAndValue);
}
}
}
}
else
{
}
}
return bRet;
}
示例是通过websocketpp:: server <websocketpp::config:: asio >这个Endpoint,来建立一个使用Boost ASIO、没有TLS加密的WebSocket Server对象myserver。这个程序在执行myserver.run()后,会持续去监听port 9002,当有信息传递进来的时候,就会触发到on_message()这个函数、并把接到的信息进行相应的处理。
websocketpp对于连接数据处理
WebSocket++是通过提供各种「Handler」(callback function)做事件的处理。
message handler 的callback function,会收到两个数据:
- websocketpp::connection_hdl型别的数据,是用来识别目前的连线用的;如果之后要传送信息给client的话,就必须要通过这个物件,来设置要把信息传送给谁
- websocketpp::server<>::message_ptr,里面储存的是传递进来的信息。一般来说,这会通过他的get_payload()函数,来取得传递进来的信息,而得到的数据,会是const string&。
websocket传输数据
基本上就是调用server<>的send()这个函数就可以了。
s->send(hdl, msg->get_payload(), msg->get_opcode());
- websocketpp::connection_hdl,让Server知道是要传给哪个client
- 要传递的数据
- opcode,来指定要传递的数据的形式