add old lci module
This commit is contained in:
1 parent
38a6e815f0
commit
8499631091
171 files changed
+25562
No files matched your search
@@ -0,0 +1,417 @@
|
||||
#include "net/HttpServer.hpp"
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
namespace pmc {
|
||||
namespace net {
|
||||
|
||||
// Session类 - 处理单个HTTP连接
|
||||
class HttpServer::Session : public std::enable_shared_from_this<Session> {
|
||||
public:
|
||||
Session(tcp::socket&& socket, HttpServer* server)
|
||||
: socket_(std::move(socket)), server_(server) {
|
||||
}
|
||||
|
||||
void run() {
|
||||
doRead();
|
||||
}
|
||||
|
||||
private:
|
||||
void doRead() {
|
||||
auto self = shared_from_this();
|
||||
http::async_read(socket_, buffer_, request_,
|
||||
[this, self](beast::error_code ec, std::size_t) {
|
||||
if (!ec) {
|
||||
processRequest();
|
||||
} else if (ec != http::error::end_of_stream) {
|
||||
// 错误处理
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void processRequest() {
|
||||
try {
|
||||
// 创建响应对象
|
||||
auto response = std::make_shared<http::response<http::string_body>>(
|
||||
server_->handleRequest(request_)
|
||||
);
|
||||
doWrite(response);
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Error processing request: " << e.what() << std::endl;
|
||||
// 发送错误响应
|
||||
auto error_response = std::make_shared<http::response<http::string_body>>();
|
||||
error_response->version(request_.version());
|
||||
error_response->result(http::status::internal_server_error);
|
||||
error_response->set(http::field::content_type, "text/plain");
|
||||
error_response->body() = "500 Internal Server Error\n";
|
||||
error_response->prepare_payload();
|
||||
doWrite(error_response);
|
||||
}
|
||||
}
|
||||
|
||||
void doWrite(std::shared_ptr<http::response<http::string_body>> response) {
|
||||
auto self = shared_from_this();
|
||||
http::async_write(socket_, *response,
|
||||
[this, self, response](beast::error_code ec, std::size_t) {
|
||||
if (!ec) {
|
||||
// 检查是否需要保持连接
|
||||
if (!response->keep_alive()) {
|
||||
socket_.shutdown(tcp::socket::shutdown_send, ec);
|
||||
}
|
||||
}
|
||||
// 继续读取下一个请求
|
||||
doRead();
|
||||
});
|
||||
}
|
||||
|
||||
tcp::socket socket_;
|
||||
beast::flat_buffer buffer_;
|
||||
http::request<http::string_body> request_;
|
||||
HttpServer* server_;
|
||||
};
|
||||
|
||||
// Listener类 - 接受新连接
|
||||
class HttpServer::Listener : public std::enable_shared_from_this<Listener> {
|
||||
public:
|
||||
Listener(asio::io_context& ioc, tcp::endpoint endpoint, HttpServer* server)
|
||||
: ioc_(ioc), acceptor_(ioc), server_(server) {
|
||||
beast::error_code ec;
|
||||
acceptor_.open(endpoint.protocol(), ec);
|
||||
if (!ec) {
|
||||
acceptor_.set_option(asio::socket_base::reuse_address(true), ec);
|
||||
acceptor_.bind(endpoint, ec);
|
||||
if (!ec) {
|
||||
acceptor_.listen(asio::socket_base::max_listen_connections, ec);
|
||||
}
|
||||
}
|
||||
if (ec) {
|
||||
std::cerr << "Listener error: " << ec.message() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void run() {
|
||||
if (acceptor_.is_open()) {
|
||||
doAccept();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void doAccept() {
|
||||
acceptor_.async_accept(
|
||||
[this, self = shared_from_this()](beast::error_code ec, tcp::socket socket) {
|
||||
if (!ec) {
|
||||
std::make_shared<Session>(std::move(socket), server_)->run();
|
||||
}
|
||||
doAccept();
|
||||
});
|
||||
}
|
||||
|
||||
asio::io_context& ioc_;
|
||||
tcp::acceptor acceptor_;
|
||||
HttpServer* server_;
|
||||
};
|
||||
|
||||
// HttpServer实现 - 新构造函数(监听所有接口)
|
||||
HttpServer::HttpServer(unsigned short port, unsigned int threads)
|
||||
: address_("0.0.0.0"), port_(port), threads_(threads),
|
||||
ioc_(std::make_unique<asio::io_context>(threads)) {
|
||||
if (!ioc_) {
|
||||
throw std::runtime_error("Failed to create io_context");
|
||||
}
|
||||
}
|
||||
|
||||
// HttpServer实现 - 新构造函数(指定监听地址)
|
||||
HttpServer::HttpServer(const std::string& address, unsigned short port, unsigned int threads)
|
||||
: address_(address), port_(port), threads_(threads),
|
||||
ioc_(std::make_unique<asio::io_context>(threads)) {
|
||||
if (!ioc_) {
|
||||
throw std::runtime_error("Failed to create io_context");
|
||||
}
|
||||
}
|
||||
|
||||
HttpServer::~HttpServer() {
|
||||
stop();
|
||||
}
|
||||
|
||||
void HttpServer::start() {
|
||||
if (running_) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 创建端点(使用指定的地址和端口)
|
||||
asio::ip::address ip_address = asio::ip::make_address(address_);
|
||||
tcp::endpoint endpoint(ip_address, port_);
|
||||
|
||||
// 创建监听器
|
||||
listener_ = std::make_shared<Listener>(*ioc_, endpoint, this);
|
||||
if (!listener_) {
|
||||
throw std::runtime_error("Failed to create listener");
|
||||
}
|
||||
listener_->run();
|
||||
|
||||
running_ = true;
|
||||
|
||||
// 启动工作线程
|
||||
for (unsigned int i = 0; i < threads_; ++i) {
|
||||
worker_threads_.emplace_back([this]() {
|
||||
try {
|
||||
ioc_->run();
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Thread exception: " << e.what() << std::endl;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
std::cout << "HTTP Server started on " << address_ << ":" << port_
|
||||
<< " with " << threads_ << " threads" << std::endl;
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Failed to start server: " << e.what() << std::endl;
|
||||
running_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void HttpServer::stop() {
|
||||
if (!running_) {
|
||||
return;
|
||||
}
|
||||
|
||||
running_ = false;
|
||||
|
||||
if (ioc_) {
|
||||
ioc_->stop();
|
||||
}
|
||||
|
||||
for (auto& thread : worker_threads_) {
|
||||
if (thread.joinable()) {
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
worker_threads_.clear();
|
||||
|
||||
std::cout << "HTTP Server stopped" << std::endl;
|
||||
}
|
||||
|
||||
void HttpServer::get(const std::string& path, HttpRequestHandler handler) {
|
||||
get_handlers_[path] = handler;
|
||||
}
|
||||
|
||||
void HttpServer::post(const std::string& path, HttpRequestHandler handler) {
|
||||
post_handlers_[path] = handler;
|
||||
}
|
||||
|
||||
void HttpServer::put(const std::string& path, HttpRequestHandler handler) {
|
||||
put_handlers_[path] = handler;
|
||||
}
|
||||
|
||||
void HttpServer::del(const std::string& path, HttpRequestHandler handler) {
|
||||
delete_handlers_[path] = handler;
|
||||
}
|
||||
|
||||
void HttpServer::use(MiddlewareHandler middleware) {
|
||||
middlewares_.push_back(middleware);
|
||||
}
|
||||
|
||||
void HttpServer::setStaticDirectory(const std::string& path) {
|
||||
static_directory_ = path;
|
||||
}
|
||||
|
||||
bool HttpServer::isRunning() const {
|
||||
return running_;
|
||||
}
|
||||
|
||||
unsigned short HttpServer::getPort() const {
|
||||
return port_;
|
||||
}
|
||||
|
||||
std::string HttpServer::getAddress() const {
|
||||
return address_;
|
||||
}
|
||||
|
||||
void HttpServer::run() {
|
||||
start();
|
||||
if (ioc_) {
|
||||
ioc_->run();
|
||||
}
|
||||
}
|
||||
|
||||
void HttpServer::showStatus() const {
|
||||
std::cout << "=== HTTP Server Status ===" << std::endl;
|
||||
std::cout << "Address: " << address_ << std::endl;
|
||||
std::cout << "Port: " << port_ << std::endl;
|
||||
std::cout << "Threads: " << threads_ << std::endl;
|
||||
std::cout << "Running: " << (running_ ? "Yes" : "No") << std::endl;
|
||||
std::cout << "Static Directory: " << (static_directory_.empty() ? "Not set" : static_directory_) << std::endl;
|
||||
std::cout << "Registered Routes:" << std::endl;
|
||||
std::cout << " GET: " << get_handlers_.size() << " handler(s)" << std::endl;
|
||||
for (const auto& [path, _] : get_handlers_) {
|
||||
std::cout << " GET " << path << std::endl;
|
||||
}
|
||||
std::cout << " POST: " << post_handlers_.size() << " handler(s)" << std::endl;
|
||||
for (const auto& [path, _] : post_handlers_) {
|
||||
std::cout << " POST " << path << std::endl;
|
||||
}
|
||||
std::cout << " PUT: " << put_handlers_.size() << " handler(s)" << std::endl;
|
||||
for (const auto& [path, _] : put_handlers_) {
|
||||
std::cout << " PUT " << path << std::endl;
|
||||
}
|
||||
std::cout << " DELETE: " << delete_handlers_.size() << " handler(s)" << std::endl;
|
||||
for (const auto& [path, _] : delete_handlers_) {
|
||||
std::cout << " DELETE " << path << std::endl;
|
||||
}
|
||||
std::cout << "Middlewares: " << middlewares_.size() << std::endl;
|
||||
std::cout << "==========================" << std::endl;
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, std::string> HttpServer::parseQueryParams(const std::string& query) {
|
||||
std::unordered_map<std::string, std::string> params;
|
||||
|
||||
if (query.empty()) {
|
||||
return params;
|
||||
}
|
||||
|
||||
std::vector<std::string> pairs;
|
||||
boost::split(pairs, query, boost::is_any_of("&"));
|
||||
|
||||
for (const auto& pair : pairs) {
|
||||
std::vector<std::string> key_value;
|
||||
boost::split(key_value, pair, boost::is_any_of("="));
|
||||
|
||||
if (key_value.size() == 2) {
|
||||
params[key_value[0]] = key_value[1];
|
||||
} else if (key_value.size() == 1) {
|
||||
params[key_value[0]] = "";
|
||||
}
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
http::response<http::string_body> HttpServer::handleRequest(const http::request<http::string_body>& req) {
|
||||
// 将 target() 转换为字符串
|
||||
std::string target_path = std::string(req.target().data(), req.target().size());
|
||||
std::string query_string;
|
||||
|
||||
// 分离路径和查询参数
|
||||
auto query_pos = target_path.find('?');
|
||||
if (query_pos != std::string::npos) {
|
||||
query_string = target_path.substr(query_pos + 1);
|
||||
target_path = target_path.substr(0, query_pos);
|
||||
}
|
||||
|
||||
// 解析查询参数
|
||||
auto query_params = parseQueryParams(query_string);
|
||||
|
||||
// 准备响应对象
|
||||
http::response<http::string_body> response;
|
||||
response.version(req.version());
|
||||
response.set(http::field::server, "Boost.Beast HttpServer");
|
||||
response.set(http::field::content_type, "text/plain");
|
||||
response.keep_alive(req.keep_alive());
|
||||
|
||||
// 执行中间件链
|
||||
bool middleware_handled = false;
|
||||
for (const auto& middleware : middlewares_) {
|
||||
if (!middleware(req, response, query_params)) {
|
||||
middleware_handled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果中间件处理了请求,直接返回
|
||||
if (middleware_handled) {
|
||||
response.prepare_payload();
|
||||
return response;
|
||||
}
|
||||
|
||||
// 查找路由处理器
|
||||
HttpRequestHandler handler = nullptr;
|
||||
|
||||
switch (req.method()) {
|
||||
case http::verb::get: {
|
||||
auto it = get_handlers_.find(target_path);
|
||||
if (it != get_handlers_.end()) {
|
||||
handler = it->second;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case http::verb::post: {
|
||||
auto it = post_handlers_.find(target_path);
|
||||
if (it != post_handlers_.end()) {
|
||||
handler = it->second;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case http::verb::put: {
|
||||
auto it = put_handlers_.find(target_path);
|
||||
if (it != put_handlers_.end()) {
|
||||
handler = it->second;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case http::verb::delete_: {
|
||||
auto it = delete_handlers_.find(target_path);
|
||||
if (it != delete_handlers_.end()) {
|
||||
handler = it->second;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// 处理静态文件
|
||||
if (!handler && !static_directory_.empty()) {
|
||||
std::string file_path = static_directory_ + target_path;
|
||||
std::ifstream file(file_path, std::ios::binary);
|
||||
|
||||
if (file.is_open()) {
|
||||
std::stringstream buffer;
|
||||
buffer << file.rdbuf();
|
||||
response.result(http::status::ok);
|
||||
response.body() = buffer.str();
|
||||
|
||||
// 根据文件扩展名设置Content-Type
|
||||
if (boost::algorithm::ends_with(file_path, ".html")) {
|
||||
response.set(http::field::content_type, "text/html");
|
||||
} else if (boost::algorithm::ends_with(file_path, ".css")) {
|
||||
response.set(http::field::content_type, "text/css");
|
||||
} else if (boost::algorithm::ends_with(file_path, ".js")) {
|
||||
response.set(http::field::content_type, "application/javascript");
|
||||
} else if (boost::algorithm::ends_with(file_path, ".json")) {
|
||||
response.set(http::field::content_type, "application/json");
|
||||
} else if (boost::algorithm::ends_with(file_path, ".png")) {
|
||||
response.set(http::field::content_type, "image/png");
|
||||
} else if (boost::algorithm::ends_with(file_path, ".jpg") ||
|
||||
boost::algorithm::ends_with(file_path, ".jpeg")) {
|
||||
response.set(http::field::content_type, "image/jpeg");
|
||||
}
|
||||
|
||||
response.prepare_payload();
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果找到处理器,调用它
|
||||
if (handler) {
|
||||
try {
|
||||
return handler(req, query_params);
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Handler exception: " << e.what() << std::endl;
|
||||
response.result(http::status::internal_server_error);
|
||||
response.body() = "500 Internal Server Error\n";
|
||||
response.prepare_payload();
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
// 404 Not Found
|
||||
response.result(http::status::not_found);
|
||||
response.body() = "404 Not Found\n";
|
||||
response.prepare_payload();
|
||||
return response;
|
||||
}
|
||||
|
||||
} // namespace net
|
||||
} // namespace pmc
|
||||
@@ -0,0 +1,386 @@
|
||||
#include "net/WebSocketClient.hpp"
|
||||
#include <boost/beast/core.hpp>
|
||||
#include <boost/beast/websocket.hpp>
|
||||
#include <boost/asio/strand.hpp>
|
||||
#include <boost/asio/dispatch.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/asio/connect.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <condition_variable>
|
||||
#include <atomic>
|
||||
|
||||
namespace pmc {
|
||||
namespace net {
|
||||
|
||||
namespace beast = boost::beast;
|
||||
namespace asio = boost::asio;
|
||||
using tcp = asio::ip::tcp;
|
||||
|
||||
// 内部实现结构体
|
||||
struct WebSocketClient::Impl {
|
||||
// 配置参数
|
||||
std::string host;
|
||||
std::string port;
|
||||
std::string path;
|
||||
|
||||
// Boost 对象
|
||||
asio::io_context ioc;
|
||||
tcp::resolver resolver;
|
||||
beast::websocket::stream<beast::tcp_stream> ws;
|
||||
|
||||
// 线程管理
|
||||
std::thread io_thread;
|
||||
std::atomic<bool> running{false};
|
||||
|
||||
// 回调函数
|
||||
WebSocketClientBase::MessageCallback message_callback;
|
||||
WebSocketClientBase::ErrorCallback error_callback;
|
||||
WebSocketClientBase::ConnectCallback connect_callback;
|
||||
WebSocketClientBase::CloseCallback close_callback;
|
||||
|
||||
// 消息队列
|
||||
std::queue<std::string> send_queue;
|
||||
std::mutex queue_mutex;
|
||||
std::condition_variable queue_cv;
|
||||
std::atomic<bool> sending{false};
|
||||
|
||||
// 状态标志
|
||||
std::atomic<bool> connected{false};
|
||||
std::atomic<bool> connecting{false};
|
||||
std::atomic<bool> stopping{false};
|
||||
|
||||
// 配置选项
|
||||
bool auto_reconnect{false};
|
||||
int reconnect_interval_ms{3000};
|
||||
int heartbeat_interval_ms{30000};
|
||||
|
||||
// 构造函数
|
||||
Impl(const std::string& host, const std::string& port, const std::string& path)
|
||||
: host(host)
|
||||
, port(port)
|
||||
, path(path)
|
||||
, resolver(asio::make_strand(ioc))
|
||||
, ws(asio::make_strand(ioc))
|
||||
{
|
||||
}
|
||||
|
||||
// 析构函数
|
||||
~Impl() {
|
||||
stop();
|
||||
}
|
||||
|
||||
// 启动IO线程
|
||||
void start() {
|
||||
if (!running) {
|
||||
running = true;
|
||||
io_thread = std::thread([this]() {
|
||||
while (running) {
|
||||
try {
|
||||
ioc.run();
|
||||
break;
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "IO上下文异常: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 停止IO线程
|
||||
void stop() {
|
||||
running = false;
|
||||
stopping = true;
|
||||
|
||||
if (io_thread.joinable()) {
|
||||
ioc.stop();
|
||||
io_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
// 执行连接
|
||||
void doConnect() {
|
||||
if (connecting || connected) {
|
||||
return;
|
||||
}
|
||||
|
||||
connecting = true;
|
||||
connected = false;
|
||||
|
||||
// 解析主机名
|
||||
resolver.async_resolve(host, port,
|
||||
[this](beast::error_code ec, tcp::resolver::results_type results) {
|
||||
onResolve(ec, results);
|
||||
});
|
||||
}
|
||||
|
||||
// 解析完成回调
|
||||
void onResolve(beast::error_code ec, tcp::resolver::results_type results) {
|
||||
if (ec) {
|
||||
onError("解析失败: " + ec.message());
|
||||
return;
|
||||
}
|
||||
|
||||
// 连接到解析出的端点
|
||||
beast::get_lowest_layer(ws).async_connect(results,
|
||||
[this](beast::error_code ec, tcp::resolver::results_type::endpoint_type) {
|
||||
onConnect(ec);
|
||||
});
|
||||
}
|
||||
|
||||
// 连接完成回调
|
||||
void onConnect(beast::error_code ec) {
|
||||
if (ec) {
|
||||
onError("连接失败: " + ec.message());
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置WebSocket选项
|
||||
ws.set_option(beast::websocket::stream_base::timeout::suggested(
|
||||
beast::role_type::client));
|
||||
|
||||
// 执行WebSocket握手
|
||||
ws.async_handshake(host, path,
|
||||
[this](beast::error_code ec) {
|
||||
onHandshake(ec);
|
||||
});
|
||||
}
|
||||
|
||||
// 握手完成回调
|
||||
void onHandshake(beast::error_code ec) {
|
||||
connecting = false;
|
||||
|
||||
if (ec) {
|
||||
onError("握手失败: " + ec.message());
|
||||
return;
|
||||
}
|
||||
|
||||
connected = true;
|
||||
|
||||
// 调用连接成功回调
|
||||
if (connect_callback) {
|
||||
connect_callback(true);
|
||||
}
|
||||
|
||||
// 开始读取消息
|
||||
doRead();
|
||||
|
||||
// 开始发送队列中的消息
|
||||
doSend();
|
||||
}
|
||||
|
||||
// 读取消息
|
||||
void doRead() {
|
||||
if (!connected) {
|
||||
return;
|
||||
}
|
||||
|
||||
ws.async_read(buffer,
|
||||
[this](beast::error_code ec, std::size_t bytes_transferred) {
|
||||
onRead(ec, bytes_transferred);
|
||||
});
|
||||
}
|
||||
|
||||
// 读取完成回调
|
||||
void onRead(beast::error_code ec, std::size_t bytes_transferred) {
|
||||
if (ec) {
|
||||
if (ec == beast::websocket::error::closed) {
|
||||
// 正常关闭
|
||||
onClose();
|
||||
} else {
|
||||
onError("读取失败: " + ec.message());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理接收到的消息
|
||||
std::string message = beast::buffers_to_string(buffer.data());
|
||||
buffer.consume(bytes_transferred);
|
||||
|
||||
if (message_callback) {
|
||||
message_callback(message);
|
||||
}
|
||||
|
||||
// 继续读取
|
||||
doRead();
|
||||
}
|
||||
|
||||
// 发送消息
|
||||
void doSend() {
|
||||
if (!connected || sending) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> lock(queue_mutex);
|
||||
if (send_queue.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
sending = true;
|
||||
std::string message = std::move(send_queue.front());
|
||||
send_queue.pop();
|
||||
lock.unlock();
|
||||
|
||||
ws.async_write(asio::buffer(message),
|
||||
[this](beast::error_code ec, std::size_t bytes_transferred) {
|
||||
onWrite(ec, bytes_transferred);
|
||||
});
|
||||
}
|
||||
|
||||
// 写入完成回调
|
||||
void onWrite(beast::error_code ec, std::size_t bytes_transferred) {
|
||||
sending = false;
|
||||
|
||||
if (ec) {
|
||||
onError("写入失败: " + ec.message());
|
||||
return;
|
||||
}
|
||||
|
||||
// 继续发送队列中的消息
|
||||
doSend();
|
||||
}
|
||||
|
||||
// 错误处理
|
||||
void onError(const std::string& error) {
|
||||
connecting = false;
|
||||
connected = false;
|
||||
|
||||
std::cerr << "WebSocket错误: " << error << std::endl;
|
||||
|
||||
if (error_callback) {
|
||||
error_callback(error);
|
||||
}
|
||||
|
||||
// 自动重连
|
||||
if (auto_reconnect && !stopping) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(reconnect_interval_ms));
|
||||
asio::post(ioc, [this]() { doConnect(); });
|
||||
}
|
||||
}
|
||||
|
||||
// 连接关闭
|
||||
void onClose() {
|
||||
connected = false;
|
||||
|
||||
if (close_callback) {
|
||||
close_callback();
|
||||
}
|
||||
|
||||
// 自动重连
|
||||
if (auto_reconnect && !stopping) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(reconnect_interval_ms));
|
||||
asio::post(ioc, [this]() { doConnect(); });
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
beast::flat_buffer buffer;
|
||||
};
|
||||
|
||||
// WebSocketClient 实现
|
||||
WebSocketClient::WebSocketClient(const std::string& host, const std::string& port, const std::string& path)
|
||||
: impl_(std::make_unique<Impl>(host, port, path))
|
||||
{
|
||||
impl_->start();
|
||||
}
|
||||
|
||||
WebSocketClient::~WebSocketClient() {
|
||||
disconnect();
|
||||
}
|
||||
|
||||
void WebSocketClient::setMessageCallback(MessageCallback callback) {
|
||||
impl_->message_callback = callback;
|
||||
}
|
||||
|
||||
void WebSocketClient::setErrorCallback(ErrorCallback callback) {
|
||||
impl_->error_callback = callback;
|
||||
}
|
||||
|
||||
void WebSocketClient::setConnectCallback(ConnectCallback callback) {
|
||||
impl_->connect_callback = callback;
|
||||
}
|
||||
|
||||
void WebSocketClient::setCloseCallback(CloseCallback callback) {
|
||||
impl_->close_callback = callback;
|
||||
}
|
||||
|
||||
bool WebSocketClient::connect(int timeout_ms) {
|
||||
if (impl_->connecting || impl_->connected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
impl_->doConnect();
|
||||
return true;
|
||||
}
|
||||
|
||||
void WebSocketClient::disconnect() {
|
||||
if (!impl_->connected && !impl_->connecting) {
|
||||
return;
|
||||
}
|
||||
|
||||
impl_->stopping = true;
|
||||
|
||||
asio::post(impl_->ioc, [this]() {
|
||||
beast::error_code ec;
|
||||
impl_->ws.close(beast::websocket::close_code::normal, ec);
|
||||
impl_->connected = false;
|
||||
impl_->connecting = false;
|
||||
});
|
||||
}
|
||||
|
||||
bool WebSocketClient::isConnected() const {
|
||||
return impl_->connected;
|
||||
}
|
||||
|
||||
bool WebSocketClient::send(const std::string& message) {
|
||||
if (!impl_->connected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(impl_->queue_mutex);
|
||||
impl_->send_queue.push(message);
|
||||
}
|
||||
|
||||
impl_->queue_cv.notify_one();
|
||||
asio::post(impl_->ioc, [this]() { impl_->doSend(); });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WebSocketClient::sendBinary(const void* data, size_t size) {
|
||||
if (!impl_->connected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string message(static_cast<const char*>(data), size);
|
||||
return send(message);
|
||||
}
|
||||
|
||||
void WebSocketClient::setAutoReconnect(bool enable, int interval_ms) {
|
||||
impl_->auto_reconnect = enable;
|
||||
impl_->reconnect_interval_ms = interval_ms;
|
||||
}
|
||||
|
||||
void WebSocketClient::setHeartbeatInterval(int interval_ms) {
|
||||
impl_->heartbeat_interval_ms = interval_ms;
|
||||
}
|
||||
|
||||
std::string WebSocketClient::getHost() const {
|
||||
return impl_->host;
|
||||
}
|
||||
|
||||
std::string WebSocketClient::getPort() const {
|
||||
return impl_->port;
|
||||
}
|
||||
|
||||
std::string WebSocketClient::getPath() const {
|
||||
return impl_->path;
|
||||
}
|
||||
|
||||
} // namespace net
|
||||
} // namespace pmc
|
||||
@@ -0,0 +1,620 @@
|
||||
#include "net/WebSocketClientSSL.hpp"
|
||||
#include <boost/beast/websocket.hpp>
|
||||
#include <boost/beast/websocket/ssl.hpp>
|
||||
#include <boost/asio/ssl.hpp>
|
||||
#include <boost/asio/strand.hpp>
|
||||
#include <boost/asio/dispatch.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <condition_variable>
|
||||
#include <atomic>
|
||||
|
||||
namespace pmc {
|
||||
namespace net {
|
||||
|
||||
namespace beast = boost::beast;
|
||||
namespace asio = boost::asio;
|
||||
namespace ssl = asio::ssl;
|
||||
using tcp = asio::ip::tcp;
|
||||
using websocket = beast::websocket::stream<beast::ssl_stream<beast::tcp_stream>>;
|
||||
|
||||
// 内部实现结构体
|
||||
struct WebSocketClientSSL::Impl {
|
||||
// 配置参数
|
||||
std::string host;
|
||||
std::string port;
|
||||
std::string path;
|
||||
|
||||
// SSL 配置
|
||||
bool verify_certificate{true};
|
||||
std::string cert_file;
|
||||
std::string key_file;
|
||||
std::string ca_file;
|
||||
std::string ciphers;
|
||||
std::string sni_hostname;
|
||||
long ssl_options{ssl::context::default_workarounds |
|
||||
ssl::context::no_sslv2 |
|
||||
ssl::context::no_sslv3};
|
||||
|
||||
// Boost 对象
|
||||
asio::io_context ioc;
|
||||
ssl::context ssl_ctx{ssl::context::tlsv12_client};
|
||||
tcp::resolver resolver;
|
||||
websocket ws;
|
||||
asio::steady_timer heartbeat_timer;
|
||||
asio::steady_timer reconnect_timer;
|
||||
|
||||
// 回调函数
|
||||
MessageCallback message_callback;
|
||||
ErrorCallback error_callback;
|
||||
ConnectCallback connect_callback;
|
||||
CloseCallback close_callback;
|
||||
|
||||
// 状态管理
|
||||
std::atomic<bool> connected{false};
|
||||
std::atomic<bool> connecting{false};
|
||||
std::atomic<bool> stopping{false};
|
||||
std::atomic<bool> auto_reconnect{false};
|
||||
std::atomic<int> reconnect_interval{3000};
|
||||
std::atomic<int> heartbeat_interval{30000};
|
||||
|
||||
// 消息队列
|
||||
struct QueuedMessage {
|
||||
std::string data;
|
||||
bool is_binary;
|
||||
};
|
||||
std::queue<QueuedMessage> send_queue;
|
||||
std::mutex queue_mutex;
|
||||
std::condition_variable queue_cv;
|
||||
std::atomic<bool> writing{false};
|
||||
|
||||
// 接收缓冲区
|
||||
beast::flat_buffer read_buffer;
|
||||
|
||||
// 线程
|
||||
std::thread io_thread;
|
||||
|
||||
Impl(const std::string& h, const std::string& p, const std::string& pt)
|
||||
: host(h)
|
||||
, port(p)
|
||||
, path(pt)
|
||||
, resolver(asio::make_strand(ioc))
|
||||
, ws(asio::make_strand(ioc), ssl_ctx)
|
||||
, heartbeat_timer(asio::make_strand(ioc))
|
||||
, reconnect_timer(asio::make_strand(ioc)) {
|
||||
|
||||
// 配置 SSL 上下文
|
||||
configureSSLContext();
|
||||
}
|
||||
|
||||
~Impl() {
|
||||
stopping = true;
|
||||
if (io_thread.joinable()) {
|
||||
ioc.stop();
|
||||
io_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void configureSSLContext() {
|
||||
try {
|
||||
// 设置 SSL 选项
|
||||
ssl_ctx.set_options(ssl_options);
|
||||
|
||||
// 设置密码列表
|
||||
if (!ciphers.empty()) {
|
||||
SSL_CTX_set_cipher_list(ssl_ctx.native_handle(), ciphers.c_str());
|
||||
}
|
||||
|
||||
// 加载证书文件
|
||||
if (!cert_file.empty()) {
|
||||
ssl_ctx.use_certificate_file(cert_file, ssl::context::pem);
|
||||
}
|
||||
|
||||
// 加载私钥文件
|
||||
if (!key_file.empty()) {
|
||||
ssl_ctx.use_private_key_file(key_file, ssl::context::pem);
|
||||
}
|
||||
|
||||
// 加载 CA 证书
|
||||
if (!ca_file.empty()) {
|
||||
ssl_ctx.load_verify_file(ca_file);
|
||||
} else {
|
||||
// 使用系统默认的 CA 证书
|
||||
ssl_ctx.set_default_verify_paths();
|
||||
}
|
||||
|
||||
// 设置验证模式
|
||||
if (verify_certificate) {
|
||||
ssl_ctx.set_verify_mode(ssl::verify_peer | ssl::verify_fail_if_no_peer_cert);
|
||||
} else {
|
||||
ssl_ctx.set_verify_mode(ssl::verify_none);
|
||||
}
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "SSL 上下文配置错误: " << e.what() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void setSNI() {
|
||||
// 设置 SNI 主机名
|
||||
if (!sni_hostname.empty()) {
|
||||
if (!SSL_set_tlsext_host_name(ws.next_layer().native_handle(), sni_hostname.c_str())) {
|
||||
beast::error_code ec{static_cast<int>(::ERR_get_error()), asio::error::get_ssl_category()};
|
||||
throw beast::system_error{ec};
|
||||
}
|
||||
} else if (!host.empty()) {
|
||||
// 使用主机名作为 SNI
|
||||
if (!SSL_set_tlsext_host_name(ws.next_layer().native_handle(), host.c_str())) {
|
||||
beast::error_code ec{static_cast<int>(::ERR_get_error()), asio::error::get_ssl_category()};
|
||||
throw beast::system_error{ec};
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 构造函数
|
||||
WebSocketClientSSL::WebSocketClientSSL(const std::string& host, const std::string& port, const std::string& path)
|
||||
: impl_(std::make_unique<Impl>(host, port, path)) {
|
||||
}
|
||||
|
||||
// 析构函数
|
||||
WebSocketClientSSL::~WebSocketClientSSL() {
|
||||
disconnect();
|
||||
}
|
||||
|
||||
// 回调函数设置
|
||||
void WebSocketClientSSL::setMessageCallback(MessageCallback callback) {
|
||||
impl_->message_callback = std::move(callback);
|
||||
}
|
||||
|
||||
void WebSocketClientSSL::setErrorCallback(ErrorCallback callback) {
|
||||
impl_->error_callback = std::move(callback);
|
||||
}
|
||||
|
||||
void WebSocketClientSSL::setConnectCallback(ConnectCallback callback) {
|
||||
impl_->connect_callback = std::move(callback);
|
||||
}
|
||||
|
||||
void WebSocketClientSSL::setCloseCallback(CloseCallback callback) {
|
||||
impl_->close_callback = std::move(callback);
|
||||
}
|
||||
|
||||
// SSL/TLS 配置
|
||||
void WebSocketClientSSL::setVerifyCertificate(bool verify) {
|
||||
impl_->verify_certificate = verify;
|
||||
impl_->configureSSLContext();
|
||||
}
|
||||
|
||||
void WebSocketClientSSL::setCertificateFile(const std::string& cert_file) {
|
||||
impl_->cert_file = cert_file;
|
||||
impl_->configureSSLContext();
|
||||
}
|
||||
|
||||
void WebSocketClientSSL::setPrivateKeyFile(const std::string& key_file) {
|
||||
impl_->key_file = key_file;
|
||||
impl_->configureSSLContext();
|
||||
}
|
||||
|
||||
void WebSocketClientSSL::setCertificateAuthorityFile(const std::string& ca_file) {
|
||||
impl_->ca_file = ca_file;
|
||||
impl_->configureSSLContext();
|
||||
}
|
||||
|
||||
void WebSocketClientSSL::setSSLContextOptions(long options) {
|
||||
impl_->ssl_options = options;
|
||||
impl_->configureSSLContext();
|
||||
}
|
||||
|
||||
void WebSocketClientSSL::setCiphers(const std::string& ciphers) {
|
||||
impl_->ciphers = ciphers;
|
||||
impl_->configureSSLContext();
|
||||
}
|
||||
|
||||
void WebSocketClientSSL::setSNIHostname(const std::string& sni_hostname) {
|
||||
impl_->sni_hostname = sni_hostname;
|
||||
}
|
||||
|
||||
// 连接函数
|
||||
bool WebSocketClientSSL::connect(int timeout_ms) {
|
||||
if (impl_->connecting || impl_->connected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
impl_->connecting = true;
|
||||
impl_->stopping = false;
|
||||
|
||||
// 启动 IO 线程
|
||||
if (!impl_->io_thread.joinable()) {
|
||||
impl_->io_thread = std::thread([this]() {
|
||||
try {
|
||||
impl_->ioc.run();
|
||||
} catch (const std::exception& e) {
|
||||
if (impl_->error_callback) {
|
||||
impl_->error_callback(std::string("IO context error: ") + e.what());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 异步连接
|
||||
asio::post(impl_->ioc, [this]() {
|
||||
try {
|
||||
impl_->setSNI();
|
||||
doConnect();
|
||||
} catch (const std::exception& e) {
|
||||
handleError("SSL setup failed", beast::error_code{}, e.what());
|
||||
}
|
||||
});
|
||||
|
||||
// 等待连接完成
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
while (impl_->connecting && !impl_->stopping) {
|
||||
if (timeout_ms > 0) {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - start).count();
|
||||
if (elapsed > timeout_ms) {
|
||||
impl_->connecting = false;
|
||||
if (impl_->error_callback) {
|
||||
impl_->error_callback("Connection timeout");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
|
||||
return impl_->connected;
|
||||
}
|
||||
|
||||
// 私有连接方法
|
||||
void WebSocketClientSSL::doConnect() {
|
||||
impl_->resolver.async_resolve(
|
||||
impl_->host,
|
||||
impl_->port,
|
||||
[this](beast::error_code ec, tcp::resolver::results_type results) {
|
||||
onResolve(ec, std::move(results));
|
||||
});
|
||||
}
|
||||
|
||||
void WebSocketClientSSL::onResolve(beast::error_code ec, tcp::resolver::results_type results) {
|
||||
if (ec) {
|
||||
handleError("Resolve failed", ec);
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置超时
|
||||
beast::get_lowest_layer(impl_->ws).expires_after(std::chrono::seconds(30));
|
||||
|
||||
// 异步连接
|
||||
beast::get_lowest_layer(impl_->ws).async_connect(
|
||||
results,
|
||||
[this](beast::error_code ec, tcp::resolver::results_type::endpoint_type endpoint) {
|
||||
onConnect(ec, endpoint);
|
||||
});
|
||||
}
|
||||
|
||||
void WebSocketClientSSL::onConnect(beast::error_code ec, tcp::resolver::results_type::endpoint_type endpoint) {
|
||||
if (ec) {
|
||||
handleError("Connect failed", ec);
|
||||
return;
|
||||
}
|
||||
|
||||
// SSL 握手
|
||||
impl_->ws.next_layer().async_handshake(
|
||||
ssl::stream_base::client,
|
||||
[this](beast::error_code ec) {
|
||||
onSSLHandshake(ec);
|
||||
});
|
||||
}
|
||||
|
||||
void WebSocketClientSSL::onSSLHandshake(beast::error_code ec) {
|
||||
if (ec) {
|
||||
handleError("SSL handshake failed", ec);
|
||||
return;
|
||||
}
|
||||
|
||||
// 关闭 TCP 超时设置
|
||||
beast::get_lowest_layer(impl_->ws).expires_never();
|
||||
|
||||
// 设置 WebSocket 选项
|
||||
impl_->ws.set_option(websocket::stream_base::timeout::suggested(beast::role_type::client));
|
||||
impl_->ws.read_message_max(10 * 1024 * 1024); // 10MB 限制
|
||||
|
||||
// WebSocket 握手
|
||||
impl_->ws.async_handshake(
|
||||
impl_->host + ":" + impl_->port,
|
||||
impl_->path,
|
||||
[this](beast::error_code ec) {
|
||||
onHandshake(ec);
|
||||
});
|
||||
}
|
||||
|
||||
void WebSocketClientSSL::onHandshake(beast::error_code ec) {
|
||||
impl_->connecting = false;
|
||||
|
||||
if (ec) {
|
||||
handleError("WebSocket handshake failed", ec);
|
||||
return;
|
||||
}
|
||||
|
||||
impl_->connected = true;
|
||||
|
||||
// 开始读取消息
|
||||
doRead();
|
||||
|
||||
// 开始心跳
|
||||
if (impl_->heartbeat_interval > 0) {
|
||||
startHeartbeat();
|
||||
}
|
||||
|
||||
// 通知连接成功
|
||||
if (impl_->connect_callback) {
|
||||
impl_->connect_callback(true);
|
||||
}
|
||||
|
||||
// 检查是否有待发送的消息
|
||||
if (hasMessages()) {
|
||||
doWrite();
|
||||
}
|
||||
}
|
||||
|
||||
// 断开连接
|
||||
void WebSocketClientSSL::disconnect() {
|
||||
impl_->stopping = true;
|
||||
impl_->auto_reconnect = false;
|
||||
|
||||
if (impl_->connected) {
|
||||
asio::post(impl_->ioc, [this]() {
|
||||
beast::error_code ec;
|
||||
impl_->ws.close(websocket::close_code::normal, ec);
|
||||
if (ec && impl_->error_callback) {
|
||||
impl_->error_callback("Error closing WebSocket: " + ec.message());
|
||||
}
|
||||
impl_->connected = false;
|
||||
stopHeartbeat();
|
||||
|
||||
if (impl_->close_callback) {
|
||||
impl_->close_callback();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 发送消息
|
||||
bool WebSocketClientSSL::send(const std::string& message) {
|
||||
return pushMessage(message, false);
|
||||
}
|
||||
|
||||
bool WebSocketClientSSL::sendBinary(const void* data, size_t size) {
|
||||
std::string message(static_cast<const char*>(data), size);
|
||||
return pushMessage(message, true);
|
||||
}
|
||||
|
||||
// 状态检查
|
||||
bool WebSocketClientSSL::isConnected() const {
|
||||
return impl_->connected;
|
||||
}
|
||||
|
||||
std::string WebSocketClientSSL::getHost() const {
|
||||
return impl_->host;
|
||||
}
|
||||
|
||||
std::string WebSocketClientSSL::getPort() const {
|
||||
return impl_->port;
|
||||
}
|
||||
|
||||
std::string WebSocketClientSSL::getPath() const {
|
||||
return impl_->path;
|
||||
}
|
||||
|
||||
// 自动重连和心跳设置
|
||||
void WebSocketClientSSL::setAutoReconnect(bool enable, int interval_ms) {
|
||||
impl_->auto_reconnect = enable;
|
||||
impl_->reconnect_interval = interval_ms;
|
||||
}
|
||||
|
||||
void WebSocketClientSSL::setHeartbeatInterval(int interval_ms) {
|
||||
impl_->heartbeat_interval = interval_ms;
|
||||
if (impl_->connected && interval_ms > 0) {
|
||||
startHeartbeat();
|
||||
}
|
||||
}
|
||||
|
||||
// ============ 私有方法实现 ============
|
||||
|
||||
void WebSocketClientSSL::doRead() {
|
||||
impl_->ws.async_read(
|
||||
impl_->read_buffer,
|
||||
[this](beast::error_code ec, std::size_t bytes_transferred) {
|
||||
onRead(ec, bytes_transferred);
|
||||
});
|
||||
}
|
||||
|
||||
void WebSocketClientSSL::onRead(beast::error_code ec, std::size_t bytes_transferred) {
|
||||
if (ec) {
|
||||
if (ec == websocket::error::closed) {
|
||||
// 正常关闭
|
||||
impl_->connected = false;
|
||||
stopHeartbeat();
|
||||
|
||||
if (impl_->close_callback) {
|
||||
impl_->close_callback();
|
||||
}
|
||||
|
||||
// 自动重连
|
||||
if (impl_->auto_reconnect && !impl_->stopping) {
|
||||
impl_->reconnect_timer.expires_after(std::chrono::milliseconds(impl_->reconnect_interval));
|
||||
impl_->reconnect_timer.async_wait(
|
||||
[this](beast::error_code ec) {
|
||||
if (!ec && !impl_->stopping) {
|
||||
doConnect();
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
handleError("Read failed", ec);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理接收到的消息
|
||||
if (impl_->message_callback) {
|
||||
std::string message = beast::buffers_to_string(impl_->read_buffer.data());
|
||||
impl_->message_callback(message);
|
||||
}
|
||||
|
||||
// 清空缓冲区并继续读取
|
||||
impl_->read_buffer.consume(bytes_transferred);
|
||||
doRead();
|
||||
}
|
||||
|
||||
void WebSocketClientSSL::doWrite() {
|
||||
if (impl_->writing || !hasMessages()) {
|
||||
return;
|
||||
}
|
||||
|
||||
impl_->writing = true;
|
||||
auto message = popMessage();
|
||||
|
||||
impl_->ws.async_write(
|
||||
asio::buffer(message.first),
|
||||
[this, is_binary = message.second](beast::error_code ec, std::size_t bytes_transferred) {
|
||||
impl_->writing = false;
|
||||
onWrite(ec, bytes_transferred);
|
||||
|
||||
// 如果是二进制消息,设置二进制模式
|
||||
if (!ec && is_binary) {
|
||||
impl_->ws.binary(true);
|
||||
} else if (!ec) {
|
||||
impl_->ws.text(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void WebSocketClientSSL::onWrite(beast::error_code ec, std::size_t bytes_transferred) {
|
||||
(void)bytes_transferred; // 消除未使用参数警告
|
||||
|
||||
if (ec) {
|
||||
handleError("Write failed", ec);
|
||||
return;
|
||||
}
|
||||
|
||||
// 继续发送队列中的下一条消息
|
||||
if (hasMessages()) {
|
||||
doWrite();
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocketClientSSL::handleError(const std::string& message, beast::error_code ec, const std::string& extra) {
|
||||
std::string error_msg = message;
|
||||
if (ec) {
|
||||
error_msg += ": " + ec.message();
|
||||
}
|
||||
if (!extra.empty()) {
|
||||
error_msg += " (" + extra + ")";
|
||||
}
|
||||
|
||||
impl_->connecting = false;
|
||||
impl_->connected = false;
|
||||
stopHeartbeat();
|
||||
|
||||
if (impl_->error_callback) {
|
||||
impl_->error_callback(error_msg);
|
||||
}
|
||||
|
||||
if (impl_->connect_callback) {
|
||||
impl_->connect_callback(false);
|
||||
}
|
||||
|
||||
// 自动重连
|
||||
if (impl_->auto_reconnect && !impl_->stopping) {
|
||||
impl_->reconnect_timer.expires_after(std::chrono::milliseconds(impl_->reconnect_interval));
|
||||
impl_->reconnect_timer.async_wait(
|
||||
[this](beast::error_code ec) {
|
||||
if (!ec && !impl_->stopping) {
|
||||
doConnect();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocketClientSSL::startHeartbeat() {
|
||||
if (impl_->heartbeat_interval <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
impl_->heartbeat_timer.expires_after(std::chrono::milliseconds(impl_->heartbeat_interval));
|
||||
impl_->heartbeat_timer.async_wait(
|
||||
[this](beast::error_code ec) {
|
||||
heartbeatTimerCallback(ec);
|
||||
});
|
||||
}
|
||||
|
||||
void WebSocketClientSSL::stopHeartbeat() {
|
||||
beast::error_code ec;
|
||||
impl_->heartbeat_timer.cancel(ec);
|
||||
}
|
||||
|
||||
void WebSocketClientSSL::heartbeatTimerCallback(beast::error_code ec) {
|
||||
if (ec || impl_->stopping || !impl_->connected) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 发送 ping 消息
|
||||
beast::error_code ping_ec;
|
||||
impl_->ws.ping(beast::websocket::ping_data{}, ping_ec);
|
||||
|
||||
if (ping_ec) {
|
||||
handleError("Heartbeat ping failed", ping_ec);
|
||||
return;
|
||||
}
|
||||
|
||||
// 重新设置定时器
|
||||
startHeartbeat();
|
||||
}
|
||||
|
||||
// 线程安全队列操作
|
||||
bool WebSocketClientSSL::pushMessage(const std::string& message, bool is_binary) {
|
||||
if (!impl_->connected || impl_->stopping) {
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(impl_->queue_mutex);
|
||||
impl_->send_queue.push({message, is_binary});
|
||||
}
|
||||
|
||||
impl_->queue_cv.notify_one();
|
||||
|
||||
// 触发写入
|
||||
asio::post(impl_->ioc, [this]() {
|
||||
if (!impl_->writing && hasMessages()) {
|
||||
doWrite();
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::pair<std::string, bool> WebSocketClientSSL::popMessage() {
|
||||
std::lock_guard<std::mutex> lock(impl_->queue_mutex);
|
||||
if (impl_->send_queue.empty()) {
|
||||
return {"", false};
|
||||
}
|
||||
|
||||
auto msg = std::move(impl_->send_queue.front());
|
||||
impl_->send_queue.pop();
|
||||
return {std::move(msg.data), msg.is_binary};
|
||||
}
|
||||
|
||||
bool WebSocketClientSSL::hasMessages() const {
|
||||
std::lock_guard<std::mutex> lock(impl_->queue_mutex);
|
||||
return !impl_->send_queue.empty();
|
||||
}
|
||||
|
||||
} // namespace net
|
||||
} // namespace pmc
|
||||
@@ -0,0 +1,382 @@
|
||||
// WebSocketServer.cpp - WebSocket服务器实现
|
||||
// 基于Boost.Beast和Boost.Asio
|
||||
|
||||
#include "net/WebSocketServer.hpp"
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <mutex>
|
||||
#include <deque>
|
||||
#include <boost/beast/core.hpp>
|
||||
#include <boost/beast/websocket.hpp>
|
||||
#include <boost/asio/strand.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
|
||||
namespace beast = boost::beast;
|
||||
namespace websocket = beast::websocket;
|
||||
namespace asio = boost::asio;
|
||||
using tcp = asio::ip::tcp;
|
||||
|
||||
namespace pmc::net {
|
||||
|
||||
// 内部Session类实现
|
||||
class WebSocketServer::Session : public std::enable_shared_from_this<Session> {
|
||||
public:
|
||||
Session(tcp::socket socket, WebSocketServer* server)
|
||||
: ws_(std::move(socket))
|
||||
, server_(server) {
|
||||
|
||||
// 获取远程地址
|
||||
auto endpoint = ws_.next_layer().socket().remote_endpoint();
|
||||
remoteAddress_ = endpoint.address().to_string() + ":" + std::to_string(endpoint.port());
|
||||
}
|
||||
|
||||
void start() {
|
||||
// 生成客户端ID
|
||||
if (server_) {
|
||||
clientId_ = server_->generateClientId();
|
||||
} else {
|
||||
// 如果server为空,使用默认ID
|
||||
static std::atomic<uint64_t> counter{0};
|
||||
clientId_ = "client_" + std::to_string(++counter);
|
||||
}
|
||||
|
||||
// 异步接受WebSocket握手
|
||||
ws_.async_accept(
|
||||
beast::bind_front_handler(
|
||||
&Session::onAccept,
|
||||
shared_from_this()));
|
||||
}
|
||||
|
||||
void send(const std::string& message) {
|
||||
// 将消息添加到发送队列
|
||||
bool write_in_progress = !sendQueue_.empty();
|
||||
sendQueue_.push_back(message);
|
||||
|
||||
if (!write_in_progress) {
|
||||
// 开始异步写入
|
||||
doWrite();
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& getClientId() const { return clientId_; }
|
||||
const std::string& getRemoteAddress() const { return remoteAddress_; }
|
||||
|
||||
private:
|
||||
websocket::stream<beast::tcp_stream> ws_;
|
||||
beast::flat_buffer buffer_;
|
||||
std::deque<std::string> sendQueue_;
|
||||
WebSocketServer* server_;
|
||||
std::string clientId_;
|
||||
std::string remoteAddress_;
|
||||
|
||||
void onAccept(beast::error_code ec) {
|
||||
if (ec) {
|
||||
std::cerr << "WebSocket accept error: " << ec.message() << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// 添加到服务器客户端列表
|
||||
if (server_) {
|
||||
server_->addClient(clientId_, shared_from_this());
|
||||
|
||||
// 调用连接处理器
|
||||
if (server_->connectHandler_) {
|
||||
server_->connectHandler_(clientId_, remoteAddress_);
|
||||
}
|
||||
}
|
||||
|
||||
// 开始读取消息
|
||||
doRead();
|
||||
}
|
||||
|
||||
void doRead() {
|
||||
// 异步读取消息
|
||||
ws_.async_read(
|
||||
buffer_,
|
||||
beast::bind_front_handler(
|
||||
&Session::onRead,
|
||||
shared_from_this()));
|
||||
}
|
||||
|
||||
void onRead(beast::error_code ec, std::size_t bytes_transferred) {
|
||||
if (ec == websocket::error::closed) {
|
||||
// 正常关闭
|
||||
if (server_) {
|
||||
server_->removeClient(clientId_);
|
||||
if (server_->disconnectHandler_) {
|
||||
server_->disconnectHandler_(clientId_);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (ec) {
|
||||
std::cerr << "WebSocket read error: " << ec.message() << std::endl;
|
||||
if (server_) {
|
||||
server_->removeClient(clientId_);
|
||||
if (server_->disconnectHandler_) {
|
||||
server_->disconnectHandler_(clientId_);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理接收到的消息
|
||||
std::string message = beast::buffers_to_string(buffer_.data());
|
||||
buffer_.consume(bytes_transferred);
|
||||
|
||||
if (server_) {
|
||||
// 查找消息处理器
|
||||
for (const auto& handler : server_->messageHandlers_) {
|
||||
// 这里可以添加路径匹配逻辑
|
||||
handler.second(clientId_, message, [this](const std::string& response) {
|
||||
send(response);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 继续读取
|
||||
doRead();
|
||||
}
|
||||
|
||||
void doWrite() {
|
||||
if (sendQueue_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取队列中的第一条消息
|
||||
auto message = sendQueue_.front();
|
||||
|
||||
// 异步写入消息
|
||||
ws_.async_write(
|
||||
asio::buffer(message),
|
||||
beast::bind_front_handler(
|
||||
&Session::onWrite,
|
||||
shared_from_this()));
|
||||
}
|
||||
|
||||
void onWrite(beast::error_code ec, std::size_t bytes_transferred) {
|
||||
if (ec) {
|
||||
std::cerr << "WebSocket write error: " << ec.message() << std::endl;
|
||||
if (server_) {
|
||||
server_->removeClient(clientId_);
|
||||
if (server_->disconnectHandler_) {
|
||||
server_->disconnectHandler_(clientId_);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 移除已发送的消息
|
||||
sendQueue_.pop_front();
|
||||
|
||||
// 继续发送队列中的下一条消息
|
||||
if (!sendQueue_.empty()) {
|
||||
doWrite();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 内部Listener类实现
|
||||
class WebSocketServer::Listener : public std::enable_shared_from_this<Listener> {
|
||||
public:
|
||||
static std::shared_ptr<Listener> create(asio::io_context& ioc, tcp::endpoint endpoint, WebSocketServer* server) {
|
||||
return std::shared_ptr<Listener>(new Listener(ioc, endpoint, server));
|
||||
}
|
||||
|
||||
void run() {
|
||||
if (!acceptor_.is_open()) {
|
||||
throw std::runtime_error("Acceptor is not open");
|
||||
}
|
||||
|
||||
doAccept();
|
||||
}
|
||||
|
||||
private:
|
||||
Listener(asio::io_context& ioc, tcp::endpoint endpoint, WebSocketServer* server)
|
||||
: ioc_(ioc)
|
||||
, acceptor_(asio::make_strand(ioc))
|
||||
, server_(server) {
|
||||
|
||||
beast::error_code ec;
|
||||
|
||||
// 打开acceptor
|
||||
acceptor_.open(endpoint.protocol(), ec);
|
||||
if (ec) {
|
||||
std::cerr << "Failed to open acceptor: " << ec.message() << std::endl;
|
||||
throw std::runtime_error("Failed to open acceptor: " + ec.message());
|
||||
}
|
||||
|
||||
// 设置地址重用
|
||||
acceptor_.set_option(asio::socket_base::reuse_address(true), ec);
|
||||
if (ec) {
|
||||
std::cerr << "Failed to set reuse address: " << ec.message() << std::endl;
|
||||
throw std::runtime_error("Failed to set reuse address: " + ec.message());
|
||||
}
|
||||
|
||||
// 绑定到端点
|
||||
acceptor_.bind(endpoint, ec);
|
||||
if (ec) {
|
||||
std::cerr << "Failed to bind to endpoint: " << ec.message() << std::endl;
|
||||
throw std::runtime_error("Failed to bind to endpoint: " + ec.message());
|
||||
}
|
||||
|
||||
// 开始监听
|
||||
acceptor_.listen(asio::socket_base::max_listen_connections, ec);
|
||||
if (ec) {
|
||||
std::cerr << "Failed to listen: " << ec.message() << std::endl;
|
||||
throw std::runtime_error("Failed to listen: " + ec.message());
|
||||
}
|
||||
}
|
||||
|
||||
void doAccept() {
|
||||
acceptor_.async_accept(
|
||||
asio::make_strand(ioc_),
|
||||
beast::bind_front_handler(
|
||||
&Listener::onAccept,
|
||||
shared_from_this()));
|
||||
}
|
||||
|
||||
void onAccept(beast::error_code ec, tcp::socket socket) {
|
||||
if (ec) {
|
||||
std::cerr << "Accept error: " << ec.message() << std::endl;
|
||||
} else {
|
||||
// 创建新的Session处理连接
|
||||
std::make_shared<Session>(std::move(socket), server_)->start();
|
||||
}
|
||||
|
||||
// 继续接受新的连接
|
||||
doAccept();
|
||||
}
|
||||
|
||||
asio::io_context& ioc_;
|
||||
tcp::acceptor acceptor_;
|
||||
WebSocketServer* server_;
|
||||
};
|
||||
|
||||
WebSocketServer::WebSocketServer(unsigned short port, unsigned int threads)
|
||||
: port_(port)
|
||||
, threads_(threads)
|
||||
, impl_(std::make_unique<Impl>()) {}
|
||||
|
||||
WebSocketServer::~WebSocketServer() {
|
||||
stop();
|
||||
}
|
||||
|
||||
void WebSocketServer::start() {
|
||||
if (running_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建监听器
|
||||
tcp::endpoint endpoint(tcp::v4(), port_);
|
||||
impl_->listener_ = Listener::create(impl_->ioc_, endpoint, this);
|
||||
|
||||
// 启动监听器
|
||||
impl_->listener_->run();
|
||||
|
||||
// 启动工作线程
|
||||
for (unsigned int i = 0; i < threads_; ++i) {
|
||||
impl_->threads_.emplace_back([this]() {
|
||||
impl_->ioc_.run();
|
||||
});
|
||||
}
|
||||
|
||||
running_ = true;
|
||||
std::cout << "WebSocketServer started on port " << port_ << " with " << threads_ << " threads" << std::endl;
|
||||
}
|
||||
|
||||
void WebSocketServer::stop() {
|
||||
if (!running_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 停止io_context
|
||||
impl_->ioc_.stop();
|
||||
|
||||
// 等待所有线程结束
|
||||
for (auto& thread : impl_->threads_) {
|
||||
if (thread.joinable()) {
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
impl_->threads_.clear();
|
||||
running_ = false;
|
||||
std::cout << "WebSocketServer stopped" << std::endl;
|
||||
}
|
||||
|
||||
void WebSocketServer::onMessage(const std::string& path, WebSocketMessageHandler handler) {
|
||||
messageHandlers_[path] = std::move(handler);
|
||||
}
|
||||
|
||||
void WebSocketServer::onConnect(WebSocketConnectHandler handler) {
|
||||
connectHandler_ = std::move(handler);
|
||||
}
|
||||
|
||||
void WebSocketServer::onDisconnect(WebSocketDisconnectHandler handler) {
|
||||
disconnectHandler_ = std::move(handler);
|
||||
}
|
||||
|
||||
bool WebSocketServer::sendToClient(const std::string& clientId, const std::string& message) {
|
||||
std::lock_guard<std::mutex> lock(clientsMutex_);
|
||||
auto it = clients_.find(clientId);
|
||||
if (it != clients_.end()) {
|
||||
it->second->send(message);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t WebSocketServer::broadcast(const std::string& message) {
|
||||
std::lock_guard<std::mutex> lock(clientsMutex_);
|
||||
size_t count = 0;
|
||||
for (auto& pair : clients_) {
|
||||
pair.second->send(message);
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
void WebSocketServer::disconnectClient(const std::string& clientId) {
|
||||
std::lock_guard<std::mutex> lock(clientsMutex_);
|
||||
auto it = clients_.find(clientId);
|
||||
if (it != clients_.end()) {
|
||||
// 这里应该关闭WebSocket连接
|
||||
// 目前只是从列表中移除
|
||||
clients_.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
size_t WebSocketServer::getClientCount() const {
|
||||
std::lock_guard<std::mutex> lock(clientsMutex_);
|
||||
return clients_.size();
|
||||
}
|
||||
|
||||
std::string WebSocketServer::generateClientId() const {
|
||||
static std::atomic<uint64_t> counter{0};
|
||||
return "client_" + std::to_string(++counter);
|
||||
}
|
||||
|
||||
void WebSocketServer::addClient(const std::string& clientId, std::shared_ptr<Session> session) {
|
||||
std::lock_guard<std::mutex> lock(clientsMutex_);
|
||||
clients_[clientId] = session;
|
||||
}
|
||||
|
||||
void WebSocketServer::removeClient(const std::string& clientId) {
|
||||
std::lock_guard<std::mutex> lock(clientsMutex_);
|
||||
clients_.erase(clientId);
|
||||
}
|
||||
|
||||
std::shared_ptr<WebSocketServer::Session> WebSocketServer::getClient(const std::string& clientId) {
|
||||
std::lock_guard<std::mutex> lock(clientsMutex_);
|
||||
auto it = clients_.find(clientId);
|
||||
if (it != clients_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace pmc::net
|
||||
Reference in new issue
Block a user