当前位置: 首页 > news >正文

Boost.Asio学习(7):Boost.Beast实现简易http服务器

namespace beast = boost::beast;

beast::flat_buffer

  • 是一个用于 Boost.AsioBoost.Beast 网络读写的缓冲区实现。

  • 专为 一次性顺序读取 / 消费 场景设计,比 std::stringstd::vector 高效,因为它是扁平内存结构(contiguous memory),避免了多段链表缓冲带来的额外开销。

  • Beast 协议解析器(HTTP、WebSocket)通常要求你提供这样一个缓冲区来存放网络数据。

常用 API

#include <boost/beast/core/flat_buffer.hpp>

构造与容量管理

flat_buffer::flat_buffer(std::size_t limit = (std::numeric_limits<std::size_t>::max)());
// limit: 最大缓冲区字节数,超出会抛异常

数据访问

std::size_t flat_buffer::size() const noexcept;
// 返回缓冲区中已存放的字节数boost::asio::const_buffer flat_buffer::data() const noexcept;
// 返回指向已存数据的只读视图(可传给 asio::buffer)void flat_buffer::consume(std::size_t n);
// 从缓冲区头部丢弃 n 字节

常见用法(HTTP 读请求)

beast::flat_buffer buffer;
http::request<http::string_body> req;
http::read(socket, buffer, req);

这里:

  • buffer 存放原始网络字节流

  • req 存放解析后的 HTTP 请求

  • read() 会先把 socket 数据读进 buffer,然后解析出 req

http::request<> / http::response<>

作用

  • 这两个是 Beast 提供的 HTTP 报文数据结构模板。

  • 模板参数是 HTTP 消息的 body 类型,决定了 HTTP 报文 body 在内存中如何存储

#include <boost/beast/http.hpp>
// 接收客户端请求时常用
http::request<http::string_body> req;// 构造响应时常用
http::response<http::string_body> res;
//原型
template<bool isRequest, class Body, class Fields = fields>
class message;template<class Body, class Fields = fields>
using request = message<true, Body, Fields>;template<class Body, class Fields = fields>
using response = message<false, Body, Fields>;

request常用成员函数

http::verb method() const noexcept;  
void method(http::verb v) noexcept;  
// 获取 / 设置 HTTP 方法 (GET, POST, etc.)string_view target() const noexcept;  
void target(string_view t);  
// 获取 / 设置请求路径 (例如 "/index.html")unsigned version() const noexcept;
void version(unsigned v) noexcept;
// 获取 / 设置 HTTP 版本 (10, 11, etc.)Body::value_type& body();
Body::value_type const& body() const;
// 获取请求体(类型由 Body 决定)

http::verb是什么?

enum class verb : unsigned char {delete_      = 0,  // DELETEget          = 1,  // GEThead         = 2,  // HEADpost         = 3,  // POSTput          = 4,  // PUTconnect      = 5,  // CONNECToptions      = 6,  // OPTIONStrace        = 7,  // TRACEcopy         = 8,  // WebDAV COPYlock         = 9,  // WebDAV LOCKmkcol        = 10, // WebDAV MKCOLmove         = 11, // WebDAV MOVEpropfind     = 12, // WebDAV PROPFINDproppatch    = 13, // WebDAV PROPPATCHsearch       = 14, // SEARCHunlock       = 15, // WebDAV UNLOCKbind         = 16, // BINDrebind       = 17, // REBINDunbind       = 18, // UNBINDacl          = 19, // ACLreport       = 20, // REPORTmkactivity   = 21, // MKACTIVITYcheckout     = 22, // CHECKOUTmerge        = 23, // MERGEmsearch      = 24, // M-SEARCHnotify       = 25, // NOTIFYsubscribe    = 26, // SUBSCRIBEunsubscribe  = 27, // UNSUBSCRIBEpatch        = 28, // PATCHpurge        = 29, // PURGEmkcalendar   = 30, // MKCALENDARlink         = 31, // LINKunlink       = 32, // UNLINKunknown      = 255 // 未知
};

最常用的其实就是:

get, post, put, delete_, head, options, patch

response常用成员函数

unsigned result_int() const noexcept;
http::status result() const noexcept;
void result(http::status s) noexcept;
// 获取 / 设置 HTTP 状态码Body::value_type& body();
Body::value_type const& body() const;
// 获取响应体

