Недавно я играл с boost asio и некоторыми новыми конструкциями c ++ 11. Вот пример раздела кода, который вызывает неожиданное поведение (по крайней мере, для меня).
void Server::startAccept()
{
connections_.push_back(std::make_shared<Connection>(io_service_));
acceptor_.async_accept(connections_.back()->socket(), std::bind(&Server::accept_handler, this, connections_.back(), std::placeholders::_1));
}
void Server::accept_handler(std::shared_ptr<Connection> con, const boost::system::error_code& ec)
{
startAccept();
if (!ec) {
con->start();
}
}
Прежде чем я вызову Server :: startAccept, я создал экземпляр io_service :: work и пул std :: thread, который вызвал io_service_.run (). После того, как я сделаю вызов startAccept, основной поток просто ждет ввода из командной строки.
Я ожидаю, что один из потоков в моем пуле потоков запустит Server :: accept_handler при установлении соединения. Этого не происходит Вместо этого я должен вызвать io_service_.run () из основного потока.
Теперь я немного поиграл и обнаружил, что могу добиться желаемого поведения, выполнив следующее:
void Server::startAccept()
{
connections_.push_back(std::make_shared<Connection>(io_service_));
io_service_.post([this]() { acceptor_.async_accept(connections_.back()->socket(), std::bind(&Server::accept_handler, this, connections_.back(), std::placeholders::_1)); });
}
void Server::accept_handler(std::shared_ptr<Connection> con, const boost::system::error_code& ec)
{
startAccept();
if (!ec) {
con->start();
}
}
В чем разница между операциями .async_ * и io_service.post?
РЕДАКТИРОВАТЬ: Определение BOOST_ASIO_ENABLE_HANDLER_TRACKING
Когда я компилирую и запускаю свою программу, а затем подключаюсь к серверу с первым блоком кода, который я включил, это вывод:
@asio|1350656555.431359|0*1|[email protected]_accept
Когда я запускаю второй блок кода, который я включил, и подключаюсь к серверу, я получаю такой вывод:
@asio|1350656734.789896|0*1|[email protected]
@asio|1350656734.789896|>1|
@asio|1350656734.789896|1*2|[email protected]_accept
@asio|1350656734.789896|<1|
@asio|1350656756.150051|>2|ec=system:0
@asio|1350656756.150051|2*3|[email protected]
@asio|1350656756.150051|>3|
@asio|1350656756.150051|2*4|[email protected]_send
@asio|1350656756.150051|3*5|[email protected]_accept
@asio|1350656756.150051|2*6|[email protected]_receive
@asio|1350656756.150051|<3|
@asio|1350656756.150051|>4|ec=system:0,bytes_transferred=54
@asio|1350656756.150051|<2|
@asio|1350656756.150051|<4|
@asio|1350656758.790803|>6|ec=system:10054,bytes_transferred=0
@asio|1350656758.790803|<6|
РЕДАКТИРОВАТЬ 2: понимание создания темы
for (int i = 0; i < NUM_WORKERS; i++) {
thread_pool.push_back(std::shared_ptr<std::thread>(new std::thread([this]() { io_service_.run(); })));
}
Если вы не забыли вызвать io_service :: run для каждого потока в пуле и использовали io_service :: work, чтобы избежать выхода из цикла io_service :: run, ваш код в первом случае абсолютно корректен.
Вот рабочий пример (я игнорирую обработку соединения и корректное завершение программы)
class Connection
{
public:
Connection(boost::asio::io_service & io_serivce) : socket_(io_serivce) {}
boost::asio::ip::tcp::socket & socket() { return socket_; }
private:
boost::asio::ip::tcp::socket socket_;
};
class Server
{
public:
Server(boost::asio::io_service & io_serivce, const std::string & addr, const std::string & port) : io_service_(io_serivce), acceptor_(io_service_) {
boost::asio::ip::tcp::resolver resolver(io_service_);
boost::asio::ip::tcp::resolver::query query(addr, port);
boost::asio::ip::tcp::endpoint endpoint = *resolver.resolve(query);
acceptor_.open(endpoint.protocol());
acceptor_.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
acceptor_.bind(endpoint);
acceptor_.listen();
startAccept();
}
void startAccept(){
connections_.push_back(boost::make_shared<Connection>( boost::ref(io_service_) ));
acceptor_.async_accept(connections_.back()->socket(), boost::bind(&Server::accept_handler, this, connections_.back(), _1) );
}
void Server::accept_handler(boost::shared_ptr<Connection> con, const boost::system::error_code& ec)
{
std::cout << "start connection" << std::endl;
startAccept();
}
private:
boost::asio::io_service & io_service_;
boost::asio::ip::tcp::acceptor acceptor_;
std::vector< boost::shared_ptr<Connection> > connections_;
};
int main(int argc, char * argv[])
{
// thread pool
boost::thread_group threads_;
boost::asio::io_service io_service_;
boost::asio::io_service::work work_(io_service_);
const size_t kThreadsCount = 3;
for (std::size_t i = 0; i < kThreadsCount; ++i) {
threads_.create_thread(boost::bind(&boost::asio::io_service::run, &io_service_));
}
Server s(io_service_, "127.0.0.1", "8089");
char ch;
std::cin >> ch;
return 0;
}
io_service.run
Функция является фактическим циклом событий, и в основном она вызывает io_service.post
в петле.
Редактировать: От io_service.post
документация:
Запросите io_service для вызова данного обработчика и немедленного возврата.
А также
io_service
гарантирует, что обработчик будет вызываться только в потоке, в которомrun()
,run_one()
,poll() or poll_one()
функции-члены в настоящее время вызывается.
Что ты должен сделать это либо реализовать свой собственный цикл событий, вызывая io_service.run_one
, или позвоните по телефону io_service.run
позволить Boost обрабатывать цикл событий. И это не имеет значения который В потоке, из которого вы запускаете цикл событий, все обработчики событий будут вызываться из потока, в котором вы запускаете цикл событий.