# Boost.Beast Boost.Beast 是建立在 Boost.Asio 之上的 HTTP 和 WebSocket 库,提供了现代 C++ 风格的网络编程接口。 ## 概述 ### 特性 - **HTTP/1.1** 完整支持(请求、响应、流式处理) - **WebSocket** 完整支持(客户端和服务器) - **SSL/TLS** 支持(通过 Boost.Asio SSL) - **零拷贝** 设计,高性能 - **协程支持** (C++20 coroutines) ### 核心组件 | 组件 | 说明 | |------|------| | `http::request` | HTTP 请求消息 | | `http::response` | HTTP 响应消息 | | `websocket::stream` | WebSocket 流 | | `flat_buffer` | 动态缓冲区 | | `tcp_stream` | 带超时的 TCP 流 | ## HTTP 编程 ### HTTP 客户端 - 同步 ```cpp #include #include #include #include #include #include #include namespace beast = boost::beast; namespace http = beast::http; namespace net = boost::asio; using tcp = net::ip::tcp; std::string http_get(const std::string& host, const std::string& target) { net::io_context ioc; tcp::resolver resolver(ioc); beast::tcp_stream stream(ioc); // 解析域名 auto const results = resolver.resolve(host, "80"); stream.connect(results); // 设置超时 stream.expires_after(std::chrono::seconds(30)); // 构建请求 http::request req{http::verb::get, target, 11}; req.set(http::field::host, host); req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); // 发送请求 http::write(stream, req); // 接收响应 beast::flat_buffer buffer; http::response res; http::read(stream, buffer, res); // 关闭连接 beast::error_code ec; stream.socket().shutdown(tcp::socket::shutdown_both, ec); return res.body(); } ``` ### HTTP 客户端 - POST 请求 ```cpp std::string http_post(const std::string& host, const std::string& target, const std::string& body, const std::string& content_type = "application/json") { net::io_context ioc; tcp::resolver resolver(ioc); beast::tcp_stream stream(ioc); auto const results = resolver.resolve(host, "80"); stream.connect(results); // 构建 POST 请求 http::request req{http::verb::post, target, 11}; req.set(http::field::host, host); req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); req.set(http::field::content_type, content_type); req.body() = body; req.prepare_payload(); // 设置 Content-Length http::write(stream, req); beast::flat_buffer buffer; http::response res; http::read(stream, buffer, res); beast::error_code ec; stream.socket().shutdown(tcp::socket::shutdown_both, ec); return res.body(); } ``` ### HTTP 服务器 ```cpp #include #include #include #include #include namespace beast = boost::beast; namespace http = beast::http; namespace net = boost::asio; using tcp = net::ip::tcp; // 处理 HTTP 请求 template http::response handle_request(http::request>&& req) { // 处理不同路径 if (req.target() == "/") { http::response res{http::status::ok, req.version()}; res.set(http::field::server, BOOST_BEAST_VERSION_STRING); res.set(http::field::content_type, "text/html"); res.keep_alive(req.keep_alive()); res.body() = "

Hello, Beast!

