![](/img/trans.png)
[英]udp (boost::asio) read error after async_receive_from
[英]boost::asio async_receive_from UDP endpoint shared between threads?
Boost asio 特別允許多個線程調用 io_service 上的 run() 方法。 這似乎是創建多線程 UDP 服務器的好方法。 但是,我遇到了一個問題,我正在努力尋找答案。
查看典型的 async_receive_from 調用:
m_socket->async_receive_from(
boost::asio::buffer(m_recv_buffer),
m_remote_endpoint,
boost::bind(
&udp_server::handle_receive,
this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
遠程端點和消息緩沖區沒有傳遞給處理程序,而是處於更高的 scope 級別(我的示例中的成員變量)。 處理 UDP 消息的代碼如下所示:
void dns_server::handle_receive(const boost::system::error_code &error, std::size_t size)
{
// process message
blah(m_recv_buffer, size);
// send something back
respond(m_remote_endpoint);
}
如果有多個線程在運行,同步是如何進行的? 在線程之間共享一個端點和接收緩沖區意味着 asio 在消息同時到達的情況下等待處理程序在單個線程中完成,然后再在另一個線程中調用處理程序。 這似乎否定了允許多個線程首先調用 run 的意義。
如果我想獲得請求的並發服務,看起來我需要將工作數據包連同端點的副本交給一個單獨的線程,允許處理程序方法立即返回,以便 asio 可以繼續並通過與調用 run() 的另一個線程並行的另一條消息。
這似乎有點令人討厭。 我在這里錯過了什么?
在線程之間共享單個端點和接收緩沖區意味着asio等待處理程序在單個線程內完成
如果您的意思是“當使用單個線程運行服務時”,那么這是正確的。
否則,情況並非如此。 相反,當您同時在單個服務對象(即套接字,而不是io_service)上調用操作時,Asio只會說行為是“未定義的”。
這似乎否定了允許多個線程首先調用run的觀點。
除非處理需要相當長的時間。
Timer.5示例介紹的第一段似乎是對您主題的一個很好的闡述。
要分離特定於請求的數據(緩沖區和端點),您需要一些會話概念。 Asio中的一種流行機制是綁定shared_ptr
或者來自此會話類的共享(boost bind支持直接綁定到boost :: shared_ptr實例)。
為了避免對m_socket
成員的並發,非同步訪問,您可以添加鎖或使用上面鏈接的Timer.5示例中記錄的strand
方法。
這里供您享受的是Daytime.6異步UDP日間服務器,經過修改可與許多服務IO線程配合使用。
請注意,從邏輯上講,仍然只有一個IO線程( strand
),因此我們不會違反套接字類的文檔線程安全性。
但是,與官方示例不同,響應可能會不按順序排隊,具體取決於udp_session::handle_request
實際處理所udp_session::handle_request
。
請注意
udp_session
類,用於保存每個請求的緩沖區和遠程端點 #include <ctime>
#include <iostream>
#include <string>
#include <boost/array.hpp>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/make_shared.hpp>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
using namespace boost;
using asio::ip::udp;
using system::error_code;
std::string make_daytime_string()
{
using namespace std; // For time_t, time and ctime;
time_t now = time(0);
return ctime(&now);
}
class udp_server; // forward declaration
struct udp_session : enable_shared_from_this<udp_session> {
udp_session(udp_server* server) : server_(server) {}
void handle_request(const error_code& error);
void handle_sent(const error_code& ec, std::size_t) {
// here response has been sent
if (ec) {
std::cout << "Error sending response to " << remote_endpoint_ << ": " << ec.message() << "\n";
}
}
udp::endpoint remote_endpoint_;
array<char, 100> recv_buffer_;
std::string message;
udp_server* server_;
};
class udp_server
{
typedef shared_ptr<udp_session> shared_session;
public:
udp_server(asio::io_service& io_service)
: socket_(io_service, udp::endpoint(udp::v4(), 1313)),
strand_(io_service)
{
receive_session();
}
private:
void receive_session()
{
// our session to hold the buffer + endpoint
auto session = make_shared<udp_session>(this);
socket_.async_receive_from(
asio::buffer(session->recv_buffer_),
session->remote_endpoint_,
strand_.wrap(
bind(&udp_server::handle_receive, this,
session, // keep-alive of buffer/endpoint
asio::placeholders::error,
asio::placeholders::bytes_transferred)));
}
void handle_receive(shared_session session, const error_code& ec, std::size_t /*bytes_transferred*/) {
// now, handle the current session on any available pool thread
socket_.get_io_service().post(bind(&udp_session::handle_request, session, ec));
// immediately accept new datagrams
receive_session();
}
void enqueue_response(shared_session const& session) {
socket_.async_send_to(asio::buffer(session->message), session->remote_endpoint_,
strand_.wrap(bind(&udp_session::handle_sent,
session, // keep-alive of buffer/endpoint
asio::placeholders::error,
asio::placeholders::bytes_transferred)));
}
udp::socket socket_;
asio::strand strand_;
friend struct udp_session;
};
void udp_session::handle_request(const error_code& error)
{
if (!error || error == asio::error::message_size)
{
message = make_daytime_string(); // let's assume this might be slow
// let the server coordinate actual IO
server_->enqueue_response(shared_from_this());
}
}
int main()
{
try {
asio::io_service io_service;
udp_server server(io_service);
thread_group group;
for (unsigned i = 0; i < thread::hardware_concurrency(); ++i)
group.create_thread(bind(&asio::io_service::run, ref(io_service)));
group.join_all();
}
catch (std::exception& e) {
std::cerr << e.what() << std::endl;
}
}
有趣的是,在大多數情況下,您會看到單線程版本的表現同樣出色,並且沒有理由使設計復雜化。
或者,您可以使用專用於IO的單線程io_service
,並使用舊式工作池對請求進行后台處理(如果這確實是CPU密集型部分)。 首先,這簡化了設計,其次,這可以提高IO任務的吞吐量,因為不再需要協調發布在鏈上的任務。
由於@sehe 回答的“建議編輯隊列”已滿,請允許我提交更新。
ctime()
boost::bind
,並刪除了socket_.get_io_service()
using namespace boost
,讓它更明顯async_send_to()
(h/t to Tanner Sansbury)#include <iostream>
#include <string>
#include <boost/array.hpp>
#include <boost/bind/bind.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/make_shared.hpp>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
using boost::asio::ip::udp;
using boost::system::error_code;
static std::string make_daytime_string()
{
return boost::posix_time::to_simple_string(boost::posix_time::second_clock::local_time());
}
class udp_server; // forward declaration
struct udp_session : boost::enable_shared_from_this<udp_session> {
udp_session(udp_server* server) : server_(server) {}
void handle_request(const error_code& error);
void handle_sent(const error_code& ec, std::size_t) {
// here response has been sent
if (ec) {
std::cout << "Error sending response to " << remote_endpoint_ << ": " << ec.message() << "\n";
}
}
udp::endpoint remote_endpoint_;
boost::array<char, 100> recv_buffer_;
std::string message;
udp_server* server_;
};
class udp_server
{
typedef boost::shared_ptr<udp_session> shared_session;
public:
udp_server(boost::asio::io_service& io_service)
: socket_(io_service, udp::endpoint(udp::v4(), 1313)),
strand_(io_service)
{
receive_session();
}
private:
void receive_session()
{
// our session to hold the buffer + endpoint
auto session = boost::make_shared<udp_session>(this);
socket_.async_receive_from(
boost::asio::buffer(session->recv_buffer_),
session->remote_endpoint_,
strand_.wrap(
boost::bind(&udp_server::handle_receive, this,
session, // keep-alive of buffer/endpoint
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred)));
}
void handle_receive(shared_session session, const error_code& ec, std::size_t /*bytes_transferred*/) {
// now, handle the current session on any available pool thread
boost::asio::post(socket_.get_executor(), boost::bind(&udp_session::handle_request, session, ec));
// immediately accept new datagrams
receive_session();
}
void enqueue_response(shared_session const& session) {
// async_send_to() is not thread-safe, so use a strand.
boost::asio::post(socket_.get_executor(),
strand_.wrap(boost::bind(&udp_server::enqueue_response_strand, this, session)));
}
void enqueue_response_strand(shared_session const& session) {
socket_.async_send_to(boost::asio::buffer(session->message), session->remote_endpoint_,
strand_.wrap(boost::bind(&udp_session::handle_sent,
session, // keep-alive of buffer/endpoint
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred)));
}
udp::socket socket_;
boost::asio::io_context::strand strand_;
friend struct udp_session;
};
void udp_session::handle_request(const error_code& error)
{
if (!error || error == boost::asio::error::message_size)
{
message = make_daytime_string(); // let's assume this might be slow
message += "\n";
// let the server coordinate actual IO
server_->enqueue_response(shared_from_this());
}
}
int main()
{
try {
boost::asio::io_service io_service;
udp_server server(io_service);
boost::thread_group group;
for (unsigned i = 0; i < boost::thread::hardware_concurrency(); ++i)
group.create_thread(bind(&boost::asio::io_service::run, boost::ref(io_service)));
group.join_all();
}
catch (std::exception& e) {
std::cerr << e.what() << std::endl;
}
return 0;
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.