Get io_context reference from a socket in Boost 1.73+ Asio - c++

How can I get boost::asio::io_context reference from a socket? Previously there were socket::get_io_service and then socket::get_io_context member functions, however now they both are deprecated. I've found the only way to do this in Boost 1.73+:
boost::asio::ip::tcp::socket socket(...);
// ...
boost::asio::io_context& io_context = static_cast<boost::asio::io_context&>(socket.get_executor().context());
This works, however looks ugly and dangerous. Is there a better way?

You would probably want to get the executor, which might be something other than the io_context.
There's a get_executor() call to do it directly:
boost::asio::io_context io;
boost::asio::ip::tcp::socket s(io);
auto ex = s.get_executor();
The executor will allow you to do most things you were probably using the io_context for.
UPDATE
To the comment, I do NOT recommend relying on the exact target of the executor you get passed in via any service object, but you can force your hand if you really don't want to update your design right now:
Live On Coliru
#include <boost/asio.hpp>
int main() {
boost::asio::io_context io;
boost::asio::ip::tcp::socket s(io);
auto ex = s.get_executor();
auto* c = ex.target<boost::asio::io_context>();
boost::asio::ip::tcp::socket more_sockets(*c);
assert(c == &io);
}
When compositing async operations, you can derive an executor from a handler using boost::asio::get_associated_executor()

Related

boost::asio, Creating a std::vector <boost::asio::ip::tcp::iostream>?

I need to extend an boost::asio::ip::tcp::iostream application to be able to connect to multiple clients. For this, i am trying to create a:
std::vector<boost::asio::ip::tcp::iostream>
and add iostreams, like this:
std::vector<boost::asio::ip::tcp::iostream> streams;
boost::asio::io_service ios;
boost::asio::ip::tcp::endpoint endpoint
= boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 4444);
boost::asio::ip::tcp::acceptor acceptor(ios, endpoint);
boost::asio::ip::tcp::iostream stream;
acceptor.accept(*stream.rdbuf());
streams.emplace_back(std::move(stream)); // gives error
the above code gives me an Attempted to reference a deleted function error.
I have tried push_back, and emplace_back as well. Is there a way to use a std::vector<boost::asio::ip::tcp::iostream>?

boost::asio crash when using a member acceptor instead of new one

I am trying to put the acceptor, socket and endpoint as members into my class but ran into crashes. Must the socket be a shared_ptr like in this Question or why does it not work?
When I'm trying to setup a acceptor on a server like this:
tcp::endpoint ep(boost::asio::ip::address::from_string(localIpAddress), portNumber);
tcp::acceptor a(io_service);
tcp::socket s(io_service);
a.open(ep.protocol());
a.bind(ep);
a.listen(MAX_CONNECTIONS);
a.async_accept(s, boost::bind(&WifiConnector::onAccept, this, boost::asio::placeholders::error));
it runs without crashing during execution, but when I try to use a socket/acceptor/endpoint that are member of my WifiConnector class it crashes.
m_acceptor.open(localEndpoint.protocol()); // it crashes in this line already
m_acceptor.bind(localEndpoint);
m_acceptor.listen(MAX_CONNECTIONS);
m_acceptor.async_accept(socket, boost::bind(&WifiConnector::onAccept, this, boost::asio::placeholders::error));
declaration in WifiConnector.hpp:
private:
tcp::socket m_socket;
tcp::acceptor m_acceptor;
tcp::endpoint m_localEndpoint;
initialization at class constructor:
WifiConnector::WifiConnector() :
io_service(),
m_socket(io_service),
m_acceptor(io_service)
{
m_localIpAddress = "192.168.137.1";
m_portNumber = 30000;
m_localEndpoint = tcp::endpoint(boost::asio::ip::address::from_string(m_localIpAddress), m_portNumber);
}
when it crashes, I get the following exeption:
boost::exception_detail::clone_impl<boost::exception_detail::error_info_injector<boost::system::system_error> >
private:
tcp::socket m_socket;
tcp::acceptor m_acceptor;
tcp::endpoint m_localEndpoint;
This will not work. You are constructing using the default constructors, which is not what you want. For one thing you want to construct using the io_service used by everything else.
Make the attributes pointers, and construct them using new when you have the io_service.

Transfer ownership of boost::asio::socket stack variable

