提升异步 Websocket 服务器问题
Boost Async Websocket Server Issue
我已经通过 boost.beast 编写了一个异步 websocket。但是当我尝试 运行 它时,我无法连接它。
服务器代码如下。当我尝试连接我的 websocket 服务器时,我的 chrome 显示连接状态。
当我通过 VS2017 进行调试时,它永远不会 运行 进入 运行().
中的 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.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.Why 我使用 lambda,它会 SOF ,但示例不会。
2.When 我使用 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));
}
当我用最上面的时候,它return error_code 995.
编辑
bind
和 lambda
有什么区别?在前者中,你延长了 iListener
实例的生命周期,在后者中,你没有。
我们需要从这一行开始:
std::make_shared<iListener>(ioc, boost::asio::ip::tcp::endpoint{address, port})->run();
// [a]
如果您不延长 run
中 iListener
的生命周期,[a] 行 iListener
中的实例将被销毁。
- std::bind
作为您传递的绑定参数之一 shared_from_this
,它从 this
指针创建 shared_ptr
,因此 bind
返回的仿函数对象将智能指针保持为 iListener
实例延长了生命周期。
- λ
iListener
的生命周期未延长,您调用 async_accept
传递 lambda 而没有增加当前对象的引用计数器,即调用 do_accept
的对象。所以 async_accept
returns 立即, 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);
}
}
从 io_context::run
调用由 async_accept
启动的任务的处理程序(在你的例子中是 lambda 的主体),你看不到这个执行,因为你的代码挂在这一行:
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
时,您传递的是无效套接字。它不能工作。它会导致未定义的行为。
执行下面的行后
std::make_shared<NormalSession>(std::move(socket_))->run();
你可以忘记 socket_
。
async_accept
已过载,您可以使用带 handler 的版本和新接受的套接字。
但是如果你想继续使用当前版本 socket
,你需要确保每次调用 async_accept
时它都使用唯一的套接字实例。
我已经通过 boost.beast 编写了一个异步 websocket。但是当我尝试 运行 它时,我无法连接它。
服务器代码如下。当我尝试连接我的 websocket 服务器时,我的 chrome 显示连接状态。 当我通过 VS2017 进行调试时,它永远不会 运行 进入 运行().
中的 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.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.Why 我使用 lambda,它会 SOF ,但示例不会。 2.When 我使用 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));
}
当我用最上面的时候,它return error_code 995.
编辑
bind
和 lambda
有什么区别?在前者中,你延长了 iListener
实例的生命周期,在后者中,你没有。
我们需要从这一行开始:
std::make_shared<iListener>(ioc, boost::asio::ip::tcp::endpoint{address, port})->run();
// [a]
如果您不延长 run
中 iListener
的生命周期,[a] 行 iListener
中的实例将被销毁。
- std::bind
作为您传递的绑定参数之一 shared_from_this
,它从 this
指针创建 shared_ptr
,因此 bind
返回的仿函数对象将智能指针保持为 iListener
实例延长了生命周期。
- λ
iListener
的生命周期未延长,您调用 async_accept
传递 lambda 而没有增加当前对象的引用计数器,即调用 do_accept
的对象。所以 async_accept
returns 立即, 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);
}
}
从 io_context::run
调用由 async_accept
启动的任务的处理程序(在你的例子中是 lambda 的主体),你看不到这个执行,因为你的代码挂在这一行:
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
时,您传递的是无效套接字。它不能工作。它会导致未定义的行为。
执行下面的行后
std::make_shared<NormalSession>(std::move(socket_))->run();
你可以忘记 socket_
。
async_accept
已过载,您可以使用带 handler 的版本和新接受的套接字。
但是如果你想继续使用当前版本 socket
,你需要确保每次调用 async_accept
时它都使用唯一的套接字实例。