"; res.prepare_payload(); return res; } if (req.target() == "/api/hello") { http::response res{http::status::ok, req.version()}; res.set(http::field::server, BOOST_BEAST_VERSION_STRING); res.set(http::field::content_type, "application/json"); res.keep_alive(req.keep_alive()); res.body() = R"({"message": "Hello, World!"})"; res.prepare_payload(); return res; } // 404 http::response res{http::status::not_found, req.version()}; res.set(http::field::server, BOOST_BEAST_VERSION_STRING); res.set(http::field::content_type, "text/plain"); res.keep_alive(req.keep_alive()); res.body() = "Not Found"; res.prepare_payload(); return res; } // HTTP 会话 class HttpSession : public std::enable_shared_from_this { beast::tcp_stream stream_; beast::flat_buffer buffer_; http::request req_; public: HttpSession(tcp::socket&& socket) : stream_(std::move(socket)) {} void run() { do_read(); } private: void do_read() { req_ = {}; stream_.expires_after(std::chrono::seconds(30)); http::async_read(stream_, buffer_, req_, beast::bind_front_handler( &HttpSession::on_read, shared_from_this())); } void on_read(beast::error_code ec, std::size_t) { if (ec == http::error::end_of_stream) return do_close(); if (ec) return; auto response = handle_request(std::move(req_)); auto sp = std::make_shared>(std::move(response)); http::async_write(stream_, *sp, [self = shared_from_this(), sp](beast::error_code ec, std::size_t) { self->on_write(ec, sp->need_eof()); }); } void on_write(beast::error_code ec, bool close) { if (ec) return; if (close) return do_close(); do_read(); } void do_close() { beast::error_code ec; stream_.socket().shutdown(tcp::socket::shutdown_send, ec); } }; ``` ## WebSocket 编程 ### WebSocket 客户端 ```cpp #include #include #include #include #include #include namespace beast = boost::beast; namespace websocket = beast::websocket; namespace net = boost::asio; using tcp = net::ip::tcp; void websocket_client(const std::string& host, const std::string& port) { net::io_context ioc; tcp::resolver resolver(ioc); websocket::stream ws(ioc); // 连接 auto const results = resolver.resolve(host, port); auto ep = net::connect(ws.next_layer(), results); // WebSocket 握手 std::string host_port = host + ":" + std::to_string(ep.port()); ws.handshake(host_port, "/"); // 发送消息 ws.write(net::buffer(std::string("Hello, WebSocket!"))); // 接收消息 beast::flat_buffer buffer; ws.read(buffer); std::cout << beast::make_printable(buffer.data()) << std::endl; // 关闭连接 ws.close(websocket::close_code::normal); } ``` ### WebSocket 服务器 ```cpp #include #include #include #include #include namespace beast = boost::beast; namespace websocket = beast::websocket; namespace net = boost::asio; using tcp = net::ip::tcp; class WebSocketSession : public std::enable_shared_from_this { websocket::stream ws_; beast::flat_buffer buffer_; public: WebSocketSession(tcp::socket&& socket) : ws_(std::move(socket)) {} void run() { // 设置选项 ws_.set_option(websocket::stream_base::timeout::suggested( beast::role_type::server)); ws_.set_option(websocket::stream_base::decorator( [](websocket::response_type& res) { res.set(http::field::server, "Beast WebSocket Server"); })); // 接受 WebSocket 握手 ws_.async_accept( beast::bind_front_handler( &WebSocketSession::on_accept, shared_from_this())); } private: void on_accept(beast::error_code ec) { if (ec) return; do_read(); } void do_read() { ws_.async_read( buffer_, beast::bind_front_handler( &WebSocketSession::on_read, shared_from_this())); } void on_read(beast::error_code ec, std::size_t bytes_transferred) { if (ec == websocket::error::closed) return; if (ec) return; // Echo 回写 ws_.text(ws_.got_text()); ws_.async_write( buffer_.data(), beast::bind_front_handler( &WebSocketSession::on_write, shared_from_this())); } void on_write(beast::error_code ec, std::size_t) { if (ec) return; buffer_.consume(buffer_.size()); do_read(); } }; ``` ## 实战:多人聊天服务器 一个完整的 HTTP/WebSocket 多人聊天服务器示例,支持: - HTTP 静态文件服务 - WebSocket 实时消息推送 - 多用户同时在线聊天 - 用户加入/离开通知 ### 项目结构 ``` chat_server/ ├── CMakeLists.txt ├── src/ │ ├── main.cpp │ ├── chat_server.hpp │ ├── websocket_session.hpp │ ├── http_session.hpp │ └── shared_state.hpp └── www/ └── index.html ``` ### CMakeLists.txt ```cmake cmake_minimum_required(VERSION 3.16) project(chat_server) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(Boost 1.75 REQUIRED COMPONENTS system) find_package(Threads REQUIRED) add_executable(chat_server src/main.cpp ) target_link_libraries(chat_server Boost::system Threads::Threads ) target_include_directories(chat_server PRIVATE src) ``` ### shared_state.hpp - 共享状态管理 ```cpp #ifndef SHARED_STATE_HPP #define SHARED_STATE_HPP #include #include #include #include class websocket_session; class shared_state { std::string doc_root_; std::mutex mutex_; std::unordered_set sessions_; public: explicit shared_state(std::string doc_root) : doc_root_(std::move(doc_root)) {} const std::string& doc_root() const noexcept { return doc_root_; } void join(websocket_session* session) { std::lock_guard lock(mutex_); sessions_.insert(session); } void leave(websocket_session* session) { std::lock_guard lock(mutex_); sessions_.erase(session); } void broadcast(const std::string& message) { std::lock_guard lock(mutex_); for (auto* session : sessions_) { session->send(message); } } size_t session_count() const { std::lock_guard lock(const_cast(mutex_)); return sessions_.size(); } }; #endif ``` ### websocket_session.hpp - WebSocket 会话 ```cpp #ifndef WEBSOCKET_SESSION_HPP #define WEBSOCKET_SESSION_HPP #include #include #include #include #include #include #include "shared_state.hpp" namespace beast = boost::beast; namespace websocket = beast::websocket; namespace net = boost::asio; using tcp = net::ip::tcp; class websocket_session : public std::enable_shared_from_this { websocket::stream ws_; beast::flat_buffer buffer_; std::shared_ptr state_; std::queue queue_; std::string username_; void fail(beast::error_code ec, char const* what) { if (ec == net::error::operation_aborted || ec == websocket::error::closed) return; std::cerr << what << ": " << ec.message() << "\n"; } void on_accept(beast::error_code ec) { if (ec) return fail(ec, "accept"); state_->join(this); do_read(); } void do_read() { ws_.async_read( buffer_, beast::bind_front_handler( &websocket_session::on_read, shared_from_this())); } void on_read(beast::error_code ec, std::size_t) { if (ec == websocket::error::closed) { state_->leave(this); if (!username_.empty()) { state_->broadcast("{\"type\":\"leave\",\"user\":\"" + username_ + "\"}"); } return; } if (ec) return fail(ec, "read"); std::string msg = beast::buffers_to_string(buffer_.data()); buffer_.consume(buffer_.size()); if (username_.empty()) { username_ = msg; state_->broadcast("{\"type\":\"join\",\"user\":\"" + username_ + "\"}"); } else { std::string broadcast_msg = "{\"type\":\"message\",\"user\":\"" + username_ + "\",\"text\":\"" + msg + "\"}"; state_->broadcast(broadcast_msg); } do_read(); } void on_send(std::shared_ptr const& ss) { queue_.push(*ss); if (queue_.size() > 1) return; ws_.async_write( net::buffer(queue_.front()), beast::bind_front_handler( &websocket_session::on_write, shared_from_this())); } void on_write(beast::error_code ec, std::size_t) { if (ec) return fail(ec, "write"); queue_.pop(); if (!queue_.empty()) { ws_.async_write( net::buffer(queue_.front()), beast::bind_front_handler( &websocket_session::on_write, shared_from_this())); } } public: websocket_session(tcp::socket&& socket, std::shared_ptr const& state) : ws_(std::move(socket)), state_(state) {} ~websocket_session() { state_->leave(this); } void run() { ws_.set_option(websocket::stream_base::timeout::suggested( beast::role_type::server)); ws_.set_option(websocket::stream_base::decorator( [](websocket::response_type& res) { res.set(beast::http::field::server, std::string(BOOST_BEAST_VERSION_STRING) + " chat-server"); })); ws_.async_accept( beast::bind_front_handler( &websocket_session::on_accept, shared_from_this())); } void send(std::string message) { auto ss = std::make_shared(std::move(message)); net::post( ws_.get_executor(), beast::bind_front_handler( &websocket_session::on_send, shared_from_this(), ss)); } }; #endif ``` ### http_session.hpp - HTTP 会话 ```cpp #ifndef HTTP_SESSION_HPP #define HTTP_SESSION_HPP #include #include #include #include #include #include #include #include #include "shared_state.hpp" #include "websocket_session.hpp" namespace beast = boost::beast; namespace http = beast::http; namespace websocket = beast::websocket; namespace net = boost::asio; using tcp = net::ip::tcp; beast::string_view mime_type(beast::string_view path) { using beast::iequals; auto const ext = [&path] { auto const pos = path.rfind("."); if (pos == beast::string_view::npos) return beast::string_view{}; return path.substr(pos); }(); if (iequals(ext, ".htm")) return "text/html"; if (iequals(ext, ".html")) return "text/html"; if (iequals(ext, ".css")) return "text/css"; if (iequals(ext, ".js")) return "application/javascript"; if (iequals(ext, ".json")) return "application/json"; if (iequals(ext, ".png")) return "image/png"; if (iequals(ext, ".jpg")) return "image/jpeg"; return "application/octet-stream"; } std::string read_file(const std::string& path) { std::ifstream file(path, std::ios::binary); if (!file) return ""; std::ostringstream ss; ss << file.rdbuf(); return ss.str(); } class http_session : public std::enable_shared_from_this { beast::tcp_stream stream_; beast::flat_buffer buffer_; std::shared_ptr state_; http::request req_; void fail(beast::error_code ec, char const* what) { if (ec == net::error::operation_aborted) return; std::cerr << what << ": " << ec.message() << "\n"; } void do_read() { req_ = {}; stream_.expires_after(std::chrono::seconds(30)); http::async_read(stream_, buffer_, req_, beast::bind_front_handler(&http_session::on_read, shared_from_this())); } void on_read(beast::error_code ec, std::size_t) { if (ec == http::error::end_of_stream) return do_close(); if (ec) return fail(ec, "read"); if (websocket::is_upgrade(req_)) { std::make_shared( stream_.release_socket(), state_)->run(); return; } handle_request(); } void handle_request() { if (req_.method() != http::verb::get) { send_response(http::status::bad_request, "Unknown HTTP method"); return; } std::string path = state_->doc_root(); std::string target = std::string(req_.target()); if (target == "/") target = "/index.html"; path += target; std::string body = read_file(path); if (body.empty()) { send_response(http::status::not_found, "File not found"); return; } http::response res{http::status::ok, req_.version()}; res.set(http::field::server, BOOST_BEAST_VERSION_STRING); res.set(http::field::content_type, mime_type(target)); res.keep_alive(req_.keep_alive()); res.body() = std::move(body); res.prepare_payload(); send_response(std::move(res)); } void send_response(http::status status, beast::string_view message) { http::response res{status, req_.version()}; res.set(http::field::server, BOOST_BEAST_VERSION_STRING); res.set(http::field::content_type, "text/plain"); res.keep_alive(req_.keep_alive()); res.body() = std::string(message); res.prepare_payload(); send_response(std::move(res)); } void send_response(http::response&& res) { auto sp = std::make_shared>(std::move(res)); http::async_write(stream_, *sp, [self = shared_from_this(), sp](beast::error_code ec, std::size_t) { self->on_write(ec, sp->need_eof()); }); } void on_write(beast::error_code ec, bool close) { if (ec) return fail(ec, "write"); if (close) return do_close(); do_read(); } void do_close() { beast::error_code ec; stream_.socket().shutdown(tcp::socket::shutdown_send, ec); } public: http_session(tcp::socket&& socket, std::shared_ptr const& state) : stream_(std::move(socket)), state_(state) {} void run() { do_read(); } }; #endif ``` ### main.cpp - 主程序 ```cpp #include #include #include #include #include #include "shared_state.hpp" #include "http_session.hpp" namespace beast = boost::beast; namespace net = boost::asio; using tcp = net::ip::tcp; class listener : public std::enable_shared_from_this { net::io_context& ioc_; tcp::acceptor acceptor_; std::shared_ptr state_; void do_accept() { acceptor_.async_accept( net::make_strand(ioc_), beast::bind_front_handler(&listener::on_accept, shared_from_this())); } void on_accept(beast::error_code ec, tcp::socket socket) { if (!ec) { std::make_shared(std::move(socket), state_)->run(); } do_accept(); } public: listener(net::io_context& ioc, tcp::endpoint endpoint, std::shared_ptr const& state) : ioc_(ioc), acceptor_(net::make_strand(ioc)), state_(state) { beast::error_code ec; acceptor_.open(endpoint.protocol(), ec); acceptor_.set_option(net::socket_base::reuse_address(true), ec); acceptor_.bind(endpoint, ec); acceptor_.listen(net::socket_base::max_listen_connections, ec); } void run() { do_accept(); } }; int main(int argc, char* argv[]) { auto const address = net::ip::make_address("0.0.0.0"); auto const port = static_cast(argc > 1 ? std::atoi(argv[1]) : 8080); auto const doc_root = std::string(argc > 2 ? argv[2] : "./www"); auto const threads = std::max(1, argc > 3 ? std::atoi(argv[3]) : 4); std::cout << "Chat server starting on port " << port << "...\n"; net::io_context ioc{threads}; auto state = std::make_shared(doc_root); std::make_shared(ioc, tcp::endpoint{address, port}, state)->run(); net::signal_set signals(ioc, SIGINT, SIGTERM); signals.async_wait([&ioc](beast::error_code const&, int) { std::cout << "\nShutting down...\n"; ioc.stop(); }); std::vector v; v.reserve(threads - 1); for (auto i = threads - 1; i > 0; --i) { v.emplace_back([&ioc] { ioc.run(); }); } ioc.run(); for (auto& t : v) t.join(); return 0; } ``` ### www/index.html - 聊天页面 ```html WebSocket Chat

