Подходящее использование boost::asio::placeholders::error

Фрагмент кода ниже можно увидеть на этой странице.

#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/enable_shared_from_this.hpp>
using namespace boost::asio;
using ip::tcp;
using std::cout;
using std::endl;


class con_handler : public boost::enable_shared_from_this<con_handler>
{
private:
  tcp::socket sock;
  std::string message = "Hello From Server!";
  enum { max_length = 1024 };
  char data[max_length];
    
public:
    
typedef boost::shared_ptr<con_handler> pointer;
 con_handler(boost::asio::io_service& io_service)
    : sock(io_service)
  {
  }

  static pointer create(boost::asio::io_service& io_service)
  {
    return pointer(new con_handler(io_service));
  }
  
  tcp::socket& socket()
  {
    return sock;
  }

  void start()
  {
    sock.async_read_some(
        boost::asio::buffer(data, max_length),
        boost::bind(&con_handler::handle_read,
                    shared_from_this(),
                    boost::asio::placeholders::error,
                    boost::asio::placeholders::bytes_transferred));

    
    sock.async_write_some(
    boost::asio::buffer(message, max_length),
        boost::bind(&con_handler::handle_write, 
            shared_from_this(),
                boost::asio::placeholders::error,
                boost::asio::placeholders::bytes_transferred));
  }

  void handle_read(const boost::system::error_code& err,
                   size_t bytes_transferred)
  {
    if (!err) {
      cout << data << endl;

            

    } else {
std::cerr << "err (recv): " << err.message() << std::endl;
      sock.close();
    }
  }
  void handle_write(const boost::system::error_code& err,
               size_t bytes_transferred)
  {
    if (!err) {
 
    cout << "Server sent Hello message!"<< endl;
    
    } else {
      std::cerr << "err (recv): " << err.message() << std::endl;
      sock.close();
    }
  }

};

class Server {

private:
  tcp::acceptor acceptor_;

void start_accept()
  {
    // creates a socket
    con_handler::pointer connection =
      con_handler::create(acceptor_.get_io_service());

    // initiates an asynchronous accept operation 
    // to wait for a new connection. 
    acceptor_.async_accept(connection->socket(),
        boost::bind(&Server::handle_accept, this, connection,
          boost::asio::placeholders::error));
  }
public:
  Server(boost::asio::io_service& io_service): acceptor_(io_service, tcp::endpoint(tcp::v4(), 1234))
  { 
     start_accept();
}
    

  void handle_accept(con_handler::pointer connection,
                     const boost::system::error_code& err)
  {
    if (!err) {
      connection->start();
    }
    start_accept();
  }

};

int main(int argc, char *argv[])
{
  try 
{
    boost::asio::io_service io_service;   
    Server server(io_service);
    io_service.run();

} 
catch(std::exception& e) 
{
    std::cerr << e.what() << endl;
}
  return 0;
}

Я думаю, что фрагмент кода следует улучшить. Поскольку asio::placeholders::error эквивалентен Placeholders::_1 и const boost::system::error_code& err является вторым параметром для void Server::handle_accept(con_handler::pointer connection, const boost::system::error_code& err) , boost::asio::placeholders::error следует заменить на boost::placeholders::_2 в приведенном ниже коде.

    acceptor_.async_accept(connection->socket(),
        boost::bind(&Server::handle_accept, this, connection,
          boost::asio::placeholders::error));

Если они эквивалентны, почему один лучше другого?

sehe 10.06.2024 13:55

@sehe Что меня смутило, так это то, что const boost::system::error_code& err является вторым параметром для void Server::handle_accept(con_handler::pointer connection, const boost::system::error_code& err) , boost::asio::placeholders::error (т. е. boost::placeholders::_1) следует заменить на boost::placeholders::_2 в приведенном ниже коде. Где я ошибаюсь?

John 10.06.2024 14:19

К счастью, я почувствовал это замешательство, поэтому мой ответ объяснил это.

sehe 10.06.2024 14:26

Я немного в замешательстве. Вы видели объяснение? Это помогло?

sehe 11.06.2024 22:40

@sehe Спасибо за подробное объяснение. Я прочитал ваш ответ несколько раз. Но жаль, что я этого не понимаю. Этот вопрос до сих пор меня смущает.

John 12.06.2024 04:18

Боюсь, я написал слишком много. Что, если вы сосредоточитесь на ответе ДО «Тем временем»? Остальное — это проверка кода, не связанная с заполнителем bind. Вы путаете интерфейс обработчика (void(error_code)) с реализацией, которую вы к нему привязываете (которая может иметь любое количество аргументов). Номер-заполнитель относится к связанному интерфейсу, поэтому _1 на самом деле правильный. Обратите внимание, что вы привязываете this и connection к другим параметрам, поэтому остается только один заполнитель.

