在boost官网案例的基础上进行的修改,以下具体功能的实现方式应该根据具体应用场景而定
boost版本:1.77
功能:websocket服务器端,能够处理多个客户端,获取其客户端的url路径
Websocket.h
cpp
#pragma once
#include <iostream>
#include <unordered_set>
#include <boost/beast.hpp>
#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/asio/dispatch.hpp>
#include <boost/asio/strand.hpp>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <algorithm>
#include <cstdlib>
#include <functional>
#include <memory>
#include <string>
#include <thread>
#include <vector>
#include "WebServer.h"
#define BUFF_SIZE_MAX 10*1024
namespace beast = boost::beast; // from <boost/beast.hpp>
namespace http = beast::http; // from <boost/beast/http.hpp>
namespace websocket = beast::websocket; // from <boost/beast/websocket.hpp>
namespace net = boost::asio; // from <boost/asio.hpp>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
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
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<tcp_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()));
}