Ускорение проблемы асинхронного сервера Websocket - PullRequest
0 голосов
/ 14 мая 2019

Я написал асинхронную веб-сокет через boost.beast. Но когда я пытаюсь запустить его, я не могу подключиться.

Код сервера, как показано ниже. Когда я пытаюсь подключить свой сервер веб-сокетов, мой chrome показывает состояние подключения. Когда я отлаживаю через VS2017, он никогда не встречается с лямбда-выражением в run ().


iListener::iListener( boost::asio::io_context& ioc,boost::asio::ip::tcp::endpoint endpoint)  : acceptor_(ioc), socket_(ioc) {

    boost::system::error_code ec;

    std::cout<<"iListener"<<std::endl;
    // Open the acceptor
    acceptor_.open(endpoint.protocol(), ec);
    if (ec) {
        // fail(ec, "open");
        return;
    }

    // Allow address reuse
    acceptor_.set_option(boost::asio::socket_base::reuse_address(true), ec);
    if (ec) {
        // fail(ec, "set_option");
        return;
    }

    // Bind to the server address
    acceptor_.bind(endpoint, ec);
    if (ec) {
        // fail(ec, "bind");
        return;
    }

    // Start listening for connections
    acceptor_.listen(
            boost::asio::socket_base::max_listen_connections, ec);
    if (ec) {

        std::cout << ec.message() << "   listen" << std::endl;
        // fail(ec, "listen");
        return;
    }
}

iListener::~iListener() {

}

void iListener::run() {
    if (!acceptor_.is_open()) {
        return;
    }
    std::cout<<"iListener run"<<std::endl;
    while (true) {
        acceptor_.async_accept(socket_, [&](boost::system::error_code ec1) {
            std::cout << "now run listener" << std::endl;
            if (ec1) {
                std::cout<<ec1.message()<<"   accept"<<std::endl;
                // fail(ec, "accept");
            } else {
                // Create the session and run it
                std::make_shared<NormalSession>(std::move(socket_))->run();
            }
        });
    }


}

void iListener::initListener(const std::string &addressStr, unsigned short port, int threads){
    auto const address = boost::asio::ip::make_address(addressStr);
    boost::asio::io_context ioc{threads};
    std::make_shared<iListener>(ioc, boost::asio::ip::tcp::endpoint{address, port})->run();
    std::vector<std::thread> v;
    v.reserve(threads - 1);
    for(auto i = threads - 1; i > 0; --i)
        v.emplace_back(
                [&ioc]
                {
                    ioc.run();
                });
    ioc.run();
}

И когда я пытаюсь подключить его на консоли Chrome. Это требует много времени для подключения, а затем показывает сбой.


Так что я перехожу обратно, как пример boost.It работает.


void iListener::run() {
    if (!acceptor_.is_open()) {
        return;
    }
   // std::cout<<"iListener run"<<std::endl;
   // while (true) {
   //     acceptor_.async_accept(socket_, [&](boost::system::error_code ec1) {
            //std::cout << "now run listener" << std::endl;
   //         if (ec1) {
   //             std::cout<<ec1.message()<<"   accept"<<std::endl;
   //             // fail(ec, "accept");
   //         } else {
   //             // Create the session and run it
   //             std::make_shared<NormalSession>(std::move(socket_))->run();
   //         }
   //     });
   // }
    do_accept();

}

void iListener::do_accept() {
    acceptor_.async_accept(
        socket_,
        std::bind(
            &iListener::on_accept,
            shared_from_this(),
            std::placeholders::_1));
}

void    iListener::on_accept(boost::system::error_code ec) {
    if (ec)
    {
        std::cout << ec.message() << "   on_accept" << std::endl;
    }
    else
    {
        // Create the session and run it
        std::make_shared<NormalSession>(std::move(socket_))->run();
    }

    // Accept another connection
    do_accept();
}

У меня два вопроса:

1.Почему я использую лямбду, это будет SOF, но пример - нет. 2. Когда я использую while (), это не работает, почему? Есть ли разница между лямбда-выражением и std :: bind () ??


