Boost.Asio学习(7):Boost.Beast实现简易http服务器
namespace beast = boost::beast;
beast::flat_buffer
是一个用于 Boost.Asio 和 Boost.Beast 网络读写的缓冲区实现。
专为 一次性顺序读取 / 消费 场景设计,比
std::string
或std::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
基类提供的,request
和 response
都能用。
函数原型(简化版):
template<class Field, class String>
basic_fields& set(Field name, String&& value);
Field name
→ 一般传http::field
枚举值(也可传字符串,但推荐枚举)String&& value
→ 可以是std::string
、const 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 专用的,就不列全了
};
注意事项
重复字段:
如果用
set
,会覆盖同名字段;如果用
insert
,会追加同名字段(比如Set-Cookie
可以多次)。
大小写:
field
枚举会自动生成标准大小写,比如content_type
→Content-Type
。
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_body | std::string | 文本、JSON、小型数据 |
http::vector_body<std::uint8_t> | std::vector | 二进制数据(图片、小文件) |
http::file_body | 文件流 | 大文件传输(零拷贝) |
http::dynamic_body | boost::beast::multi_buffer | 流式传输,不预先知道长度 |
http::empty_body | 无 body | GET 响应或 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_body
的 body()
返回的是一个 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_body
或buffer_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 兼容 |
③ 设置 body | beast::ostream(response.body()) << "..." 或 response.body() = ... | 写响应内容 |
2. 常用步骤(几乎每次都会用)
这些是大多数 HTTP 服务端都会加的,方便客户端解析或优化传输。
步骤 | 函数/API | 作用 |
---|---|---|
设置 Content-Type | response.set(http::field::content_type, "text/html") | 让浏览器/客户端知道返回的是什么格式(HTML、JSON、文本等) |
设置 Server 头 | response.set(http::field::server, "Beast") | 告诉客户端这是哪个服务器返回的(调试 / 统计用) |
设置 Keep-Alive | response.keep_alive(request.keep_alive()) | 决定是否复用 TCP 连接,减少重复握手开销 |
设置 Content-Length | response.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") | 自定义协议 / 版本号 / 签名等 |
设置 Cookie | response.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)
,这样就不会每次都忘记 version
、keep_alive
、content_length
这些关键步骤。
prepare_payload
template<bool isRequest, class Body, class Fields>
void
message<isRequest, Body, Fields>::
prepare_payload(std::false_type);
作用
自动计算 Content-Length
如果消息体可以一次性知道长度(比如
string_body
或dynamic_body
已经写入完成),会自动设置Content-Length
字段。
设置 Transfer-Encoding
如果无法提前知道长度(比如
chunked_body
),会自动添加"Transfer-Encoding: chunked"
。
保持已有字段
如果你已经手动设置了
Content-Length
或Transfer-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::socket
、ssl::stream<tcp::socket>
。必须有async_read_some
、get_executor()
。DynamicBuffer& buffer
读缓冲。典型用beast::flat_buffer
。必须与消息对象同寿命(至少覆盖到回调结束)。函数会从stream
读数据 → 填充到buffer
→ 解析到msg
。注意:解析只会“消费”组成当前消息的字节,如果收到了一部分“下一条请求”(HTTP pipelining),剩余字节会留在
buffer
里供你下一次async_read
继续用。message<...>& msg
或Parser& 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,就在同一串行上下文里)。
要点 / 易错点
buffer
和msg
必须在回调返回前存活(常见做法:作为 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::socket
或ssl::stream<tcp::socket>
。必须有async_write_some
、get_executor()
。message& msg
函数会根据消息的 header/body 进行 HTTP 序列化并异步写出。
若使用
dynamic_body
且通过beast::ostream(msg.body())
写入数据,记得:要么
msg.prepare_payload()
(自动设置Content-Length
),要么手动
msg.content_length(msg.body().size())
;切勿 同时存在
Content-Length
和Transfer-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 上连续收到多个请求。
串行化的处理方式是:
async_read
→ 收到一个请求处理并
async_write
回应再次调用
async_read
,等下一个请求如此循环,直到对端关闭或者你主动关闭
并行化的处理方式是(可以参考之前写的完备的c++服务器那一节):
async_read
→ 收到一个请求扔到线程池或逻辑处理线程去处理,继续调用
async_read
处理完请求,生成了一个响应,传回连接类里维护的一个响应队列
响应队列不断调用
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_after
或expires_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();
本例中也是类似的做法