Websocketpp的使用

Websocketpp的使用


websocketpp库是依赖于boost库与openssl库的(如果不需要加密算法可以不使用openssl库) websocket实现,功能相对完善

为什么使用websocket


websocket基于tcp实现,较之http是全双工的,服务器可以主动向客户端推送数据,也可以通过请求获得。

Boost库系列:asio


因为websocketpp依赖于boost的asio,所以简单介绍一下

asio的使用条件:

  1. 核心类io_service,asio的任何操作都需要io_service的参与

    同步模式下,程序流水执行,需要io时等待io返回再执行下一步。

    异步模式下,由io_service提交io异步执行,程序不需要等待io返回直接执行下一条语句。异步执行中由io_service的run()函数负责检测io返回并通知预先注册的回调句柄(可理解为回调函数),由回调句柄执行io返回的后续动作。

  2. handler回调句柄,异步模式下io_service::run()函数负责检查IO返回并触发句柄函数的调用

  3. io_service::strand类。io_service::run()在多个线程中被调用时,怎么保证handler回调句柄函数正确执行?这时strand类就派上用场了。通常用成员函数wrap()函数对一个handler函数进行包装,保证线程安全地在strand中执行。

  4. io_service::work类,主要目的是阻止io_service::run()函数的返回,不使用work的情况下,io_service::run()函数检测(并提交相应的回调句柄执行)完所有已注册的IO事件后,run()本身函数也执行完毕并返回。使用work之后,run()函数不返回而继续等待将来可能发生的异步事件

  5. 错误处理,有两种方式。一是在异步操作函数中提供error_code&的输出参数,调用后检查该参数是否发生了错误。另一种方式是try-catch块捕获抛出的错误

  6. 跟踪日志。异步执行流程正常很跟踪。asio提供了handler的跟踪机制。在头文件<boost/asio.hpp>前定义宏BOOST_ASIO_ENABLE_HANDLER_TRACKING,它将向标准流cerr输出日志。可以使用操作系统的重定向写入到文件

websocketpp使用流程


要开发程序时,基本上要include两种文件,一种是用来做组态设置的(config)的,一种是用来决定开发程序的角色类型(Role)的。

Role主要分成Server(用来做开发端)和Client(用来开发客户端)两种

  1. 如果建立Server端程序的话,要#include<websocketpp/server.hpp>然后建立websocketpp::server<>的物件,拿来做操作
  2. 如果建立Client端程序的话,要#include<websocketpp/client.hpp>然后建立websocketpp::client<>的物件做连线

但是WebSocket++的server和client这两种类别,都是模板的class,在建立是需要指定使用的config才可以。

Config主要提供三种类型:

  1. config::core:提供有限功能的设置,只使用C++11.
  2. config::asio:使用BoostASIO做基础来提供完整的功能
  3. 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>

端点配置

  1. 生成一个Server对象

  2. 设置日志级别:

    set_access_channels(websocketpp::log::alevel::all);

  3. 屏蔽某个级别的日志:

    clear_access_channels(websocketpp::log::alevel::frame_payload);

  4. 调用init_asio()这个函数,初始化内部的Boost ASIO的io_service,作为后续网络连线等功能之使用:

    init_asio();

  5. 设置收到消息时的回调函数:

    set_message_handler(bind(&on_message, &myserver, ::_1, ::_2));

    myserver.set_http_handler(bind(&on_http, &myserver, ::_1));

  6. 设置是否开启SO_REUSEADDR TCP套接字选项:

    set_reuse_addr(true);

  7. 设置关闭时回调函数:

    myserver.set_close_handler(bind(&on_close, &myserver, ::_1));

  8. 设置打开连接时的回调函数

    myserver.set_open_handler(bind(&on_open, &myserver, ::_1));

  9. 调用listen函数指定监听端口

    listen(9002);

  10. 调用start_accept()开始接收输入

    start_accept();

  11. 调用run(),进入 Server的主循环,直到Server 被停下来

  12. 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,会收到两个数据:

  1. websocketpp::connection_hdl型别的数据,是用来识别目前的连线用的;如果之后要传送信息给client的话,就必须要通过这个物件,来设置要把信息传送给谁
  2. websocketpp::server<>::message_ptr,里面储存的是传递进来的信息。一般来说,这会通过他的get_payload()函数,来取得传递进来的信息,而得到的数据,会是const string&。
websocket传输数据

基本上就是调用server<>的send()这个函数就可以了。

s->send(hdl, msg->get_payload(), msg->get_opcode());

  1. websocketpp::connection_hdl,让Server知道是要传给哪个client
  2. 要传递的数据
  3. opcode,来指定要传递的数据的形式
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值