Моя проблема связана с перемещением сокетов 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, если это имеет значение.