C++ - 使用Websocket++编写客户端连接WebSocket服务器并进行通信

文章目录

  • 1 库依赖
  • 2 根据官方示例代码修改封装的WebsocketClient类
    • 2.1 WebsocketClient代码
    • 2.2 WebsocketClient类使用代码
  • 3 Websocket++官方编写客户端示例教程
  • 4 与Websocket++官方示例客户端的不同

 

1 库依赖

Websocket++/Websocketpp依赖于boost(使用boost 1.74),Websocket++ 0.8.2版本,因为暂时没有使用wss,所以没有集成Openssl。

2 根据官方示例代码修改封装的WebsocketClient类

2.1 WebsocketClient代码

  • WebsocketClient.h
#ifndef WEBSOCKET_CLIENT_H
#define WEBSOCKET_CLIENT_H

// 不包含TLS Client
#include 
#include 

// 包含TLS Client
// #include 
// #include 

#include 
#include 


#include 
#include 
#include 

typedef websocketpp::client client;

static std::wstring string_to_wstring(const std::string &s)
{
	using default_convert = std::codecvt;
	static std::wstring_convertconv(new default_convert("CHS"));
	return conv.from_bytes(s);
}
static std::string wstring_to_string(const std::wstring &s)
{
	using default_convert = std::codecvt;
	static std::wstring_convertconv(new default_convert("CHS"));
	return conv.to_bytes(s);
}
static std::string ansi_to_utf8(const std::string &s)
{
	static std::wstring_convert > conv;
	return conv.to_bytes(string_to_wstring(s));
}
static std::string utf8_to_ansi(const std::string& s)
{
	static std::wstring_convert > conv;
	return wstring_to_string(conv.from_bytes(s));
}


// 保存一个连接的metadata
class connection_metadata {
public:
	typedef websocketpp::lib::shared_ptr ptr;

	connection_metadata(websocketpp::connection_hdl hdl, std::string url)
		: m_Hdl(hdl)
		, m_Status("Connecting")
		, m_Url(url)
		, m_Server("N/A")
	{}

	void on_open(client * c, websocketpp::connection_hdl hdl) 
	{
		m_Status = "Open";

		client::connection_ptr con = c->get_con_from_hdl(hdl);
		m_Server = con->get_response_header("Server");
	}

	void on_fail(client * c, websocketpp::connection_hdl hdl)
	{
		m_Status = "Failed";

		client::connection_ptr con = c->get_con_from_hdl(hdl);
		m_Server = con->get_response_header("Server");
		m_Error_reason = con->get_ec().message();
	}

	void on_close(client * c, websocketpp::connection_hdl hdl)
	{
		m_Status = "Closed";
		client::connection_ptr con = c->get_con_from_hdl(hdl);
		std::stringstream s;
		s << "close code: " << con->get_remote_close_code() << " ("
			<< websocketpp::close::status::get_string(con->get_remote_close_code())
			<< "), close reason: " << con->get_remote_close_reason();
		m_Error_reason = s.str();
	}

	void on_message(websocketpp::connection_hdl, client::message_ptr msg)
	{
		if (msg->get_opcode() == websocketpp::frame::opcode::text) 
		{
			std::string message = utf8_to_ansi(msg->get_payload());
			std::cout << "收到来自服务器的消息:" << message << std::endl;
		}
		else 
		{
			std::string message = websocketpp::utility::to_hex(msg->get_payload());
		}
	}

	websocketpp::connection_hdl get_hdl() const
	{
		return m_Hdl;
	}

	std::string get_status() const
	{
		return m_Status;
	}
private:
	websocketpp::connection_hdl m_Hdl;  // websocketpp表示连接的编号
	std::string m_Status;               // 连接自动状态
	std::string m_Url;                  // 连接的URI
	std::string m_Server;               // 服务器信息
	std::string m_Error_reason;         // 错误原因
};


class WebsocketClient
{
public:
	WebsocketClient();
	virtual~WebsocketClient();

public:
	bool Connect(std::string const & url);
	bool Close(std::string reason = "");
	bool Send(std::string message);