http::status是什么,一部分如下

enum class status : unsigned short {// 1xx Informationalcontinue_                    = 100, // Continueswitching_protocols          = 101, // Switching Protocolsprocessing                   = 102, // WebDAV Processing// 2xx Successok                           = 200, // OKcreated                      = 201, // Createdaccepted                     = 202, // Acceptedno_content                   = 204, // No Contentpartial_content              = 206, // Partial Content// 3xx Redirectionmultiple_choices             = 300,moved_permanently            = 301,found                        = 302, // Moved Temporarilysee_other                    = 303,not_modified                 = 304,temporary_redirect           = 307,permanent_redirect           = 308,// 4xx Client Errorbad_request                  = 400,unauthorized                 = 401,forbidden                    = 403,not_found                    = 404,method_not_allowed           = 405,request_timeout              = 408,conflict                     = 409,gone                         = 410,length_required              = 411,payload_too_large            = 413,uri_too_long                 = 414,unsupported_media_type       = 415,expectation_failed           = 417,too_many_requests            = 429,// 5xx Server Errorinternal_server_error        = 500,not_implemented              = 501,bad_gateway                  = 502,service_unavailable          = 503,gateway_timeout              = 504,http_version_not_supported   = 505
};

开发时最常用的状态码是:

ok, bad_request, not_found, internal_server_error

set函数

set 用于设置 HTTP 头字段
它是 message 基类提供的,requestresponse 都能用。

函数原型(简化版):

template<class Field, class String>
basic_fields& set(Field name, String&& value);
  • Field name → 一般传 http::field 枚举值(也可传字符串,但推荐枚举)

  • String&& value → 可以是 std::stringconst char*

  • 返回值是 basic_fields&,所以你可以链式调用。

示例:

namespace http = boost::beast::http;
http::response<http::string_body> res;res.result(http::status::ok);             // 设置状态码
res.version(11);                          // HTTP/1.1
res.set(http::field::server, "MyServer/1.0");
res.set(http::field::content_type, "text/html");
res.set(http::field::connection, "close");
res.body() = "<html><body>Hello</body></html>";
res.prepare_payload();                    // 自动设置 Content-Length

http::field 常用枚举

enum class field : unsigned short {// 常见通用字段server,             // Serverhost,               // Hostuser_agent,         // User-Agentcontent_type,       // Content-Typecontent_length,     // Content-Lengthconnection,         // Connectionaccept,             // Acceptaccept_encoding,    // Accept-Encodingaccept_language,    // Accept-Languagecache_control,      // Cache-Controldate,               // Datelocation,           // Location (重定向时用)upgrade,            // Upgrade (WebSocket)transfer_encoding,  // Transfer-Encodingauthorization,      // Authorization (认证)cookie,             // Cookieset_cookie,         // Set-Cookiereferer,            // Refererrange,              // Range (部分请求)origin,             // Origin (跨域)access_control_allow_origin, // CORS: Access-Control-Allow-Originaccess_control_allow_methods, // CORS: Access-Control-Allow-Methodsaccess_control_allow_headers, // CORS: Access-Control-Allow-Headers// 还有很多 WebDAV、HTTP2/3 专用的,就不列全了
};

注意事项

  1. 重复字段

    • 如果用 set,会覆盖同名字段;

    • 如果用 insert,会追加同名字段(比如 Set-Cookie 可以多次)。

  2. 大小写

    • field 枚举会自动生成标准大小写,比如 content_typeContent-Type

  3. prepare_payload()

    • 建议在设置好 body 后调用,它会帮你计算并设置 Content-Length(除非用分块传输)。

常见字段的值规则(重点)

头字段(http::field 枚举)典型值示例规则/注意事项
server"MyServer/1.0"服务端软件标识,纯文本
content_type"text/html" / "application/json"必须是标准 MIME 类型
content_length"1234"必须是整数(字节数),要与 body 实际长度一致,否则客户端可能卡死或丢数据
connection"close" / "keep-alive"只能是 HTTP 规定的值
location"http://example.com/path"必须是完整或相对 URL,用于重定向
cache_control"no-cache" / "max-age=3600"按 RFC7234 格式写
set_cookie"name=value; Path=/; HttpOnly"Cookie 语法很严格,不能乱写
access_control_allow_origin"*" / "https://example.com"CORS 允许的域名
transfer_encoding"chunked"表示分块传输,不能和 Content-Length 同时存在
date"Fri, 08 Aug 2025 12:00:00 GMT"必须是 GMT RFC1123 格式

