使用boost封装一个websocketserver类

在boost官网案例的基础上进行的修改,以下具体功能的实现方式应该根据具体应用场景而定

boost版本:1.77
功能:websocket服务器端,能够处理多个客户端,获取其客户端的url路径

Websocket.h

#pragma once

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "WebServer.h"

#define BUFF_SIZE_MAX 10*1024

namespace beast = boost::beast;         // from 
namespace http = beast::http;           // from 
namespace websocket = beast::websocket; // from 
namespace net = boost::asio;            // from 
using tcp = boost::asio::ip::tcp;       // from 

class WebSocketSession;
// 客户端关闭回调
typedef std::function<void(std::shared_ptr<WebSocketSession> ptr)> pCb_Close_;
// 接收数据回调
typedef std::function<void(std::string, std::string)> pRecvData; 

// Echoes back all received WebSocket messages
class WebSocketSession : public std::enable_shared_from_this<WebSocketSession>
{
public:
	// Take ownership of the socket
	explicit WebSocketSession(tcp::socket&& socket, pCb_LogInfo pLog, pRecvData ptr, pCb_Close_ pclose)
		: ws_(std::move(socket)),
		pLog_(pLog),
		pMsg_(ptr),
		pClose_(pclose),
		bConnected_(true)
	{
		memset(&buffer_, 0, BUFF_SIZE_MAX);
	}

	std::string GetUrlPath() const;
	bool GetStatus() const;
	void SendJsonData(std::string data);

	// Get on the correct executor
	void run();

	// Start the asynchronous operation
	void on_run();
	void on_accept(beast::error_code ec);
	void do_read();
	void on_read(beast::error_code ec, std::size_t bytes_transferred);
	void on_write(beast::error_code ec, std::size_t bytes_transferred);
private:
	void Info(std::string msg);

private:
	websocket::stream<beast::tcp_stream> ws_;
	pRecvData pMsg_;
	pCb_LogInfo pLog_;
	pCb_Close_ pClose_;
	std::string urlPath_;              // url路径
	std::atomic<bool> bConnected_;     // 连接标志位
	char buffer_[BUFF_SIZE_MAX];
};

// Accepts incoming connections and launches the sessions
class listener : public std::enable_shared_from_this<listener>
{
public:
	listener(int port, pCb_LogInfo pLog, pRecvData pData);

	// Start accepting incoming connections
	void run();

	bool SendMsg(std::string url, std::string data);

private:
	void do_accept();
	void on_accept(beast::error_code ec, tcp::socket socket);

	void Info(std::string msg);
	void closeClient(std::shared_ptr<WebSocketSession> ptr);

private:
	typedef boost::shared_ptr<net::io_service> io_service_ptr;
	typedef boost::shared_ptr<net::io_service::work> work_ptr;

	io_service_ptr ios_ptr_;
	work_ptr work_ptr_;
	std::shared_ptr<std::thread> pthread_;

	std::mutex mtxClients_;
	std::unordered_set<std::shared_ptr<WebSocketSession>> sessions_;  // 客户端管理
	std::shared_ptr<tcp::acceptor> pAcceptor_;
	pRecvData pMsg_;      // 消息回调
	pCb_LogInfo pLog_;    // 日志回调
};

Websocket.cpp

#include "Websocket.h"

std::string WebSocketSession::GetUrlPath() const
{
	return urlPath_;
}


bool WebSocketSession::GetStatus() const
{
	return bConnected_;
}

void WebSocketSession::SendJsonData(std::string data)
{
	if (!bConnected_)
		return;
	boost::beast::error_code ecode;
	ws_.text(ws_.got_text());
	ws_.write(boost::asio::buffer(data), ecode);
	if (ecode)
	{
		std::cerr << "[WebSocketSession::SendJsonData] error" << ecode.message()<<std::endl;
		bConnected_ = false;
	}
}

void WebSocketSession::run()
{
	// We need to be executing within a strand to perform async operations
    // on the I/O objects in this session. Although not strictly necessary
	// for single-threaded contexts, this example code is written to be
	// thread-safe by default.
	net::dispatch(ws_.get_executor(),
		beast::bind_front_handler(
			&WebSocketSession::on_run,
			shared_from_this()));
}

void WebSocketSession::on_run()
{
	// Set suggested timeout settings for the websocket
	ws_.set_option(
		websocket::stream_base::timeout::suggested(
			beast::role_type::server));

	// Set a decorator to change the Server of the handshake
	ws_.set_option(websocket::stream_base::decorator(
		[](websocket::response_type& res)
		{
			res.set(http::field::server,
				std::string(BOOST_BEAST_VERSION_STRING) +
				" websocket-server-async");
		}));

	beast::flat_buffer buffer;

	// Read the HTTP request ourselves
	http::request<http::string_body> req;
	http::read(ws_.next_layer(), buffer, req);

	// See if its a WebSocket upgrade request
	if (websocket::is_upgrade(req))
	{
		// Construct the stream, transferring ownership of the socket
		//stream ws(std::move(sock));

		// Clients SHOULD NOT begin sending WebSocket
		// frames until the server has provided a response.
		BOOST_ASSERT(buffer.size() == 0);

		// Accept the upgrade request
		ws_.async_accept(req,
			beast::bind_front_handler(
				&WebSocketSession::on_accept,
				shared_from_this()));

		// 获取客户端url路径
		urlPath_ = std::string(req.target().data(), req.target().length()).data();
	}
}