	connection_metadata::ptr GetConnectionMetadataPtr();

private:
	connection_metadata::ptr m_ConnectionMetadataPtr;
	client m_WebsocketClient;
	websocketpp::lib::shared_ptr m_Thread; // 线程
};


#endif // !WEBSOCKET_ENDPOINT_H

  • WebsocketClient.cpp
#include "WebsocketClient.h"

WebsocketClient::WebsocketClient()
{
	m_WebsocketClient.clear_access_channels(websocketpp::log::alevel::all);  // 开启全部接入日志级别
	m_WebsocketClient.clear_error_channels(websocketpp::log::elevel::all);   // 开启全部错误日志级别

	m_WebsocketClient.init_asio();       // 初始化asio
	m_WebsocketClient.start_perpetual(); // 避免请求为空时退出,实际上,也是避免asio退出

	// 独立运行client::run()的线程,主要是避免阻塞
	m_Thread = websocketpp::lib::make_shared(&client::run, &m_WebsocketClient);
}

WebsocketClient::~WebsocketClient()
{
	m_WebsocketClient.stop_perpetual();

	if (m_ConnectionMetadataPtr != nullptr && m_ConnectionMetadataPtr->get_status() == "Open")
	{
		websocketpp::lib::error_code ec;
		m_WebsocketClient.close(m_ConnectionMetadataPtr->get_hdl(), websocketpp::close::status::going_away, "", ec); // 关闭连接

		if (ec) {
			std::cout << "> Error initiating close: " << ec.message() << std::endl;
		}
	}

	m_Thread->join();
}

bool WebsocketClient::Connect(std::string const & url)
{
	websocketpp::lib::error_code ec;

	// 创建connect的共享指针,注意,此时创建并没有实际建立
	client::connection_ptr con = m_WebsocketClient.get_connection(url, ec);  

	if (ec) {
		std::cout << "> Connect initialization error: " << ec.message() << std::endl;
		return false;
	}

	// 创建连接的metadata信息,并保存
	connection_metadata::ptr metadata_ptr = websocketpp::lib::make_shared(con->get_handle(), url);
	m_ConnectionMetadataPtr = metadata_ptr;

	// 注册连接打开的Handler
	con->set_open_handler(websocketpp::lib::bind(
		&connection_metadata::on_open,
		metadata_ptr,
		&m_WebsocketClient,
		websocketpp::lib::placeholders::_1
	));

	// 注册连接失败的Handler
	con->set_fail_handler(websocketpp::lib::bind(
		&connection_metadata::on_fail,
		metadata_ptr,
		&m_WebsocketClient,
		websocketpp::lib::placeholders::_1
	));

	// 注册连接关闭的Handler
	con->set_close_handler(websocketpp::lib::bind(
		&connection_metadata::on_close,
		metadata_ptr,
		&m_WebsocketClient,
		websocketpp::lib::placeholders::_1
	));

	// 注册连接接收消息的Handler
	con->set_message_handler(websocketpp::lib::bind(
		&connection_metadata::on_message,
		metadata_ptr,
		websocketpp::lib::placeholders::_1,
		websocketpp::lib::placeholders::_2
	));

	// 进行连接
	m_WebsocketClient.connect(con);

	std::cout << "Websocket连接成功" << std::endl;

	return true;
}

bool WebsocketClient::Close(std::string reason)
{
	websocketpp::lib::error_code ec;

	if (m_ConnectionMetadataPtr != nullptr)
	{
		int close_code = websocketpp::close::status::normal;
		// 关闭连接
		m_WebsocketClient.close(m_ConnectionMetadataPtr->get_hdl(), close_code, reason, ec);
		if (ec) {
			std::cout << "> Error initiating close: " << ec.message() << std::endl;
			return false;
		}
		std::cout << "关闭Websocket连接成功" << std::endl;
	}

	return true;
}

bool WebsocketClient::Send(std::string message)
{
	websocketpp::lib::error_code ec;

	if (m_ConnectionMetadataPtr != nullptr)
	{
		// 连接发送数据
		m_WebsocketClient.send(m_ConnectionMetadataPtr->get_hdl(), ansi_to_utf8(message), websocketpp::frame::opcode::text, ec);
		if (ec) {
			std::cout << "> Error sending message: " << ec.message() << std::endl;
			return false;
		}
		std::cout << "发送数据成功" << std::endl;
	}

	return true;
}