HTTP Body 类型

Boost.Beast 预定义了多种 HTTP Body 类型(模板参数用到):

Body 类型存储方式适用场景
http::string_bodystd::string文本、JSON、小型数据
http::vector_body<std::uint8_t>std::vector二进制数据(图片、小文件)
http::file_body文件流大文件传输(零拷贝)
http::dynamic_bodyboost::beast::multi_buffer流式传输,不预先知道长度
http::empty_body无 bodyGET 响应或 204/304 等无内容响应

http::dynamic_body 特点:

  • 内部使用 beast::multi_buffer(链式内存块)

  • 特别适合 未知大小的数据流(例如 HTTP Chunked Transfer)

  • 常见于代理服务器、WebSocket HTTP handshake

示例:动态 body 的 HTTP 请求

http::request<http::dynamic_body> req;
http::read(socket, buffer, req);
auto& body = req.body(); // multi_buffer

beast::ostream

在 Boost.Beast 里,
beast::ostream 往往比直接给 body 赋值更安全、更方便,尤其是在你用 http::dynamic_body 这种可变缓冲区类型的时候。

1. 类型安全,避免直接操作内部缓冲

http::dynamic_bodybody() 返回的是一个 boost::beast::multi_buffer 对象。
如果你直接赋值,必须自己构造缓冲区、拷贝数据,还得注意内存扩容。

// 不推荐:自己构造 multi_buffer 再赋值
auto& buf = response.body();
net::buffer_copy(buf.prepare(size), net::buffer(data, size));
buf.commit(size);

这种写法啰嗦,而且容易忘记调用 commit 或者计算长度。

2. beast::ostream 自动扩容 + 类似 iostream 的接口

beast::ostream 会包装 multi_buffer,你就像写文件一样用 << 写数据,Beast 会自动分配内存并追加内容:

beast::ostream(response.body()) << "Hello, world\n";

这样写:

  • 自动扩容

  • 不用关心 buffer 内部结构

  • std::cout 一样直观

3. 和 HTTP API 配合好

Boost.Beast 在发送前会自动读取 body 的大小(dynamic_body 会自动提供 .size()),
而用 beast::ostream 写入时,这个大小会自动更新,不会出现 Content-Length 不匹配 的问题。

如果你直接给 body 原始赋值,很可能需要自己去算并手动 response.content_length(...)

 结论

  • 如果是 dynamic_body → 用 beast::ostream 最省事、安全

  • 如果是 string_body → 可以直接 response.body() = "xxx";,因为它本质上就是个 std::string

  • 如果是大文件传输 → 会用 file_bodybuffer_body,那就不能用 ostream

response如何设置?

在 Boost.Beast 里,一个典型的 设置 http::response 并发送 的流程,其实就是围绕 几个关键 API 顺序调用
可以按“必需 / 常用 / 可选”三个层次整理一下

1. 必需步骤(核心三步)

这三步是 HTTP 协议层面必须有的,否则对方可能无法解析你的响应。

步骤函数/API作用
① 设置状态码response.result(http::status::ok)告诉客户端这是成功(200 OK)还是错误(404 Not Found 等)
② 设置版本response.version(request.version())一般直接用客户端的版本号,保证 HTTP/1.1 和 HTTP/1.0 兼容
③ 设置 bodybeast::ostream(response.body()) << "..."response.body() = ...写响应内容

2. 常用步骤(几乎每次都会用)

这些是大多数 HTTP 服务端都会加的,方便客户端解析或优化传输。

步骤函数/API作用
设置 Content-Typeresponse.set(http::field::content_type, "text/html")让浏览器/客户端知道返回的是什么格式(HTML、JSON、文本等)
设置 Server 头response.set(http::field::server, "Beast")告诉客户端这是哪个服务器返回的(调试 / 统计用)
设置 Keep-Aliveresponse.keep_alive(request.keep_alive())决定是否复用 TCP 连接,减少重复握手开销
设置 Content-Lengthresponse.content_length(response.body().size())HTTP/1.1 可以自动分块,但静态内容最好显式告诉长度,提升性能

