Фрагмент кода ниже можно увидеть на этой странице.
#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 Что меня смутило, так это то, что 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
в приведенном ниже коде. Где я ошибаюсь?
К счастью, я почувствовал это замешательство, поэтому мой ответ объяснил это.
Я немного в замешательстве. Вы видели объяснение? Это помогло?
@sehe Спасибо за подробное объяснение. Я прочитал ваш ответ несколько раз. Но жаль, что я этого не понимаю. Этот вопрос до сих пор меня смущает.
Боюсь, я написал слишком много. Что, если вы сосредоточитесь на ответе ДО «Тем временем»? Остальное — это проверка кода, не связанная с заполнителем bind
. Вы путаете интерфейс обработчика (void(error_code)
) с реализацией, которую вы к нему привязываете (которая может иметь любое количество аргументов). Номер-заполнитель относится к связанному интерфейсу, поэтому _1
на самом деле правильный. Обратите внимание, что вы привязываете this
и connection
к другим параметрам, поэтому остается только один заполнитель.
@sehe Спасибо за подробное объяснение и обзор кода. Прочитав ваш последний комментарий, я внезапно это понял. Я думаю, что есть два способа понять это. 1. Как вы упомянули, this
и connection
уже связаны, и последовательность заполнителя должна увеличиваться одна за другой, скажем, _1
, _2
, _3
, поскольку только один аргумент не связан, поэтому _1
- правильный выбор . Слишком долго, пожалуйста, смотрите следующий комментарий.
2. Независимо от того, сколько параметров будет связано программистом, подтверждается интерфейс обработчика, которому нужен только один аргумент, поэтому нужен только _1
. Другими словами, как только интерфейс обработчика (т. е. void(error_code)
) зафиксирован библиотекой, boost::asio::placeholders::error
всегда эквивалентен _1
. Я думаю, что оба этих объяснения верны, но последнее лучше. Как вы об этом думаете?
Правильный. Я думаю об этом как о преобразовании интерфейса, и заполнители выбираются из целевого интерфейса («будущий ввод»). Все может стать довольно диким: coliru.stacked-crooked.com/a/704d78db5cc299e8
@sehe Замечательный пример кода! Такого использования я еще не видел! Как вы говорите, заполнители выбираются из целевого интерфейса («будущий ввод»), будущий ввод для handler(i.e. void(error_code))
передается самим asio, когда устанавливается соединение или что-то идет не так. Я прав?
Да <padding/> <padding/>
@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));
). Я прав?
Кажется, что последний мой комментарий невозможен. Я считаю, что невозможно вызвать Complete_handler("no_meaning_arg", "no_meaning_arg", err) в библиотеке asio при вызове полного обработчика, поскольку подпись обработчика уже определена как void(error_code)
Да обоим. Если бы Asio сделал это, это сработало бы, и да, Boost этого не делает;)
Если вы думали о положении параметра (_1), это правильно, потому что подпись обработчика (docs):
void handler(
const boost::system::error_code& error // Result of operation.
);
Обратите внимание, что вашим обработчиком является выражение связывания, а не handle_accept
(которое принимает 3 аргумента, включая аргумент this).
Если вы считаете, что _1
более выразительно, дерзайте.
Тем временем,
<boost/bind/bind.hpp>
или настраивайте определения компилятора)bytes_transferred
); в частности, НЕ полагайтесь на NUL-завершение data
!using namespace
без разбора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
. Например. определитесь с построчным вводом-выводом, чтобы вы читали до первой новой строки (а также писали сообщения с ограничителем новой строки)#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;
}
С более полезной локальной демонстрацией:
Если они эквивалентны, почему один лучше другого?