connection_metadata::ptr WebsocketClient::GetConnectionMetadataPtr()
{
	return m_ConnectionMetadataPtr;
}

2.2 WebsocketClient类使用代码

#include "WebsocketClient.h"


int main() {
	bool done = false;
	std::string input;
	WebsocketClient endpoint;

	while (!done) {
		std::cout << "Enter Command: ";
		std::getline(std::cin, input);

		if (input == "quit") {
			done = true;
		}
		else if (input == "help") {
			std::cout
				<< "\nCommand List:\n"
				<< "connect \n"
				<< "send \n"
				<< "close [] []\n"
				<< "help: Display this help text\n"
				<< "quit: Exit the program\n"
				<< std::endl;
		}
		else if (input.substr(0, 7) == "connect") {
			endpoint.Connect(input.substr(8));
		}
		else if (input.substr(0, 4) == "send") {
			std::stringstream ss(input);

			std::string cmd;
			int id;
			std::string message;

			ss >> cmd;
			std::getline(ss, message);

			endpoint.Send(message);
		}
		else if (input.substr(0, 5) == "close") {
			std::stringstream ss(input);

			std::string cmd;
			std::string reason;

			ss >> cmd ;
			std::getline(ss, reason);

			endpoint.Close(reason);
		}
		else if (input.substr(0, 4) == "quit") {
			done = true;
		}
		else {
			std::cout << "> Unrecognized Command" << std::endl;
		}
	}

	return 0;
}

3 Websocket++官方编写客户端示例教程

可参考链接:https://docs.websocketpp.org/md_tutorials_utility_client_utility_client.html
也可在Websocket++的文件夹\tutorials\utility_client找到该示例!
或者在Websocket++的文件夹\example\scratch_client找到该示例!

4 与Websocket++官方示例客户端的不同

  • 增加ansi与utf-8编码转换,中文字符不再乱码
  • 不适用官方使用id管理多个链接方式,这里认为的客户端只有一个长连接,而WebsocketClient只维护一个长连接即可

2020年9月16日更新:修改WebsocketClient类

修改信息

  • 将open,close,message等回调函数移动到WebsocketClient内部
  • 新增设置回调函数的公共接口

WebsocketClient.h

#ifndef WEBSOCKET_CLIENT_H
#define WEBSOCKET_CLIENT_H

// 不包含TLS Client
#include 
#include 

// 包含TLS Client
// #include 
// #include 

#include 
#include 


#include 
#include 
#include 

typedef websocketpp::client client;

static std::wstring string_to_wstring(const std::string &s)
{
	using default_convert = std::codecvt;
	static std::wstring_convertconv(new default_convert("CHS"));
	return conv.from_bytes(s);
}
static std::string wstring_to_string(const std::wstring &s)
{
	using default_convert = std::codecvt;
	static std::wstring_convertconv(new default_convert("CHS"));
	return conv.to_bytes(s);
}
static std::string ansi_to_utf8(const std::string &s)
{
	static std::wstring_convert > conv;
	return conv.to_bytes(string_to_wstring(s));
}
static std::string utf8_to_ansi(const std::string& s)
{
	static std::wstring_convert > conv;
	return wstring_to_string(conv.from_bytes(s));
}

typedef std::function OnOpenFunc;
typedef std::function OnFailFunc;
typedef std::function OnCloseFunc;
typedef std::function OnMessageFunc;

class WebsocketClient 
{
public:
	WebsocketClient();
	virtual~WebsocketClient();

public:
	bool Connect(std::string const & url);
	bool Close(std::string reason = "");
	bool Send(std::string message);

	connection_metadata::ptr GetConnectionMetadataPtr();

	void OnOpen(client * c, websocketpp::connection_hdl hdl);
	void OnFail(client * c, websocketpp::connection_hdl hdl);
	void OnClose(client * c, websocketpp::connection_hdl hdl);
	void OnMessage(websocketpp::connection_hdl, client::message_ptr msg);