⚠ 注意:如果用 dynamic_body + beast::ostream 写入,body().size() 会自动更新,但你仍然需要调用 content_length(...) 来让 HTTP 知道长度(除非走 chunked encoding)。

3. 可选步骤(视业务需要)

这些是根据功能需求添加的,不是协议必需。

用途示例 API说明
设置重定向response.result(http::status::found); response.set(http::field::location, "/newpath");告诉客户端去别的 URL
添加自定义头response.set("X-My-Header", "value")自定义协议 / 版本号 / 签名等
设置 Cookieresponse.set(http::field::set_cookie, "id=123; Path=/; HttpOnly")登录状态管理
压缩传输response.set(http::field::content_encoding, "gzip")前提是 body 已 gzip 压缩

4. 最后一步:发送

Boost.Beast 一般用异步 API 发送:

http::async_write(socket, response, handler);

发送完成后:

  • 如果是短连接:socket.shutdown(tcp::socket::shutdown_send, ec);

  • 如果是长连接:可以继续 async_read 等待下一个请求

一个标准模板

response.version(request.version());
response.keep_alive(request.keep_alive());
response.result(http::status::ok);
response.set(http::field::server, "Beast");
response.set(http::field::content_type, "text/html");
beast::ostream(response.body()) << "<html><body>Hello</body></html>";
response.content_length(response.body().size());
http::async_write(socket, response, handler);

在写服务端时,可以把这套设置流程封装成一个小函数,比如 make_response(request, status, type, body_string),这样就不会每次都忘记 versionkeep_alivecontent_length 这些关键步骤。

prepare_payload

template<bool isRequest, class Body, class Fields>
void
message<isRequest, Body, Fields>::
prepare_payload(std::false_type);

作用

  1. 自动计算 Content-Length

    • 如果消息体可以一次性知道长度(比如 string_bodydynamic_body 已经写入完成),会自动设置 Content-Length 字段。

  2. 设置 Transfer-Encoding

    • 如果无法提前知道长度(比如 chunked_body),会自动添加 "Transfer-Encoding: chunked"

  3. 保持已有字段

    • 如果你已经手动设置了 Content-LengthTransfer-Encoding,它不会覆盖。

常见用法

在你构造好响应后、发送前调用:

http::response<http::string_body> res{http::status::ok, req.version()};
res.set(http::field::server, "TestServer");
res.set(http::field::content_type, "text/plain");
res.body() = "Hello, World!";
res.prepare_payload();  // 自动设置 Content-Length
使用注意
  • 调用时机:必须在你已经完全构造好 body() 之后调用,否则长度不对。

  • 如果你使用了 分块传输(chunked encoding),就不需要调用 prepare_payload,因为长度是运行时逐块写的。

  • 在 Beast 的例子里,prepare_payload 常用于 同步或异步写 之前的最后一步,确保 HTTP 头是合法的。

hppt异步读写

async_read

常用函数原型(简化)

// 读“完整一条”HTTP 消息(含 header+body)
template<class AsyncReadStream,              // 要满足 AsyncReadStream 概念(如 tcp::socket)class DynamicBuffer,                // 要满足 DynamicBuffer 概念(如 beast::flat_buffer)bool isRequest, class Body, class Allocator,//一般是requestclass ReadHandler                   // void(error_code, std::size_t)
>
void async_read(AsyncReadStream& stream,DynamicBuffer& buffer,boost::beast::http::message<isRequest, Body,boost::beast::http::basic_fields<Allocator>>& msg,ReadHandler&& handler);// 使用 parser 分阶段控制(需要你自带 parser)
template<class AsyncReadStream,class DynamicBuffer,class Parser,                       // http::request_parser<Body> 或 response_parser<Body>class ReadHandler
>
void async_read(AsyncReadStream& stream,DynamicBuffer& buffer,Parser& parser,ReadHandler&& handler);