sehe 12.06.2024 04:59

@sehe Спасибо за подробное объяснение и обзор кода. Прочитав ваш последний комментарий, я внезапно это понял. Я думаю, что есть два способа понять это. 1. Как вы упомянули, this и connection уже связаны, и последовательность заполнителя должна увеличиваться одна за другой, скажем, _1, _2, _3, поскольку только один аргумент не связан, поэтому _1 - правильный выбор . Слишком долго, пожалуйста, смотрите следующий комментарий.

John 12.06.2024 05:40

2. Независимо от того, сколько параметров будет связано программистом, подтверждается интерфейс обработчика, которому нужен только один аргумент, поэтому нужен только _1. Другими словами, как только интерфейс обработчика (т. е. void(error_code)) зафиксирован библиотекой, boost::asio::placeholders::error всегда эквивалентен _1. Я думаю, что оба этих объяснения верны, но последнее лучше. Как вы об этом думаете?

John 12.06.2024 05:40

Правильный. Я думаю об этом как о преобразовании интерфейса, и заполнители выбираются из целевого интерфейса («будущий ввод»). Все может стать довольно диким: coliru.stacked-crooked.com/a/704d78db5cc299e8

sehe 12.06.2024 14:08

@sehe Замечательный пример кода! Такого использования я еще не видел! Как вы говорите, заполнители выбираются из целевого интерфейса («будущий ввод»), будущий ввод для handler(i.e. void(error_code)) передается самим asio, когда устанавливается соединение или что-то идет не так. Я прав?

John 12.06.2024 14:36

Да <padding/> <padding/>

sehe 12.06.2024 15:40

@sehe Большое тебе спасибо. Благодаря вашей щедрой помощи мое понимание этого вопроса находится на более глубоком уровне. Если я вас правильно понимаю, если handler (т. е. void(error_code)) в asio-библиотеке называется как handler("no_meaning_arg", "no_meaning_arg", err), то _3 должен быть правильным выбором (т. е. acceptor_.async_accept(connection->socket(), boost::bind(&Server::handle_accept, this, connection, _3));). Я прав?

John 13.06.2024 04:23

Кажется, что последний мой комментарий невозможен. Я считаю, что невозможно вызвать Complete_handler("no_meaning_arg", "no_meaning_arg", err) в библиотеке asio при вызове полного обработчика, поскольку подпись обработчика уже определена как void(error_code)

John 13.06.2024 04:41

Да обоим. Если бы Asio сделал это, это сработало бы, и да, Boost этого не делает;)

sehe 13.06.2024 11:20
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
14
85
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Если вы думали о положении параметра (_1), это правильно, потому что подпись обработчика (docs):

void handler(
    const boost::system::error_code& error // Result of operation.
);

Обратите внимание, что вашим обработчиком является выражение связывания, а не handle_accept (которое принимает 3 аргумента, включая аргумент this).

Если вы считаете, что _1 более выразительно, дерзайте.

Тем временем,

  • не используйте глобальные заполнители Boost Bind (включайте <boost/bind/bind.hpp> или настраивайте определения компилятора)
  • еще лучше заменить boost::bind на std::bind
  • замените boost::shared_ptr и boost::enable_shared_from_this на std::xxx
  • используйте псевдонимы типов вместо определений типов
  • не иметь неиспользуемых аргументов (например, bytes_transferred); в частности, НЕ полагайтесь на NUL-завершение data!
  • не используйте using namespace без разбора
  • не используйте массивы в стиле C
  • не завышайте размер буфера (это источник ошибок)
  • ловить исключения const&
  • сделать handle_XYZ приватным
  • не нужно sock.close() при ошибке; вы можете отменить любые цепочки асинхронных операций и позволить деструктору con_handler справиться с этим. В вашем случае даже cancel является избыточным, поскольку нет асинхронных циклов, а одно чтение/запись уже не удалось.

Упрощенно до этого момента: Live On Coliru

#include <boost/asio.hpp>
#include <iomanip>
#include <iostream>
namespace asio = boost::asio;
using namespace std::placeholders;
using asio::ip::tcp;

struct con_handler : std::enable_shared_from_this<con_handler> {
    using pointer = std::shared_ptr<con_handler>;
    con_handler(asio::any_io_executor ex) : sock(ex) {}

    tcp::socket& socket() { return sock; }