	void SetOnOpenFunc(OnOpenFunc func);
	void SetOnFailFunc(OnFailFunc func);
	void SetOnCloseFunc(OnCloseFunc func);
	void SetMessageFunc(OnMessageFunc func);
private:
	connection_metadata::ptr m_ConnectionMetadataPtr;
	client m_WebsocketClient;
	websocketpp::lib::shared_ptr m_Thread; // 线程

	OnOpenFunc m_OnOpenFunc;
	OnFailFunc m_OnFailFunc;
	OnCloseFunc m_OnCloseFunc;
	OnMessageFunc m_MessageFunc;
};
#endif // !WEBSOCKET_ENDPOINT_H

WebsocketClient.cpp

#include "WebsocketClient.h"

WebsocketClient::WebsocketClient()
{
	m_WebsocketClient.clear_access_channels(websocketpp::log::alevel::all);  // 开启全部接入日志级别
	m_WebsocketClient.clear_error_channels(websocketpp::log::elevel::all);   // 开启全部错误日志级别

	m_WebsocketClient.init_asio();       // 初始化asio
	m_WebsocketClient.start_perpetual(); // 避免请求为空时退出,实际上,也是避免asio退出

	// 独立运行client::run()的线程,主要是避免阻塞
	m_Thread = websocketpp::lib::make_shared(&client::run, &m_WebsocketClient);

	m_OnOpenFunc = nullptr;
	m_OnFailFunc = nullptr;
	m_OnCloseFunc = nullptr;
	m_MessageFunc = nullptr;
}

WebsocketClient::~WebsocketClient()
{
	m_WebsocketClient.stop_perpetual();

	if (m_ConnectionMetadataPtr != nullptr && m_ConnectionMetadataPtr->get_status() == "Open")
	{
		websocketpp::lib::error_code ec;
		m_WebsocketClient.close(m_ConnectionMetadataPtr->get_hdl(), websocketpp::close::status::going_away, "", ec); // 关闭连接

		if (ec) {
			std::cout << "> Error initiating close: " << ec.message() << std::endl;
		}
	}

	m_Thread->join();
}

bool WebsocketClient::Connect(std::string const & url)
{
	websocketpp::lib::error_code ec;

	// 创建connect的共享指针,注意,此时创建并没有实际建立
	client::connection_ptr con = m_WebsocketClient.get_connection(url, ec);  

	if (ec) {
		std::cout << "> Connect initialization error: " << ec.message() << std::endl;
		return false;
	}

	// 创建连接的metadata信息,并保存
	connection_metadata::ptr metadata_ptr = websocketpp::lib::make_shared(con->get_handle(), url);
	m_ConnectionMetadataPtr = metadata_ptr;

	// 注册连接打开的Handler
	//con->set_open_handler(websocketpp::lib::bind(
	//	&connection_metadata::on_open,
	//	metadata_ptr,
	//	&m_WebsocketClient,
	//	websocketpp::lib::placeholders::_1
	//));
	con->set_open_handler(websocketpp::lib::bind(
		&WebsocketClient::OnOpen,
		this,
		&m_WebsocketClient,
		websocketpp::lib::placeholders::_1
	));

	// 注册连接失败的Handler
	//con->set_fail_handler(websocketpp::lib::bind(
	//	&connection_metadata::on_fail,
	//	metadata_ptr,
	//	&m_WebsocketClient,
	//	websocketpp::lib::placeholders::_1
	//));
	con->set_fail_handler(websocketpp::lib::bind(
		&WebsocketClient::OnFail,
		this,
		&m_WebsocketClient,
		websocketpp::lib::placeholders::_1
	));

	// 注册连接关闭的Handler
	//con->set_close_handler(websocketpp::lib::bind(
	//	&connection_metadata::on_close,
	//	metadata_ptr,
	//	&m_WebsocketClient,
	//	websocketpp::lib::placeholders::_1
	//));
	con->set_close_handler(websocketpp::lib::bind(
		&WebsocketClient::OnClose,
		this,
		&m_WebsocketClient,
		websocketpp::lib::placeholders::_1
	));

	// 注册连接接收消息的Handler
	//con->set_message_handler(websocketpp::lib::bind(
	//	&connection_metadata::on_message,
	//	metadata_ptr,
	//	websocketpp::lib::placeholders::_1,
	//	websocketpp::lib::placeholders::_2
	//));
	con->set_message_handler(websocketpp::lib::bind(
		&WebsocketClient::OnMessage,
		this,
		websocketpp::lib::placeholders::_1,
		websocketpp::lib::placeholders::_2
	));

	// 进行连接
	m_WebsocketClient.connect(con);

	std::cout << "Websocket连接成功" << std::endl;

	// 注意,不能在Websocket连接完成之后马上就发送消息,不然会出现Invalid State的错误,
	// 导致消息发送不成功,所以在连接成功之后,主线程休眠1秒
	std::this_thread::sleep_for(std::chrono::milliseconds(1000));

	return true;
}

bool WebsocketClient::Close(std::string reason)
{
	websocketpp::lib::error_code ec;

	if (m_ConnectionMetadataPtr != nullptr)
	{
		int close_code = websocketpp::close::status::normal;
		// 关闭连接
		m_WebsocketClient.close(m_ConnectionMetadataPtr->get_hdl(), close_code, reason, ec);
		if (ec) {
			std::cout << "> Error initiating close: " << ec.message() << std::endl;
			return false;
		}
		std::cout << "关闭Websocket连接成功" << std::endl;
	}

	return true;
}

bool WebsocketClient::Send(std::string message)
{
	websocketpp::lib::error_code ec;

	if (m_ConnectionMetadataPtr != nullptr)
	{
		// 连接发送数据
		m_WebsocketClient.send(m_ConnectionMetadataPtr->get_hdl(),ansi_to_utf8(message), websocketpp::frame::opcode::text, ec);
		if (ec) 
		{
			std::cout << "> Error sending message: " << ec.message() << std::endl;
			std::string errorMessage = ec.message();
			return false;
		}
		std::cout << "发送数据成功" << std::endl;
	}

	return true;
}

connection_metadata::ptr WebsocketClient::GetConnectionMetadataPtr()
{
	return m_ConnectionMetadataPtr;
}

void WebsocketClient::OnOpen(client * c, websocketpp::connection_hdl hdl)
{
	if (m_OnOpenFunc != nullptr)
	{
		m_OnOpenFunc();
	}
}

void WebsocketClient::OnFail(client * c, websocketpp::connection_hdl hdl)
{
	if (m_OnFailFunc != nullptr)
	{
		m_OnFailFunc();
	}
}

void WebsocketClient::OnClose(client * c, websocketpp::connection_hdl hdl)
{
	if (m_OnCloseFunc != nullptr)
	{
		m_OnCloseFunc();
	}
}

void WebsocketClient::OnMessage(websocketpp::connection_hdl, client::message_ptr msg)
{
	if (msg->get_opcode() == websocketpp::frame::opcode::text)
	{
		std::string message = utf8_to_ansi(msg->get_payload());
		//std::cout << "收到来自服务器的消息:" << message << std::endl;

		if (m_MessageFunc != nullptr)
		{
			m_MessageFunc(message);
		}	
	}
	else
	{
		std::string message = websocketpp::utility::to_hex(msg->get_payload());
	}
}

void WebsocketClient::SetOnOpenFunc(OnOpenFunc func)
{
	m_OnOpenFunc = func;
}

void WebsocketClient::SetOnFailFunc(OnFailFunc func)
{
	m_OnFailFunc = func;
}

void WebsocketClient::SetOnCloseFunc(OnCloseFunc func)
{
	m_OnCloseFunc = func;
}

void WebsocketClient::SetMessageFunc(OnMessageFunc func)
{
	m_MessageFunc = func;
}

另外在应用该类的时候发现了一个Websocket++的一个问题: 不能在Websocket连接完成之后马上就发送消息,不然会出现Invalid State的错误,导致消息发送不成功,所以在连接成功之后,主线程休眠1秒,这在代码中也有体现。

你可能感兴趣的:(初学者学习SQL,SERVER,TCP/IOCP/Socket)