void WebSocketSession::on_accept(beast::error_code ec)
{
	if (ec)
	{
		std::cerr << "[WebSocketSession::on_accept] error info:"<<ec.message() << std::endl;
		return;
	}
	
	// Read a message
	do_read();
}

void WebSocketSession::do_read()
{
	// Read a message into our buffer
	memset(buffer_, 0, BUFF_SIZE_MAX);
	ws_.async_read_some(
		boost::asio::buffer(buffer_, BUFF_SIZE_MAX),
		beast::bind_front_handler(
			&WebSocketSession::on_read,
			shared_from_this()));
}

void WebSocketSession::on_read(beast::error_code ec, std::size_t bytes_transferred)
{
	if (ec)
	{
		std::string msg = ec.message();
		if (pClose_)
			pClose_(shared_from_this());
		return;
	}
	
	if (pMsg_)
		pMsg_(urlPath_, std::string(buffer_, bytes_transferred));
	
	do_read();
}

void WebSocketSession::on_write(beast::error_code ec, std::size_t bytes_transferred)
{
	boost::ignore_unused(bytes_transferred);

	if (ec)
	{
		std::cerr << "error in write:" << ec.message() << std::endl;
		Info("write:" + ec.message());
		if (pClose_)
			pClose_(shared_from_this());
	}
}

void WebSocketSession::Info(std::string msg)
{
	if (pLog_)
		pLog_("[WebSocketClient]" + urlPath_ + "," + msg);
}



listener::listener(int port, pCb_LogInfo pLog, pRecvData pData)
	:pMsg_(pData),
	pLog_(pLog)
{
	ios_ptr_.reset(new boost::asio::io_service);
	work_ptr_.reset(new boost::asio::io_service::work(*ios_ptr_));
	pthread_.reset(new std::thread(boost::bind(&boost::asio::io_service::run, ios_ptr_)));

	tcp::endpoint ep(boost::asio::ip::tcp::v4(), port);
	pAcceptor_.reset(new tcp::acceptor(*ios_ptr_));
	beast::error_code ec;

	// Open the acceptor
	pAcceptor_->open(ep.protocol(), ec);
	if (ec)
	{
		std::string msg = ec.message();
		return;
	}

	// Allow address reuse
	pAcceptor_->set_option(net::socket_base::reuse_address(true), ec);
	if (ec)
	{
		//fail(ec, "set_option");
		std::string msg = ec.message();
		return;
	}

	// Bind to the server address
	pAcceptor_->bind(ep, ec);
	if (ec)
	{
		//	fail(ec, "bind");
		std::string msg = ec.message();
		return;
	}

	// Start listening for connections
	pAcceptor_->listen(net::socket_base::max_listen_connections, ec);
	if (ec)
	{
		//	fail(ec, "listen");
		std::string msg = ec.message();
		return;
	}
}

void listener::run()
{
	do_accept();
}

bool listener::SendMsg(std::string url, std::string data)
{
	std::lock_guard<std::mutex> lock(mtxClients_);
	if (data.empty() || url.empty() || 0 == sessions_.size())
		return false;

	for (auto ptr = sessions_.begin(); ptr != sessions_.end();)
	{
		if (!ptr->get()->GetStatus())
		{
			ptr = sessions_.erase(ptr);
			if (ptr == sessions_.end())
				break;
			else
				continue;
		}
		if (ptr->get()->GetUrlPath() == url)
				ptr->get()->SendJsonData(data);
		++ptr;
	}

	return true;
}

void listener::do_accept()
{
	// The new connection gets its own strand
	pAcceptor_->async_accept(
		net::make_strand(*ios_ptr_),
		beast::bind_front_handler(
			&listener::on_accept,
			shared_from_this()));
}

void listener::on_accept(beast::error_code ec, tcp::socket socket)
{
	if (ec)
	{
		//	fail(ec, "accept");
	}
	else
	{
		// Create the session and run it
		auto session = std::make_shared<WebSocketSession>(std::move(socket),
			pLog_,
			pMsg_,
			std::bind(&listener::closeClient, this,
				std::placeholders::_1));
		session->run();
		std::unique_lock<std::mutex> lock(mtxClients_);
		sessions_.insert(session);
	}

	// Accept another connection
	do_accept();
}

void listener::Info(std::string msg)
{
	if (pLog_)
		pLog_("[WebSocketServer]" + msg);
}

void listener::closeClient(std::shared_ptr<WebSocketSession> ptr)
{
	std::unique_lock<std::mutex> lock(mtxClients_);
	if (nullptr == ptr || sessions_.empty())
		return;

	for (auto it = sessions_.begin(); it != sessions_.end(); ++it)
	{
		if (*it == ptr)
		{
			sessions_.erase(it);
			break;
		}
	}
	Info("clients:" + std::to_string(sessions_.size()));
}

你可能感兴趣的:(c++)