参数与行为

  • AsyncReadStream& stream
    你的 I/O 流对象:如 tcp::socketssl::stream<tcp::socket>。必须有 async_read_someget_executor()

  • DynamicBuffer& buffer
    读缓冲。典型用 beast::flat_buffer必须与消息对象同寿命(至少覆盖到回调结束)。函数会从 stream 读数据 → 填充到 buffer → 解析到 msg

    注意:解析只会“消费”组成当前消息的字节,如果收到了一部分“下一条请求”(HTTP pipelining),剩余字节会留在 buffer 里供你下一次 async_read 继续用。

  • message<...>& msgParser& parser

    • msg 版本:一次性读取并解析完整 HTTP 报文。

    • parser 版本:你可更细粒度控制(例如:限制 header 大小、限制 body 大小、提前终止等)。

  • ReadHandler&& handler
    回调签名:void(boost::system::error_code ec, std::size_t bytes_transferred) (boost::beast::error_code = boost::system::error_code;)

    • ec 为 0 表示成功拿到一整条消息;

    • 典型错误:http::error::end_of_stream(对端正常关闭),boost::asio::error::operation_aborted(你取消/超时),其它网络错误等。

    • 回调stream.get_executor() 所在执行环境里调用(若你把 socket 绑定到 strand,就在同一串行上下文里)。

要点 / 易错点

  • buffermsg 必须在回调返回前存活(常见做法:作为 session 成员)。

  • 不要对同一个 stream 并行发起多个 async_read(违反协议/并发冲突)。

  • 读取成功后,msg 已经是完整可用(header+body)的结构;buffer 可能残留后续请求数据

  • 大消息 / 流式场景可用 parser 重载async_read_some(分段处理)。

async_write

常用函数原型(简化)

// 发送“完整一条”HTTP 消息(自动序列化 header+body)
template<class AsyncWriteStream,             // 要满足 AsyncWriteStream 概念bool isRequest, class Body, class Fields,class WriteHandler                  // void(error_code, std::size_t)
>
void async_write(AsyncWriteStream& stream,boost::beast::http::message<isRequest, Body, Fields>& msg,WriteHandler&& handler);// 使用 serializer(零拷贝/分段写更灵活)
template<class AsyncWriteStream,class Serializer,                   // http::serializer<isRequest, Body, Fields>class WriteHandler
>
void async_write(AsyncWriteStream& stream,Serializer& sr,WriteHandler&& handler);

参数与行为

  • AsyncWriteStream& stream
    tcp::socketssl::stream<tcp::socket>。必须有 async_write_someget_executor()

  • message& msg

    • 函数会根据消息的 header/body 进行 HTTP 序列化并异步写出

    • 若使用 dynamic_body 且通过 beast::ostream(msg.body()) 写入数据,记得:

      • 要么 msg.prepare_payload()(自动设置 Content-Length),

      • 要么手动 msg.content_length(msg.body().size())

      • 切勿 同时存在 Content-LengthTransfer-Encoding: chunked

  • Serializer& sr
    更底层/灵活,适合大文件分段、零拷贝等高级用法(你的代码持有 serializer,可以多次调用写,控制 backpressure)。

  • WriteHandler&& handler
    签名:void(boost::system::error_code ec, std::size_t bytes_transferred)
    回调在 stream.get_executor() 的环境里执行。
    写完后,如需关闭连接,调用:

stream.shutdown(tcp::socket::shutdown_send, ec);

要点 / 易错点

  • 生命周期msg(或 serializer)必须活到回调结束。

  • 长度一致性Content-Length 必须与实际 body 大小一致;否则对端会挂起或报错。

  • 并发:不要在同一 stream 上并行发起多个 async_write。若要串行多个响应,使用 strand 或写队列。

  • 关闭语义:HTTP/1.1 默认 keep-alive;若要主动短连接,msg.keep_alive(false) 或写后 shutdown/close

简易示例

涉及一个连接类,里面拥有flat_buffer、request、response等成员变量,通过重复对同一个flat_buffer调用异步读实现接收http请求