WebSocket Chat

Real-time multi-user chat powered by Boost.Beast

Enter your username to join:

``` ### 编译和运行 ```bash # 编译 mkdir build && cd build cmake .. make # 运行 ./chat_server 8080 ../www 4 # 访问 http://localhost:8080 ``` ### 架构说明 ``` ┌─────────────────────────────────────┐ │ listener │ │ (接受 TCP 连接) │ └──────────────┬──────────────────────┘ │ ┌──────────────▼──────────────────────┐ │ http_session │ │ (处理 HTTP 请求,检测 WS 升级) │ └──────────────┬──────────────────────┘ │ ┌──────────┼──────────┐ │ │ │ ┌───▼───┐ ┌───▼───┐ ┌────▼────┐ │ WS │ │Static │ │ 404 │ │Session│ │Files │ │Response │ └───┬───┘ └───────┘ └─────────┘ │ ┌───▼───────────────┐ │ shared_state │ │ (广播消息到所有 │ │ WebSocket 连接) │ └───────────────────┘ ``` ```{tip} **Beast 使用建议:** 1. 使用 `tcp_stream` 而非原始 socket(提供超时支持) 2. 正确管理对象生命周期(使用 `shared_from_this()`) 3. WebSocket 握手前验证升级请求 4. 生产环境添加 SSL/TLS 支持 ``` ## 相关文档 - [Boost.Asio](boost_asio.md) - Asio 异步 I/O 库 - [Boost 库概览](boost.md) - 回到 Boost 主页