Boost.Asio TCP 移动到套接字析构函数不足以干净地关闭?
Boost.Asio TCP moved-to socket destructor not enough to cleanly close?
考虑这个测试程序:
#include <boost/asio/io_service.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <functional>
#include <iostream>
static void callback (boost::asio::ip::tcp::socket && socket)
{
//boost::asio::ip::tcp::socket new_socket = std::move(socket);
std::cout << "Accepted" << std::endl;
}
static void on_accept (boost::asio::ip::tcp::acceptor & acceptor,
boost::asio::ip::tcp::socket & socket,
boost::system::error_code const & error)
{
if (error)
{
std::cerr << error << ' ' << error.message() << std::endl;
return ;
}
callback(std::move(socket));
acceptor.async_accept
(
socket,
std::bind(on_accept, std::ref(acceptor), std::ref(socket), std::placeholders::_1)
);
}
int main ()
{
boost::asio::io_service service;
boost::asio::io_service::work work { service };
boost::asio::ip::tcp::acceptor acceptor { service };
boost::asio::ip::tcp::socket socket { service };
boost::asio::ip::tcp::endpoint endpoint { boost::asio::ip::tcp::v4(), 5555 };
boost::system::error_code ec;
using socket_base = boost::asio::socket_base;
auto option = socket_base::reuse_address { false };
if (acceptor.open(endpoint.protocol(), ec) ||
acceptor.set_option(option, ec) ||
acceptor.bind(endpoint, ec) ||
acceptor.listen(socket_base::max_connections, ec) ||
acceptor.is_open() == false)
return 1;
acceptor.async_accept
(
socket,
std::bind(on_accept, std::ref(acceptor), std::ref(socket), std::placeholders::_1)
);
service.run();
}
当我将客户端连接到它时,出现错误:
Accepted
system:1 Incorrect function
(当 callback()
函数中的 socket
对象被销毁时,调用 on_accept()
函数并显示错误代码。
此外,客户端根本没有断开连接。
如果我取消注释 callback()
函数中的行,一切正常,没有错误消息,并且客户端按预期断开连接。
现在进行环境设置,我在 Windows 8.1 下,使用 MinGW-w64 v4.9.2 编译器和使用相同编译器编译的 Boost.Asio v1.58.0。
用于编译文件的命令行如下:
$ g++ -std=c++14 -IC:/C++/boost/1.58.0 main.cpp -LC:/C++/boost/1.58.0/lib -lboost_system-mgw49-mt-1_58 -lwsock32 -lws2_32 -o test.exe
请注意,使用 Boost 1.57.0 会导致相同的行为。
我也可以完全删除注释行,然后使用这个:
static void callback (boost::asio::ip::tcp::socket && socket)
{
std::cout << "Accepted" << std::endl;
socket.shutdown(socket.shutdown_both);
socket.close();
}
程序也能正常运行。
那么,为什么我需要添加额外的步骤才能在此处不出现错误? IIRC 几个月前,当我第一次尝试该程序时,这种行为并不存在。
代码只创建一个socket,它是一个自动变量,其生命周期将结束一次main()
returns。 std::move(socket)
只有 return 一个可以提供给套接字 move constructor 的 xvalue;它不构造套接字。
要解决此问题,请考虑将 callback()
签名更改为通过值接受套接字,从而允许编译器在给定 xvalue 时为您调用移动构造函数。变化:
static void callback (boost::asio::ip::tcp::socket && socket)
至:
static void callback (boost::asio::ip::tcp::socket socket)
总的来说,代码的流程如下:
void callback(socket&&); // rvalue reference.
void on_accept(acceptor&, socket&, ...) // lvalue reference.
{
...
callback(static_cast<socket&&>(socket)); // Cast to xvalue.
acceptor.async_accept(socket,
std::bind(&on_accept, std::ref:acceptor),
std::ref(socket), // lvalue reference.
...);
}
int main()
{
boost::asio::io_service io_service;
boost::asio::io_service::work work(io_service);
boost::asio::ip::tcp::acceptor acceptor(io_service);
boost::asio::ip::tcp::socket socket(io_service); // Constructor.
...
acceptor.async_accept(socket,
std::bind(&on_accept, std::ref:acceptor),
std::ref(socket), // lvalue reference.
...);
io_service.run();
}
成功接受第一个连接后,main()
中的套接字打开。 on_accept()
函数使用 xvalue 调用 callback()
,并且不会更改套接字的状态。使用已打开的套接字启动另一个 async_accept()
操作,立即导致操作失败。 async_accept()
操作失败,调用 on_accept()
将提前 return,停止其调用链。由于 io_service::work
附加到 io_service
,执行永远不会从 io_service::run()
执行 return,从而防止 main()
从 returning 和销毁套接字。最终结果是不再接受连接(没有 async_accept()
操作)并且客户端没有断开连接(socket
永远不会被销毁)。
当 callback()
将套接字的状态更改为关闭时,问题不再存在,因为满足 async_accept()
的先决条件。其他示例满足此前提条件,因为:
- 取消注释一行会导致调用移动构造函数。移出的套接字将具有与使用
socket(io_service&)
构造函数构建时相同的状态。
- 套接字已通过
socket.close()
显式关闭。
考虑这个测试程序:
#include <boost/asio/io_service.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <functional>
#include <iostream>
static void callback (boost::asio::ip::tcp::socket && socket)
{
//boost::asio::ip::tcp::socket new_socket = std::move(socket);
std::cout << "Accepted" << std::endl;
}
static void on_accept (boost::asio::ip::tcp::acceptor & acceptor,
boost::asio::ip::tcp::socket & socket,
boost::system::error_code const & error)
{
if (error)
{
std::cerr << error << ' ' << error.message() << std::endl;
return ;
}
callback(std::move(socket));
acceptor.async_accept
(
socket,
std::bind(on_accept, std::ref(acceptor), std::ref(socket), std::placeholders::_1)
);
}
int main ()
{
boost::asio::io_service service;
boost::asio::io_service::work work { service };
boost::asio::ip::tcp::acceptor acceptor { service };
boost::asio::ip::tcp::socket socket { service };
boost::asio::ip::tcp::endpoint endpoint { boost::asio::ip::tcp::v4(), 5555 };
boost::system::error_code ec;
using socket_base = boost::asio::socket_base;
auto option = socket_base::reuse_address { false };
if (acceptor.open(endpoint.protocol(), ec) ||
acceptor.set_option(option, ec) ||
acceptor.bind(endpoint, ec) ||
acceptor.listen(socket_base::max_connections, ec) ||
acceptor.is_open() == false)
return 1;
acceptor.async_accept
(
socket,
std::bind(on_accept, std::ref(acceptor), std::ref(socket), std::placeholders::_1)
);
service.run();
}
当我将客户端连接到它时,出现错误:
Accepted
system:1 Incorrect function
(当 callback()
函数中的 socket
对象被销毁时,调用 on_accept()
函数并显示错误代码。
此外,客户端根本没有断开连接。
如果我取消注释 callback()
函数中的行,一切正常,没有错误消息,并且客户端按预期断开连接。
现在进行环境设置,我在 Windows 8.1 下,使用 MinGW-w64 v4.9.2 编译器和使用相同编译器编译的 Boost.Asio v1.58.0。
用于编译文件的命令行如下:
$ g++ -std=c++14 -IC:/C++/boost/1.58.0 main.cpp -LC:/C++/boost/1.58.0/lib -lboost_system-mgw49-mt-1_58 -lwsock32 -lws2_32 -o test.exe
请注意,使用 Boost 1.57.0 会导致相同的行为。
我也可以完全删除注释行,然后使用这个:
static void callback (boost::asio::ip::tcp::socket && socket)
{
std::cout << "Accepted" << std::endl;
socket.shutdown(socket.shutdown_both);
socket.close();
}
程序也能正常运行。
那么,为什么我需要添加额外的步骤才能在此处不出现错误? IIRC 几个月前,当我第一次尝试该程序时,这种行为并不存在。
代码只创建一个socket,它是一个自动变量,其生命周期将结束一次main()
returns。 std::move(socket)
只有 return 一个可以提供给套接字 move constructor 的 xvalue;它不构造套接字。
要解决此问题,请考虑将 callback()
签名更改为通过值接受套接字,从而允许编译器在给定 xvalue 时为您调用移动构造函数。变化:
static void callback (boost::asio::ip::tcp::socket && socket)
至:
static void callback (boost::asio::ip::tcp::socket socket)
总的来说,代码的流程如下:
void callback(socket&&); // rvalue reference.
void on_accept(acceptor&, socket&, ...) // lvalue reference.
{
...
callback(static_cast<socket&&>(socket)); // Cast to xvalue.
acceptor.async_accept(socket,
std::bind(&on_accept, std::ref:acceptor),
std::ref(socket), // lvalue reference.
...);
}
int main()
{
boost::asio::io_service io_service;
boost::asio::io_service::work work(io_service);
boost::asio::ip::tcp::acceptor acceptor(io_service);
boost::asio::ip::tcp::socket socket(io_service); // Constructor.
...
acceptor.async_accept(socket,
std::bind(&on_accept, std::ref:acceptor),
std::ref(socket), // lvalue reference.
...);
io_service.run();
}
成功接受第一个连接后,main()
中的套接字打开。 on_accept()
函数使用 xvalue 调用 callback()
,并且不会更改套接字的状态。使用已打开的套接字启动另一个 async_accept()
操作,立即导致操作失败。 async_accept()
操作失败,调用 on_accept()
将提前 return,停止其调用链。由于 io_service::work
附加到 io_service
,执行永远不会从 io_service::run()
执行 return,从而防止 main()
从 returning 和销毁套接字。最终结果是不再接受连接(没有 async_accept()
操作)并且客户端没有断开连接(socket
永远不会被销毁)。
当 callback()
将套接字的状态更改为关闭时,问题不再存在,因为满足 async_accept()
的先决条件。其他示例满足此前提条件,因为:
- 取消注释一行会导致调用移动构造函数。移出的套接字将具有与使用
socket(io_service&)
构造函数构建时相同的状态。 - 套接字已通过
socket.close()
显式关闭。