Перемещение сокета с помощью std :: move () - PullRequest
0 голосов
/ 02 ноября 2018

Моя проблема связана с перемещением сокетов Boost.Asio с использованием std::move.

Я работаю на сервере в C ++ с переключаемыми реализациями и библиотеками (Boost.Asio, POSIX-сокеты, Winsock-сокеты). Для этого у меня есть интерфейсы HttpServer и HttpClient, которые реализуются такими классами, как PosixHttpServer, PosixHttpClient, BoostHttpServer, BoostHttpClient. После привязки и прослушивания на сервере HttpClient будет создан HttpServer::accept(). Я удостоверился, что отключил копирование на HttpServer и HttpClient. Я не планирую сейчас использовать асинхронные операции.

// main.cpp

using HttpServerPtr = std::unique_ptr<HttpServer>;
using HttpClientPtr = std::unique_ptr<HttpClient>;

...

int main(int argc, char* argv[])
{
    ...

   //HttpServerPtr server = std::make_unique<PosixHttpServer>();
   HttpServerPtr server = std::make_unique<BoostHttpServer>();
   if (!server->bind(server_port)
   {
       ...
   }

   if (!server->listen())
   {
       ...
   }
   std::cout << "Listening to client connections at "
             << server->getAddress()
             << " on port " << std::to_string(server_port)
             << std::endl;

   while (1)
   {
       HttpClientPtr client = server->accept();
       if (!client)
       {
           std::cerr << "failed to accept" << std::endl;
           continue;
       }

       std::cout << "Got connection from "
                 << client->getAddress() << std::endl;

       // TODO: Write HTTP response
       if (!client->write("Hello World!"))
       {
           std::cerr << "failed to write" << std::endl;
       }
    }

    return EXIT_SUCCESS;
}

Чтобы отделить клиентский сокет от HttpServer, клиент boost::asio::ip::tcp::socket будет сохранен как элемент HttpClient. В BoostHttpServer::accept() я могу передать сокет Boost.Asio в HttpClient, если я использую std::shared_ptr (_io_service и _acceptor являются частными членами HttpServer).

// boosthttpserver.cpp
using tcp = boost::asio::ip::tcp;

bool BoostHttpServer::bind(const uint16_t port)
{
    try
    {
        tcp::resolver resolver(_io_service);
        tcp::resolver::query query(tcp::v4(), boost::asio::ip::host_name(), std::to_string(port));

        tcp::resolver::iterator available_endpoint = resolver.resolve(query);
        tcp::resolver::iterator end;
        for (; available_endpoint != end; ++available_endpoint)
        {
            tcp::endpoint localhost = *available_endpoint;
            _acceptor.open(localhost.protocol());
            _acceptor.bind(localhost);

            _address = localhost.address().to_string();
            break;
        }
    }
    catch (std::exception& e)
    {
        std::cerr << e.what() << std::endl;
        return false;
    }

    return true;
}

bool BoostHttpServer::listen()
{
    try
    {
        _acceptor.listen();
    }
    catch (std::exception& e)
    {
        std::cerr << e.what() << std::endl;
        return false;
    }

    return true;
}

HttpClientPtr BoostHttpServer::accept()
{
    try
    {
        auto client_socket = std::make_shared<tcp::socket>(_io_service);
        _acceptor.accept(*client_socket);
        return std::make_unique<BoostHttpClient>(client_socket);
    }
    catch (std::exception& e)
    {
        std::cerr << e.what() << std::endl;
        return nullptr;
    }
}


// boosthttpclient.hpp
class BoostHttpClient : public HttpClient
{
public:
    BoostHttpClient(std::shared_ptr<boost::asio::ip::tcp::socket> socket);
    BoostHttpClient(const BoostHttpClient&) = delete;
    BoostHttpClient& operator=(const BoostHttpClient&) = delete;

    virtual bool write(const std::string& message) override;
    virtual std::string getAddress() const override;

private:
    boost::asio::ip::tcp::socket _socket;
    const std::string _address;
};

// boosthttpclient.cpp
BoostHttpClient::BoostHttpClient(
    std::shared_ptr<boost::asio::ip::tcp::socket> socket
)
    : _socket(socket)
    , _address(socket->remote_endpoint().address().to_string())
{
}

Это вывод приложения, когда я запускаю сервер и использую telnet localhost 8080, например:

Listening to connections at 127.0.0.1 on port 8080
Got connection from 127.0.0.1

Однако я хочу сэкономить выделение кучи, создать client_socket в стеке и передать владение им BoostHttpClient с std::move.

.
// boosthttpserver.cpp
using tcp = boost::asio::ip::tcp;

...

HttpClientPtr BoostHttpServer::accept()
{
    try
    {
        tcp::socket client_socket(_io_service);
        _acceptor.accept(client_socket);
        return std::make_unique<BoostHttpClient>(std::move(client_socket));
    }
    catch (std::exception& e)
    {
        std::cerr << e.what() << std::endl;
        return nullptr;
    }
}


// boosthttpclient.hpp
class BoostHttpClient : public HttpClient
{
public:
    BoostHttpClient(boost::asio::ip::tcp::socket&& socket);
    BoostHttpClient(const BoostHttpClient&) = delete;
    BoostHttpClient& operator=(const BoostHttpClient&) = delete;

    virtual bool write(const std::string& message) override;
    virtual std::string getAddress() const override;

private:
    boost::asio::ip::tcp::socket _socket;
    const std::string _address;
};

// boosthttpclient.cpp
BoostHttpClient::BoostHttpClient(boost::asio::ip::tcp::socket&& socket)
    : _socket(std::move(socket))
    , _address(socket.remote_endpoint().address().to_string())
{
}

Однако розетка исчезла после переезда. Как будто его деструктор вызвали и закрыли.

Listening to connections at 127.0.0.1 on port 8080
remote_endpoint: Bad file descriptor
failed to accept

Я думал, что это может быть так же просто, как передавать другие не копируемые объекты, такие как std::thread или std::mutex, в качестве параметров конструктора, но я понимаю, что есть кое-что, что я не понимаю, что происходит в std::move(client_socket). Почему не переводится и почему закрывается сам?

Я использую Boost 1.64.0, gcc версии 7.3.1 20180712 (Red Hat 7.3.1-6) и C ++ 17, если это имеет значение.

...