Boost.Asio TCP移动到套接字析构函数不够干净关闭?

时间:2015-04-29 14:49:04

标签: c++ sockets boost boost-asio move-semantics

考虑这个测试程序:

#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();
}

当我将客户端连接到它时,我收到错误:

  

接受
  system:1功能不正确

(当on_accept()函数中的socket对象被销毁时,将使用错误代码调用callback()函数。

此外,客户端根本没有断开连接。

如果我取消注释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当我第一次尝试该程序时,这种行为并不存在。

1 个答案:

答案 0 :(得分:2)

代码只创建一个套接字,这是一个自动变量,其生命周期将在main()返回后结束。 std::move(socket)仅返回可以提供给套接字move constructor的xvalue;它不构造套接字。

要解决此问题,请考虑将callback()签名更改为通过值接受套接字,允许编译器在给定xvalue时为您调用move-constructor。变化:

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(),停止其调用链。由于io_service::work附加到io_service,执行永远不会从io_service::run()返回,从而阻止main()返回并销毁套接字。最终结果是不再接受连接(没有async_accept()操作)并且客户端未断开连接(socket永远不会被销毁)。

callback()更改套接字的状态以关闭时,问题不再存在,因为满足async_accept()的前提条件。其他例子符合这一先决条件,因为:

  • 取消注释一行会导致move-constructor被调用。移动的套接字将具有与使用socket(io_service&)构造函数构造的状态相同的状态。
  • 套接字通过socket.close()明确关闭。