簡體   English   中英

boost::asio async_receive_from UDP 端點在線程之間共享?

[英]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類,用於保存每個請求的緩沖區和遠程端點
  • 一個線程池,它能夠在多個內核上擴展實際處理(而不是IO)的負載。
#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 樣式,例如新的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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM