使用 Boost Beast 进行并发请求处理
Concurrent request processing with Boost Beast
我指的是来自 Beast 存储库的示例程序:https://www.boost.org/doc/libs/1_67_0/libs/beast/example/http/server/fast/http_server_fast.cpp
我对代码进行了一些更改以检查同时处理多个请求的能力。
boost::asio::io_context ioc{1};
tcp::acceptor acceptor{ioc, {address, port}};
std::list<http_worker> workers;
for (int i = 0; i < 10; ++i)
{
workers.emplace_back(acceptor, doc_root);
workers.back().start();
}
ioc.run();
我对上述内容的理解是,我现在将有 10 个工作对象 运行 I/O,即处理传入连接。
那么,我的第一个问题是上面的理解正确吗?
假设以上是正确的,我对传递给 tcp::acceptor:
的 lambda(处理程序)做了一些更改
void accept()
{
// Clean up any previous connection.
boost::beast::error_code ec;
socket_.close(ec);
buffer_.consume(buffer_.size());
acceptor_.async_accept(
socket_,
[this](boost::beast::error_code ec)
{
if (ec)
{
accept();
}
else
{
boost::system::error_code ec2;
boost::asio::ip::tcp::endpoint endpoint = socket_.remote_endpoint(ec2);
// Request must be fully processed within 60 seconds.
request_deadline_.expires_after(
std::chrono::seconds(60));
std::cerr << "Remote Endpoint address: " << endpoint.address() << " port: " << endpoint.port() << "\n";
read_request();
}
});
}
还有 process_request()
:
void process_request(http::request<request_body_t, http::basic_fields<alloc_t>> const& req)
{
switch (req.method())
{
case http::verb::get:
std::cerr << "Simulate processing\n";
std::this_thread::sleep_for(std::chrono::seconds(30));
send_file(req.target());
break;
default:
// We return responses indicating an error if
// we do not recognize the request method.
send_bad_response(
http::status::bad_request,
"Invalid request-method '" + req.method_string().to_string() + "'\r\n");
break;
}
}
这是我的问题:如果我同时向我的服务器发送 2 个 GET
请求,它们将按顺序处理,我知道这是因为第二个“模拟处理”语句在大约 30 秒后打印前一个意味着执行在第一个线程上被阻塞。
我试图阅读 boost::asio 的文档以更好地理解这一点,但无济于事。
acceptor::async_accept
的文档说:
Regardless of whether the asynchronous operation completes immediately or not, the handler will not be >invoked from within this function. Invocation of the handler will be performed in a manner equivalent to >using boost::asio::io_service::post().
boost::asio::io_service::post()
的文档说:
The io_service guarantees that the handler will only be called in a thread in which the run(), >run_one(), poll() or poll_one() member functions is currently being invoked.
那么,如果有 10 个 worker 处于 run()
状态,那么为什么这两个请求会排队?
另外,有没有办法在不适应不同示例的情况下解决此问题? (例如 https://www.boost.org/doc/libs/1_67_0/libs/beast/example/http/server/async/http_server_async.cpp)
io_context
不会在内部创建线程来执行任务,而是使用显式调用 io_context::run
的线程。在示例中,io_context::run
仅从一个线程(主线程)调用。因此,您只有一个线程用于任务执行,该线程在 sleep
中被阻塞,并且没有其他线程可以执行其他任务。
要使此示例有效,您必须:
- 向池中添加更多线程(如您提到的第二个示例)
size_t const threads_count = 4;
std::vector<std::thread> v;
v.reserve(threads_count - 1);
for(size_t i = 0; i < threads_count - 1; ++i) { // add thraed_count threads into the pool
v.emplace_back([&ioc]{ ioc.run(); });
}
ioc.run(); // add the main thread into the pool as well
- 在需要的地方(至少对于套接字读写)添加同步(例如,像第二个例子那样使用
strand
),因为现在你的应用程序是multi-threaded.
更新 1
回答问题“如果实际上 io_context
仅在一个线程上 运行,Beast 示例中的工作人员列表(第一个提到的)的目的是什么?”
注意,无论线程数如何,这里的 IO 操作都是异步的,这意味着 http::async_write(socket_...)
不会阻塞线程。请注意,我在这里解释的是原始示例(不是您的修改版本)。这里的一名工人处理 'request-response' 中的一个 round-trip。想象一下这种情况。有两个客户端client1 和client2。客户端 1 的互联网连接不佳(或请求非常大的文件),客户端 2 的情况相反。 Client1 发出请求。然后 client2 发出请求。因此,如果只有一个工作人员,client2 将不得不等到 client1 完成整个 round-trip 'request-response`。但是,因为有多个工作人员 client2 立即得到响应而不等待 client1(请记住 IO 不会阻塞您的单线程)。 该示例针对瓶颈是 IO 而不是实际工作的情况进行了优化。在您修改的示例中,您的情况完全相反 - 与 IO 相比,工作(30 秒)非常昂贵。对于这种情况,最好使用第二个示例。
我指的是来自 Beast 存储库的示例程序:https://www.boost.org/doc/libs/1_67_0/libs/beast/example/http/server/fast/http_server_fast.cpp
我对代码进行了一些更改以检查同时处理多个请求的能力。
boost::asio::io_context ioc{1};
tcp::acceptor acceptor{ioc, {address, port}};
std::list<http_worker> workers;
for (int i = 0; i < 10; ++i)
{
workers.emplace_back(acceptor, doc_root);
workers.back().start();
}
ioc.run();
我对上述内容的理解是,我现在将有 10 个工作对象 运行 I/O,即处理传入连接。
那么,我的第一个问题是上面的理解正确吗?
假设以上是正确的,我对传递给 tcp::acceptor:
的 lambda(处理程序)做了一些更改 void accept()
{
// Clean up any previous connection.
boost::beast::error_code ec;
socket_.close(ec);
buffer_.consume(buffer_.size());
acceptor_.async_accept(
socket_,
[this](boost::beast::error_code ec)
{
if (ec)
{
accept();
}
else
{
boost::system::error_code ec2;
boost::asio::ip::tcp::endpoint endpoint = socket_.remote_endpoint(ec2);
// Request must be fully processed within 60 seconds.
request_deadline_.expires_after(
std::chrono::seconds(60));
std::cerr << "Remote Endpoint address: " << endpoint.address() << " port: " << endpoint.port() << "\n";
read_request();
}
});
}
还有 process_request()
:
void process_request(http::request<request_body_t, http::basic_fields<alloc_t>> const& req)
{
switch (req.method())
{
case http::verb::get:
std::cerr << "Simulate processing\n";
std::this_thread::sleep_for(std::chrono::seconds(30));
send_file(req.target());
break;
default:
// We return responses indicating an error if
// we do not recognize the request method.
send_bad_response(
http::status::bad_request,
"Invalid request-method '" + req.method_string().to_string() + "'\r\n");
break;
}
}
这是我的问题:如果我同时向我的服务器发送 2 个 GET
请求,它们将按顺序处理,我知道这是因为第二个“模拟处理”语句在大约 30 秒后打印前一个意味着执行在第一个线程上被阻塞。
我试图阅读 boost::asio 的文档以更好地理解这一点,但无济于事。
acceptor::async_accept
的文档说:
Regardless of whether the asynchronous operation completes immediately or not, the handler will not be >invoked from within this function. Invocation of the handler will be performed in a manner equivalent to >using boost::asio::io_service::post().
boost::asio::io_service::post()
的文档说:
The io_service guarantees that the handler will only be called in a thread in which the run(), >run_one(), poll() or poll_one() member functions is currently being invoked.
那么,如果有 10 个 worker 处于 run()
状态,那么为什么这两个请求会排队?
另外,有没有办法在不适应不同示例的情况下解决此问题? (例如 https://www.boost.org/doc/libs/1_67_0/libs/beast/example/http/server/async/http_server_async.cpp)
io_context
不会在内部创建线程来执行任务,而是使用显式调用 io_context::run
的线程。在示例中,io_context::run
仅从一个线程(主线程)调用。因此,您只有一个线程用于任务执行,该线程在 sleep
中被阻塞,并且没有其他线程可以执行其他任务。
要使此示例有效,您必须:
- 向池中添加更多线程(如您提到的第二个示例)
size_t const threads_count = 4;
std::vector<std::thread> v;
v.reserve(threads_count - 1);
for(size_t i = 0; i < threads_count - 1; ++i) { // add thraed_count threads into the pool
v.emplace_back([&ioc]{ ioc.run(); });
}
ioc.run(); // add the main thread into the pool as well
- 在需要的地方(至少对于套接字读写)添加同步(例如,像第二个例子那样使用
strand
),因为现在你的应用程序是multi-threaded.
更新 1
回答问题“如果实际上 io_context
仅在一个线程上 运行,Beast 示例中的工作人员列表(第一个提到的)的目的是什么?”
注意,无论线程数如何,这里的 IO 操作都是异步的,这意味着 http::async_write(socket_...)
不会阻塞线程。请注意,我在这里解释的是原始示例(不是您的修改版本)。这里的一名工人处理 'request-response' 中的一个 round-trip。想象一下这种情况。有两个客户端client1 和client2。客户端 1 的互联网连接不佳(或请求非常大的文件),客户端 2 的情况相反。 Client1 发出请求。然后 client2 发出请求。因此,如果只有一个工作人员,client2 将不得不等到 client1 完成整个 round-trip 'request-response`。但是,因为有多个工作人员 client2 立即得到响应而不等待 client1(请记住 IO 不会阻塞您的单线程)。 该示例针对瓶颈是 IO 而不是实际工作的情况进行了优化。在您修改的示例中,您的情况完全相反 - 与 IO 相比,工作(30 秒)非常昂贵。对于这种情况,最好使用第二个示例。