Boost 异步 Websocket 服务器问题

问题描述 投票:0回答:1

我通过 boost.beast 编写了一个异步 websocket。但是当我尝试运行它时,我无法连接它。

服务器代码如下。当我尝试连接我的 websocket 服务器时,我的 chrome 显示连接状态。 当我通过 VS2017 进行调试时,它永远不会遇到 run() 中的 lambda 表达式。


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为例。它有效。


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.为什么我使用 lambda,它会 SOF ,但 example 不会。 2.当我使用while()时,它不起作用,为什么? lambda 表达式和 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));
}

当我使用最上面的一个时,它返回error_code 995。

boost-asio boost-beast beast-websockets
1个回答
1
投票

编辑

bind
lambda
有什么区别?在前者中,您可以延长
iListener
实例的生命周期,而在后者中则不能。

我们需要从这一行开始:

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

如果您不延长

iListener
run
的生命周期,则 [a] 行中的
iListener
实例将被销毁。

  • std::绑定

作为您传递的绑定参数之一

shared_from_this
,它从
shared_ptr
指针创建
this
,因此
bind
返回的函子对象保持指向
iListener
实例的智能指针,从而延长其生命周期。

  • 拉姆达

iListener

的生命周期不会延长,您调用
async_accept
传递lambda而不增加当前对象的引用计数器,即调用
do_accept
。所以
async_accept
立即返回,
do_accept
结束,最后
run
也结束,
std::make_shared<iListener>(ioc, boost::asio::ip::tcp::endpoint{address, port})
创建的对象被删除。 

您需要通过将

shared_ptr

 按值传递到 lambda 中来更新引用计数器:

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

 启动的任务的处理程序(在您的例子中是 lambda 的主体)是从 
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

 已重载,您可以使用采用 
handler 和新接受的套接字的版本。

但是如果您想保留当前版本并采用

socket

,则需要确保每次调用 
async_accept
 时都采用唯一的套接字实例。

© www.soinside.com 2019 - 2024. All rights reserved.