I'm writing a simple tcp socket server capable of handling multiple concurrent connections. The idea is that the main listening thread will do a blocking accept and offload socket handles to a worker thread (in a thread pool) to handle the communication asynchronously from there.
void server::run() {
{
io_service::work work(io_service);
for (std::size_t i = 0; i < pool_size; i++)
thread_pool.push_back(std::thread([&] { io_service.run(); }));
boost::asio::io_service listener;
boost::asio::ip::tcp::acceptor acceptor(listener, ip::tcp::endpoint(ip::tcp::v4(), port));
while (listening) {
boost::asio::ip::tcp::socket socket(listener);
acceptor.accept(socket);
io_service.post([&] {callback(std::move(socket));});
}
}
for (ThreadPool::iterator it = thread_pool.begin(); it != thread_pool.end(); it++)
it->join();
}
I'm creating socket on the stack because I don't want to have to repeatedly allocate memory inside the while(listening) loop.
The callback function callback has the following prototype:
void callback(boost::asio::socket socket);
It is my understanding that calling callback(std::move(socket)) will transfer ownership of socket to callback. However when I attempt to call socket.receive() from inside callback, I get a Bad file descriptor error, so I assume something is wrong here.
How can I transfer ownership of socket to the callback function, ideally without having to create sockets on the heap?
Undefined behavior is potentially being invoked, as the lambda may be invoking std::move() on a previously destroyed socket via a dangling reference. For example, consider the case where the loop containing the socket ends its current iteration, causing socket to be destroyed, before the lambda is invoked:
Main Thread | Thread Pool
-----------------------------------+----------------------------------
tcp::socket socket(...); |
acceptor.accept(socket); |
io_service.post([&socket] {...}); |
~socket(); // end iteration |
... // next iteration | callback(std::move(socket));
To resolve this, one needs to transfer socket ownership to the handler rather than transfer ownership within the handler. Per documentation, Handlers must be CopyConstructible, and hence their arguments, including the non-copyable socket, must be as well. Yet, this requirement can be relaxed if Asio can eliminate all calls to the handler's copy constructor and one has defined BOOST_ASIO_DISABLE_HANDLER_TYPE_REQUIREMENTS.
#define BOOST_ASIO_DISABLE_HANDLER_TYPE_REQUIREMENTS
#include <boost/asio.hpp>
void callback(boost::asio::ip::tcp::socket socket);
...
// Transfer ownership of socket to the handler.
io_service.post(
[socket=std::move(socket)]() mutable
{
// Transfer ownership of socket to `callback`.
callback(std::move(socket));
});
For more details on Asio's type checking, see this answer.
Here is a complete example demonstrating a socket's ownership being transferred to a handler:
#include <functional> // std::bind
#include <utility> // std::move
#include <vector> // std::vector
#define BOOST_ASIO_DISABLE_HANDLER_TYPE_REQUIREMENTS
#include <boost/asio.hpp>
const auto noop = std::bind([]{});
void callback(boost::asio::ip::tcp::socket socket)
{
const std::string actual_message = "hello";
boost::asio::write(socket, boost::asio::buffer(actual_message));
}
int main()
{
using boost::asio::ip::tcp;
// Create all I/O objects.
boost::asio::io_service io_service;
tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), 0));
tcp::socket client_socket(io_service);
// Connect the sockets.
client_socket.async_connect(acceptor.local_endpoint(), noop);
{
tcp::socket socket(io_service);
acceptor.accept(socket);
// Transfer ownership of socket to the handler.
assert(socket.is_open());
io_service.post(
[socket=std::move(socket)]() mutable
{
// Transfer ownership of socket to `callback`.
callback(std::move(socket));
});
assert(!socket.is_open());
} // ~socket
io_service.run();
// At this point, sockets have been conencted, and `callback`
// should have written data to `client_socket`.
std::vector<char> buffer(client_socket.available());
boost::asio::read(client_socket, boost::asio::buffer(buffer));
// Verify the correct message was read.
const std::string expected_message = "hello";
assert(std::equal(
begin(buffer), end(buffer),
begin(expected_message), end(expected_message)));
}

create socket after io_service run

In all examples of using boost, usually people do the following
boost::asio::io_service io_service;
tcp::socket s1(io_service);
tcp::socket s2(io_service);
io_service.run();
But i am writing class that already has running in thread io_service and it has to create sockets with this io_service. And there is my question. How to make it thread safety?
class MySocket
{
private:
boost::asio::io_service* ioService;
tcp::socket* socket;
public:
MySocket(boost::asio::io_service* nioService,
tcp::resolver::iterator endpoint_iterator):
ioService(nioService)
{
socket = new tcp::socket(*ioService);
}
~MySocket();
};
SocketHandler handler;
handler.run(); //run io_service in thread
MySocket* s1 = handler.createSocket("localhost", "80");
//do something
MySocket* s2 = handler.createSocket("localhost", "81");
//dododo
handler.destroySocket(s1);
handler.destroySocket(s2);
You can create new sockets at any time with boost::asio.
io_service::run() blocks until working queue is empty. If it there is no work in the queue - the function returns immediately. That's why people usually add work to it (create timers, bind sockets, etc) prior to io_service::run().
BTW: I don't recommend doing this way:
MySocket* s1 = handler.createSocket("localhost", "80");
...
handler.destroySocket(s1);
use RAII-objects (smart pointers) instead.

C++ Boost.Asio object lifetimes

asio::io_service ioService;
asio::ip::tcp::socket* socket = new asio::ip::tcp::socket(ioService);
socket->async_connect(endpoint, handler);
delete socket;
Socket's destructor should close the socket. But can the asynchronous backend handle this? Will it cancel the asynchronous operation and calling the handler? Probably not?
When the socket is destroyed, it invokes destroy on its service. When a SocketService's destroy() function is invoked, it cancels asynchronous operations by calling a non-throwing close(). Handlers for cancelled operations will be posted for invocation within io_service with a boost::asio::error::operation_aborted error.
Here is a complete example demonstrating the documented behavior:
#include <iostream>
#include <boost/asio.hpp>
void handle_connect(const boost::system::error_code& error)
{
std::cout << "handle_connect: " << error.message() << std::endl;
}
int main()
{
namespace ip = boost::asio::ip;
using ip::tcp;
boost::asio::io_service io_service;
// Create socket with a scoped life.
{
tcp::socket socket(io_service);
socket.async_connect(
tcp::endpoint(ip::address::from_string("1.2.3.4"), 12345),
&handle_connect);
}
io_service.run();
}
And its output:
handle_connect: Operation canceled
Why did you create the socket using new? It won't definitely do normal process.
If you really want to create the socket using new, you have to close and delete at the end of your program.
Here is a sample, just.
io_service service_;
ip::tcp::socket sock(service_);
sock.async_connect(ep, connect_handler);
deadline_timer t(service_, boost::posix_time::seconds(5));
t.async_wait(timeout_handler);
service_.run();