// beast-http-server.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//#include <iostream>
#include<boost/asio.hpp>
#include<boost/beast/core.hpp>
#include<boost/beast/http.hpp>
#include <boost/version.hpp>
#include <chrono>
#include<ctime>
#include<cstdlib>
#include<memory>
#include<string>
#include<json/json.h>
#include<json/value.h>
#include<json/reader.h>
namespace beast = boost::beast;
namespace http = beast::http;
namespace asio = boost::asio;
using tcp = asio::ip::tcp;
namespace my_program_state {std::size_t request_count() {//统计别人请求我的次数static std::size_t count = 0;return count++;}std::time_t now() {//获取当前时间return std::time(0);}
}
//http连接类;继承enable_shared_from_this,通过使用shared_from_this保活
class http_connection :public std::enable_shared_from_this<http_connection> {
public:http_connection(tcp::socket &&socket):socket_(std::move(socket)){}void start() {read_request();//接收请求check_deadline();//处理超时}
private:tcp::socket socket_;//套接字beast::flat_buffer buffer_{ 8192 };//flat_bufferhttp::request<http::dynamic_body> request_;//请求http::response<http::dynamic_body> response_;//回应asio::steady_timer deadline_{ socket_.get_executor(),std::chrono::seconds{60} };//定时器,1分钟算超时void read_request(){//注册读请求auto self = shared_from_this();//调用异步读函数http::async_read(socket_, buffer_, request_, [self](beast::error_code ec,std::size_t bytes_transferred) {boost::ignore_unused(bytes_transferred);if (!ec) {//没错self->process_request();//处理请求}});}void check_deadline() {auto self = shared_from_this();deadline_.async_wait([self](boost::system::error_code ec) {if (!ec) {//没错self->socket_.close(ec);//超时了}});}void process_request() {response_.version(request_.version());//请求是什么版本,回应就什么版本response_.keep_alive(false);//短连接switch (request_.method())//判断请求类型{case http::verb::get://get请求response_.result(http::status::ok);response_.set(http::field::server, "Beast");create_response();//生成回应break;case http::verb::post://post请求response_.result(http::status::ok);response_.set(http::field::server, "Beast");create_post_response();//生成回应break;default:response_.result(http::status::bad_request);response_.set(http::field::content_type, "text/plain");beast::ostream(response_.body()) << "Invalid request-method '" << std::string(request_.method_string()) << "'";break;}//发送回去write_response();}void create_response() {//get请求,返回htmlif (request_.target() == "/count"){response_.set(http::field::content_type, "text/html");beast::ostream(response_.body())<< "<html>\n"<< "<head><title>Request count</title></head>\n"<< "<body>\n"<< "<h1>Request count</h1>\n"<< "<p>There have been "<< my_program_state::request_count()<< " requests so far.</p>\n"<< "</body>\n"<< "</html>\n";}else if (request_.target() == "/time"){response_.set(http::field::content_type, "text/html");//返回 一个html网页beast::ostream(response_.body())<< "<html>\n"<< "<head><title>Current time</title></head>\n"<< "<body>\n"<< "<h1>Current time</h1>\n"<< "<p>The current time is "<< my_program_state::now()<< " seconds since the epoch.</p>\n"<< "</body>\n"<< "</html>\n";}else {response_.result(http::status::not_found);response_.set(http::field::content_type, "text/plain");//返回一个纯文本beast::ostream(response_.body()) << "File not found\r\n";}}void write_response() {//注册写请求auto self = shared_from_this();response_.content_length(response_.body().size());//记得调用content_length设置长度http::async_write(socket_, response_, [self](beast::error_code ec, std::size_t bytes_transferred) {self->socket_.shutdown(tcp::socket::shutdown_send,ec);//关闭服务器的发送端self->deadline_.cancel();//发送完了,取消定时器});}void create_post_response() {//生成post请求对应的回应if (request_.target() == "/email") {auto& body = this->request_.body();//获得request的包体auto body_str = boost::beast::buffers_to_string(body.data());//把buffer转成字符串std::cout << "receive body is " << body_str << std::endl;this->response_.set(http::field::content_type, "text/json");Json::Value root;Json::Reader reader;Json::Value src_root;bool parse_success = reader.parse(body_str, src_root);if (!parse_success) {//解析json失败std::cout << "Failed to parse Json data" << std::endl;root["error"] = 1001;std::string jsonstr = root.toStyledString();beast::ostream(response_.body()) << jsonstr;return;}auto email = src_root["email"].asString();//获取原post请求的"email"字段,回声发回去std::cout << "email is" << email << std::endl;root["error"] = 0;root["email"] = src_root["email"];root["msg"] = "receive email post success";std::string jsonstr = root.toStyledString();beast::ostream(response_.body()) << jsonstr;}else {response_.result(http::status::not_found);response_.set(http::field::content_type, "text/plain");//返回一个纯文本beast::ostream(response_.body()) << "File not found\r\n";}}
};void http_server(tcp::acceptor& acceptor, asio::io_context& ioc) {//反复获取连接acceptor.async_accept([&](const boost::system::error_code& ec, tcp::socket socket) {if (!ec) {//必须用智能指针保活,不然回调结束该连接会自动释放std::make_shared<http_connection>(std::move(socket))->start();}http_server(acceptor, ioc); // 继续接受下一个连接});
}int main()
{try{asio::ip::tcp::endpoint ep(asio::ip::tcp::v4(),8080);//服务器地址端口asio::io_context ioc;//ioctcp::acceptor acceptor(ioc, ep);//接收器http_server(acceptor,ioc);ioc.run();}catch (const std::exception& e){std::cerr << "Error: " << e.what() << std::endl;return EXIT_FAILURE;}
}

一些注意点:

单个连接如果有多个请求怎么处理?

情况 A: 短连接(keep_alive(false)

像代码里写的:

response_.keep_alive(false);

那就一个请求 → 一个响应 → 断开。
这种情况最简单:一条连接最多就 1 个请求,不会“积压”

情况 B: 长连接(keep_alive(true)

HTTP/1.1 默认是长连接(除非客户端发了 Connection: close)。
那就会出现一个 socket 上连续收到多个请求。
串行化的处理方式是:

  1. async_read → 收到一个请求

  2. 处理并 async_write 回应

  3. 再次调用 async_read,等下一个请求

  4. 如此循环,直到对端关闭或者你主动关闭

并行化的处理方式是(可以参考之前写的完备的c++服务器那一节):

  1. async_read → 收到一个请求

  2. 扔到线程池或逻辑处理线程去处理,继续调用async_read

  3. 处理完请求,生成了一个响应,传回连接类里维护的一个响应队列

  4. 响应队列不断调用async_write 进行发送

这样做到了读写之间的解耦

body是啥类型,body.data()又是啥

比如代码中

http::request<http::dynamic_body> request_;

request_ 是一个 HTTP 请求对象,它的 Body 类型参数是 http::dynamic_body

所以:request_.body() 返回的是一个 http::dynamic_body::value_type &

struct dynamic_body {using value_type = boost::beast::multi_buffer;
};

也就是说 request_.body() 返回的就是一个 boost::beast::multi_buffer&

multi_buffer 是 Beast 提供的一个 分段式内存缓冲区(scatter/gather buffer)。它内部可能包含多个小块 buffer,拼起来表示完整的数据。

  • 所以 body 并不是一个 std::string,而是一个「缓冲区容器」。

  • 为了访问它的内容,需要用 multi_buffer::data()

multi_buffer::const_buffers_type data() const noexcept;

返回的是一个 BufferSequence(即一系列 boost::asio::const_buffer),符合 Asio 的 Buffer 概念。

boost::beast::buffers_to_string

template<class ConstBufferSequence>
std::string buffers_to_string(ConstBufferSequence const& buffers);

作用:把任意 ConstBufferSequence(比如 multi_buffer::data() 返回的东西)拷贝拼接成一个 std::string

boost::ignore_unused(bytes_transferred);

在底层Asio的异步读回调里,我们常常要关注读了多少字节,对读到的数据做拆分解析,那么这里为什么又忽略了?

http::async_read 是 Beast 提供的高级 API,作用是:

  • 它会循环调用底层的 async_read_some,一直读 socket。

  • 它知道 HTTP 报文格式(头部 + 可选 body)。

  • 只有在 完整的 HTTP 消息被解析完毕 之后,它才会调用回调函数。
    换句话说,你的 request_ 在回调里一定是完整的请求,不会只有一半

所以这里 不需要自己检查 "读了多少字节",因为 Beast 已经处理了。

bytes_transferred 有什么用?

它表示 本次调用一共从 socket 里搬运了多少字节到 buffer
但这个值一般只在调试或统计用(比如你想做流量监控、带宽统计),在业务逻辑里不需要检查。

boost::ignore_unused的作用是:
显式“使用”某个变量/参数,从而避免编译器发出未使用变量的警告

namespace boost {template <typename... Ts>void ignore_unused(const Ts&...) {}
}

也就是说,它接收任意数量、任意类型的参数,但什么都不做。

这样可以避免未使用变量的编译警告

boost::asio::steady_timer

boost::asio::steady_timer 是 Boost.Asio 提供的 定时器类,用来在未来某个时间点或者某个时间段后触发操作(同步/异步)。它是基于 单调时钟 (steady clock) 的,区别于 system_timer(基于系统时间,可能受系统时间调整影响)。

简易声明

namespace boost {
namespace asio {class steady_timer {public:// 构造函数explicit steady_timer(boost::asio::io_context& ctx);template <typename Executor>explicit steady_timer(const Executor& ex);// 构造并指定过期时间steady_timer(boost::asio::io_context& ctx, std::chrono::steady_clock::duration expiry_time);steady_timer(boost::asio::io_context& ctx, std::chrono::steady_clock::time_point expiry_time);// 设置过期时间void expires_after(const std::chrono::steady_clock::duration& d);void expires_at(const std::chrono::steady_clock::time_point& t);// 同步等待void wait(boost::system::error_code& ec);void wait();// 异步等待,回调类型是void(const boost::system::error_code& ec);template <typename WaitHandler>void async_wait(WaitHandler&& handler);// 取消定时器std::size_t cancel();std::size_t cancel_one();};
}
}
  • steady_timer 使用 std::chrono::steady_clock,它保证时间点不会因系统时间调整而跳变(单调递增)。

  • 定时器默认构造后不会立刻触发,需要通过 expires_afterexpires_at 设置过期时间。

  • 可以选择 同步等待 (wait)异步等待 (async_wait)

  • 取消时会使等待操作立即返回,handler 也会被调用。

和 socket 配合实现超时控制

boost::asio::io_context ioc;
tcp::socket socket(ioc);
boost::asio::steady_timer timer(ioc);// 读 socket 超时控制
timer.expires_after(std::chrono::seconds(10));
timer.async_wait([&](const boost::system::error_code& ec){if (!ec) {std::cout << "timeout!\n";socket.close(); // 超时就关闭 socket}
});// 异步读
asio::async_read(socket, asio::buffer(buf), [&](const boost::system::error_code& ec, std::size_t){timer.cancel(); // 读完成了就取消定时器});ioc.run();

本例中也是类似的做法

http://www.dtcms.com/a/334066.html

相关文章:

  • Rust学习笔记(四)|结构体与枚举(面向对象、模式匹配)
  • C++基础——内存管理
  • 基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
  • 零知开源——基于STM32F407VET6的TCS230颜色识别器设计与实现
  • 开源数据发现平台:Amundsen Frontend Service 推荐实践
  • Camx-Tuning参数加载流程分析
  • 【时时三省】(C语言基础)共用体类型数据的特点
  • 她的热情为何突然冷却?—— 解析 Kafka 吞吐量下降之谜
  • 智能合约:区块链时代的“数字契约革命”
  • 外出业务员手机自动添加报价单​——仙盟创梦IDE
  • 多商户商城系统源码选型指南:开源框架 vs 定制开发的优劣对比
  • Android RxJava 组合操作符实战:优雅处理多数据源
  • 12分区南排烟机,多线模块没电
  • Linux上管理Java的JDK版本
  • LeetCode 刷题【43. 字符串相乘】
  • 34 HTB Cat 机器 - 中等难度
  • 完整设计 之 智能合约系统:主题约定、代理协议和智能合约 (临时命名)----PromptPilot (助手)答问之2
  • Three.js三大组件:场景(Scene)、相机(Camera)、渲染器(Renderer)
  • 线性代数之两个宇宙文明关于距离的对话
  • 图像相似度算法汇总及Python实现
  • 01数据结构-关键路径
  • Unity 游戏提升 Android TargetVersion 相关记录
  • Docker小游戏 | 使用Docker部署人生重开模拟器
  • MySQL的三大范式:
  • 机器学习--决策树
  • Rust 语法基础教程
  • sqli-labs通关笔记-第52关 GET数值型order by堆叠注入(手工注入+脚本注入两种方法)
  • Ubuntu 25.04 安装并使用 MySQL 8.4.5 的步骤
  • 使用 npm-run-all2 简化你的 npm 脚本工作流
  • Linux中的restore