Итак, еще один вопрос, что отличается между двумя кодовыми блоками ниже?

void iListener::do_accept() {
    acceptor_.async_accept(
        socket_,
        [&](boost::system::error_code ec1) mutable {
        on_accept(ec1);
    }
}


void iListener::do_accept() {
    acceptor_.async_accept(
        socket_,
        std::bind(
            &iListener::on_accept,
            shared_from_this(),
            std::placeholders::_1));
}

Когда я использую верхний, он возвращает код ошибки 995.

1 Ответ

1 голос
/ 14 мая 2019

РЕДАКТИРОВАТЬ

В чем разница между bind и lambda?В первом случае вы продлеваете срок действия экземпляра iListener, во втором - нет.

Нам нужно начать с этой строки:

std::make_shared<iListener>(ioc, boost::asio::ip::tcp::endpoint{address, port})->run();
// [a]

, если вы не продлите время жизни iListener в run, в строке [a] экземпляр iListener будетуничтожены.

  • std :: bind

В качестве одного из параметров связывания, который вы передаете shared_from_this, он создает shared_ptr из this указателя, поэтому объект functor возвращаетсяна bind сохраняет умный указатель на iListener экземпляр продлевает свое время жизни.

  • лямбда

время жизни iListener не продлено, вы вызываете async_accept мимоходлямбда без увеличения счетчика ссылок для текущего объекта, т.е. для которого вызывается do_accept.Таким образом, async_accept возвращается немедленно, do_accept заканчивается, наконец, run также заканчивается, и объект, созданный std::make_shared<iListener>(ioc, boost::asio::ip::tcp::endpoint{address, port}), удаляется.

Вам необходимо обновить счетчик ссылок, передав shared_ptr по значению в вашу лямбду:

void iListener::do_accept() {
    auto sp = shared_from_this();
    acceptor_.async_accept(
        socket_,
        [&,sp](boost::system::error_code ec1) mutable 
      {
        on_accept(ec1);
      }
}

Обработчик (в вашем случае тело лямбды) для задачи, инициированнойasync_accept вызывается из io_context::run, вы не можете видеть это выполнение, потому что ваш код висит на этой строке:

std::make_shared<iListener>(ioc, boost::asio::ip::tcp::endpoint{address, port})->run();

это создает iListener экземпляр и вызывает run, который содержит бесконечный цикл и никогдазаканчивается:

while (true) { // INFINITE LOOP
    acceptor_.async_accept(socket_, [&](boost::system::error_code ec1) {
        std::cout << "now run listener" << std::endl;
        if (ec1) {
            std::cout<<ec1.message()<<"   accept"<<std::endl;
            // fail(ec, "accept");
        } else {
            // Create the session and run it
            std::make_shared<NormalSession>(std::move(socket_))->run();
        }
    });
}

, поэтому вы не можете добраться до строк, с которых начинается io_context::run, в которых можно вызывать обработчики.

Исправлено: перед запуском io_context::run вы можете запустить другой поток, где iListener::run выполнен.

Посетите Boost Asio examples , чтобы узнать, как используется async_accept.Обычным способом является вызов async_accept из его обработчика, но если вы хотите это сделать, ваш iListener должен получить из enable_shared_from_this, чтобы продлить срок его службы при переходе в обработчик.


Другая проблемас socket_ членом данных.Я предполагаю, что вы хотите держать один сокет за сеанс, но теперь ваш код не обрабатывает это правильно.У вас есть только один экземпляр socket_, который перемещается в NormalSession, если было установлено новое соединение.Поэтому, когда async_accept вызывается во второй раз, вы передаете сокет INVALID.Это не может работать.Это приводит к неопределенному поведению.

После выполнения строки ниже

std::make_shared<NormalSession>(std::move(socket_))->run();

вы можете забыть о перегрузке socket_.

async_accept, вы можете использоватьверсия, принимающая обработчик с недавно принятым сокетом.

Но если вы хотите остаться с текущей версией, принимающей socket, вам нужно убедиться, что каждый раз, когда вызывается async_accept, она принимает уникальныеэкземпляр сокета.

...