    void start() {
        auto self = shared_from_this();
        sock.async_read_some(asio::buffer(data), bind(&con_handler::handle_read, self, _1, _2));
        sock.async_write_some(asio::buffer(message), bind(&con_handler::handle_write, self, _1, _2));
    }

  private:
    void handle_read(boost::system::error_code const& err, size_t bytes_transferred) {
        std::cerr << "recv: " << bytes_transferred << "(" << err.message() << ")" << std::endl;
        if (!err) {
            std::string_view msg(data.data(), bytes_transferred);
            std::cout << quoted(msg) << std::endl;
        }
        // else sock.cancel();
    }

    void handle_write(boost::system::error_code const& err, size_t bytes_transferred) {
        std::cerr << "sent: " << bytes_transferred << "(" << err.message() << ")" << std::endl;
        if (!err)
            std::cout << "Server sent " << quoted(message) << std::endl;

        // else sock.cancel();
    }

    tcp::socket            sock;
    std::string            message = "Hello From Server!";
    std::array<char, 1024> data;
};

struct Server {
    Server(asio::any_io_executor ex) //
        : acceptor_(ex, tcp::endpoint(tcp::v4(), 1234)) {
        start_accept();
    }

    void handle_accept(con_handler::pointer connection, boost::system::error_code const& err) {
        if (!err)
            connection->start();

        start_accept();
    }

  private:
    tcp::acceptor acceptor_;

    void start_accept() {
        con_handler::pointer connection = std::make_shared<con_handler>(acceptor_.get_executor());

        acceptor_.async_accept(connection->socket(), bind(&Server::handle_accept, this, connection, _1));
    }
};

int main() try {
    asio::io_context ioc;
    Server           server(ioc.get_executor());
    ioc.run();
} catch (std::exception const& e) {
    std::cerr << e.what() << std::endl;
}

Локальная демо:

Версия Asio: get_io_service() указывает на древнюю версию boost.

  • вместо ссылок io_service передавайте исполнителей, делая код менее связанным и его легче поддерживать.
  • не используйте частные конструкторы и new в пользу make_shared
  • используйте перегрузку перемещения-принятия async_accept, чтобы вам не приходилось нарушать инкапсуляцию с помощью socket() { return sock; }

Текстовые протоколы:

  • Рассмотрите возможность логического чтения вместо read_some. Например. определитесь с построчным вводом-выводом, чтобы вы читали до первой новой строки (а также писали сообщения с ограничителем новой строки)

v2 Live On Coliru

#include <boost/asio.hpp>
#include <iomanip>
#include <iostream>
namespace asio = boost::asio;
using namespace std::placeholders;
using asio::ip::tcp;

struct con_handler : std::enable_shared_from_this<con_handler> {
    con_handler(tcp::socket s) : sock(std::move(s)) {}

    void start() {
        do_read();
        async_write(sock, asio::buffer(message), //
                    bind(&con_handler::handle_write, shared_from_this(), _1, _2));
    }

  private:
    void do_read() {
        async_read_until(sock, asio::dynamic_buffer(response), "\n",
                         bind(&con_handler::handle_read, shared_from_this(), _1, _2));
    }

    void handle_read(boost::system::error_code const& err, size_t bytes_transferred) {
        std::cerr << "recv: " << bytes_transferred << "(" << err.message() << ")" << std::endl;
        if (!err) {
            auto msg = response.substr(0, bytes_transferred - 1);
            response.erase(0, bytes_transferred);

            std::cout << quoted(msg) << std::endl;
            do_read();
        }

        // else sock.cancel();
    }

    void handle_write(boost::system::error_code const& err, size_t bytes_transferred) {
        std::cerr << "sent: " << bytes_transferred << "(" << err.message() << ")" << std::endl;
        if (!err)
            std::cout << "Server sent " << quoted(message) << std::endl;

        // else sock.cancel();
    }

    tcp::socket sock;
    std::string message = "Hello From Server!\n", response;
};

struct Server {
    Server(asio::any_io_executor ex) : acceptor_(ex, tcp::endpoint(tcp::v4(), 1234)) { accept_loop(); }

  private:
    tcp::acceptor acceptor_;

    void accept_loop() {
        acceptor_.async_accept([this](boost::system::error_code ec, tcp::socket s) {
            if (!ec)
                std::make_shared<con_handler>(std::move(s))->start();

            accept_loop();
        });
    }
};

int main() try {
    asio::io_context ioc;
    Server           server(ioc.get_executor());
    ioc.run();
} catch (std::exception const& e) {
    std::cerr << e.what() << std::endl;
}

С более полезной локальной демонстрацией:

Другие вопросы по теме