How to connect to an unix domain socket with boost::asio? - c++

I want to create and connect to an unix domain socket of type SOCK_SEQPACKET by specifying the path name of the socket endpoint, but this fails to compile in boost::asio v1.60:
using namespace boost::asio::generic;
seq_packet_protocol proto{AF_UNIX, IPPROTO_SCTP}; // SOCK_SEQPACKET
seq_packet_protocol::socket sock(io_service, proto);
boost::asio::local::basic_endpoint<seq_packet_protocol> ep("/tmp/socket");
sock.connect(ep); // does not compile
do you know how to properly create an unix domain socket?

I suggest to keep it simple:
#include <boost/asio.hpp>
int main() {
boost::asio::io_service io_service;
using boost::asio::local::stream_protocol;
stream_protocol::socket s(io_service);
s.connect("/tmp/socket");
}
No doubt you can go more lowlevel, but I'm not sure when you'd need that.
UPDATE Mimicking the pre-defined stream_protocol, here's how to define seqpacket_protocol:
Live On Coliru
namespace SeqPacket {
using namespace boost::asio::local;
struct seqpacket_protocol
{
int type() const { return IPPROTO_SCTP; }
int protocol() const { return 0; }
int family() const { return AF_UNIX; }
typedef basic_endpoint<seqpacket_protocol> endpoint;
typedef boost::asio::basic_stream_socket<seqpacket_protocol> socket;
typedef boost::asio::basic_socket_acceptor<seqpacket_protocol> acceptor;
#if !defined(BOOST_ASIO_NO_IOSTREAM)
/// The UNIX domain iostream type.
typedef boost::asio::basic_socket_iostream<seqpacket_protocol> iostream;
#endif // !defined(BOOST_ASIO_NO_IOSTREAM)
};
}
Just use it in the same pattern:
int main() {
boost::asio::io_service io_service;
using SeqPacket::seqpacket_protocol;
seqpacket_protocol::socket s(io_service);
s.connect("socket");
}

To add on to sehe's answer, the socket definition is wrong, it should be:
typedef boost::asio::basic_seq_packet_socket<seqpacket_protocol> socket;
putting it together:
namespace SeqPacket {
struct seqpacket_protocol
{
int type() const { return SOCK_SEQPACKET; }
int protocol() const { return 0; }
int family() const { return AF_UNIX; }
typedef boost::asio::local::basic_endpoint<seqpacket_protocol> endpoint;
typedef boost::asio::basic_seq_packet_socket<seqpacket_protocol> socket;
typedef boost::asio::basic_socket_acceptor<seqpacket_protocol> acceptor;
#if !defined(BOOST_ASIO_NO_IOSTREAM)
/// The UNIX domain iostream type.
typedef boost::asio::basic_socket_iostream<seqpacket_protocol> iostream;
#endif // !defined(BOOST_ASIO_NO_IOSTREAM)
};
}

Related

Boost ASIO deserialization wrong values

when I try to deserialize a struct on the server, I am receiving incorrect values from initial transmission. I created a struct with member variables of the same type (2 and 1); however, when transmitting receive zeros and unsure why.
Does the incoming data in connection.hpp need to be of the same type, which I am sending? Any suggestions ?
Client:
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <iostream>
#include <vector>
#include "connection.hpp" // Must come before boost/serialization headers.
#include <boost/serialization/vector.hpp>
#include "info.hpp"
class client
{
public:
/// Constructor starts the asynchronous connect operation.
client(boost::asio::io_service& io_service,
const std::string& host, const std::string& service)
: connection_(io_service)
{
Packet p;
p.a = 2;
p.b = 1;
packet_.push_back(p);
// Resolve the host name into an IP address.
boost::asio::ip::tcp::resolver resolver(io_service);
boost::asio::ip::tcp::resolver::query query(host, service);
boost::asio::ip::tcp::resolver::iterator endpoint_iterator =
resolver.resolve(query);
// Start an asynchronous connect operation.
boost::asio::async_connect(connection_.socket(), endpoint_iterator,
boost::bind(&client::handle_connect, this,
boost::asio::placeholders::error));
}
/// Handle completion of a connect operation.
void handle_connect(const boost::system::error_code& e)
{
if (!e)
{
connection_.async_write(packet_,
boost::bind(&client::handle_write, this,
boost::asio::placeholders::error));
}
else
{
std::cerr << e.message() << std::endl;
}
}
void handle_write(const boost::system::error_code& e)
{
}
private:
connection connection_;
std::vector<Packet> packet_;
};
}
int main(int argc, char* argv[])
{
try
{
// Check command line arguments.
if (argc != 3)
{
std::cerr << "Usage: client <host> <port>" << std::endl;
return 1;
}
boost::asio::io_service io_service;
s11n_example::client client(io_service, argv[1], argv[2]);
io_service.run();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
Info:
#ifndef Packet_HPP
#define Packet_HPP
namespace s11n_example {
struct Packet
{
int a;
int b;
template <typename Archive>
void serialize(Archive& ar, const unsigned int version)
{
int a;
int b;
}
};
} // namespace s11n_example
#endif
Server:
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/lexical_cast.hpp>
#include <iostream>
#include <vector>
#include "connection.hpp" // Must come before boost/serialization headers.
#include <boost/serialization/vector.hpp>
#include "info.hpp"
namespace s11n_example {
/// Serves stock quote information to any client that connects to it.
class server
{
public:
/// Constructor opens the acceptor and starts waiting for the first incoming
/// connection.
server(boost::asio::io_service& io_service, unsigned short port)
: acceptor_(io_service,
boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port))
{
// Create the data to be sent to each client.
// Start an accept operation for a new connection.
connection_ptr new_conn(new connection(acceptor_.get_io_service()));
acceptor_.async_accept(new_conn->socket(),
boost::bind(&server::handle_accept, this,
boost::asio::placeholders::error, new_conn));
}
/// Handle completion of a accept operation.
void handle_accept(const boost::system::error_code& e, connection_ptr conn)
{
if (!e)
{
conn->async_read(packet_,
boost::bind(&server::handle_read, this,
boost::asio::placeholders::error, conn));
}
// Start an accept operation for a new connection.
connection_ptr new_conn(new connection(acceptor_.get_io_service()));
acceptor_.async_accept(new_conn->socket(),
boost::bind(&server::handle_accept, this,
boost::asio::placeholders::error, new_conn));
}
/// Handle completion of a write operation.
void handle_read(const boost::system::error_code& e, connection_ptr conn)
{
// Print out the data that was received.
for (std::size_t i = 0; i < packet_.size(); ++i)
std::cout << packet_[i].a << "\n";
}
private:
/// The acceptor object used to accept incoming socket connections.
boost::asio::ip::tcp::acceptor acceptor_;
/// The data to be sent to each client.
std::vector<Packet> packet_;
};
} // namespace s11n_example
int main(int argc, char* argv[])
{
try
{
// Check command line arguments.
if (argc != 2)
{
std::cerr << "Usage: server <port>" << std::endl;
return 1;
}
unsigned short port = boost::lexical_cast<unsigned short>(argv[1]);
boost::asio::io_service io_service;
s11n_example::server server(io_service, port);
io_service.run();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
Connection
#ifndef SERIALIZATION_CONNECTION_HPP
#define SERIALIZATION_CONNECTION_HPP
#include <boost/asio.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/tuple/tuple.hpp>
#include <iomanip>
#include <string>
#include <sstream>
#include <vector>
namespace s11n_example {
/// The connection class provides serialization primitives on top of a socket.
/**
* Each message sent using this class consists of:
* #li An 8-byte header containing the length of the serialized data in
* hexadecimal.
* #li The serialized data.
*/
class connection
{
public:
/// Constructor.
connection(boost::asio::io_service& io_service)
: socket_(io_service)
{
}
/// Get the underlying socket. Used for making a connection or for accepting
/// an incoming connection.
boost::asio::ip::tcp::socket& socket()
{
return socket_;
}
/// Asynchronously write a data structure to the socket.
template <typename T, typename Handler>
void async_write(const T& t, Handler handler)
{
// Serialize the data first so we know how large it is.
std::ostringstream archive_stream;
boost::archive::text_oarchive archive(archive_stream);
archive << t;
outbound_data_ = archive_stream.str();
// Format the header.
std::ostringstream header_stream;
header_stream << std::setw(header_length)
<< std::hex << outbound_data_.size();
if (!header_stream || header_stream.str().size() != header_length)
{
// Something went wrong, inform the caller.
boost::system::error_code error(boost::asio::error::invalid_argument);
socket_.get_io_service().post(boost::bind(handler, error));
return;
}
outbound_header_ = header_stream.str();
// Write the serialized data to the socket. We use "gather-write" to send
// both the header and the data in a single write operation.
std::vector<boost::asio::const_buffer> buffers;
buffers.push_back(boost::asio::buffer(outbound_header_));
buffers.push_back(boost::asio::buffer(outbound_data_));
boost::asio::async_write(socket_, buffers, handler);
}
/// Asynchronously read a data structure from the socket.
template <typename T, typename Handler>
void async_read(T& t, Handler handler)
{
// Issue a read operation to read exactly the number of bytes in a header.
void (connection::*f)(
const boost::system::error_code&,
T&, boost::tuple<Handler>)
= &connection::handle_read_header<T, Handler>;
boost::asio::async_read(socket_, boost::asio::buffer(inbound_header_),
boost::bind(f,
this, boost::asio::placeholders::error, boost::ref(t),
boost::make_tuple(handler)));
}
/// Handle a completed read of a message header. The handler is passed using
/// a tuple since boost::bind seems to have trouble binding a function object
/// created using boost::bind as a parameter.
template <typename T, typename Handler>
void handle_read_header(const boost::system::error_code& e,
T& t, boost::tuple<Handler> handler)
{
if (e)
{
boost::get<0>(handler)(e);
}
else
{
// Determine the length of the serialized data.
std::istringstream is(std::string(inbound_header_, header_length));
std::size_t inbound_data_size = 0;
if (!(is >> std::hex >> inbound_data_size))
{
// Header doesn't seem to be valid. Inform the caller.
boost::system::error_code error(boost::asio::error::invalid_argument);
boost::get<0>(handler)(error);
return;
}
// Start an asynchronous call to receive the data.
inbound_data_.resize(inbound_data_size);
void (connection::*f)(
const boost::system::error_code&,
T&, boost::tuple<Handler>)
= &connection::handle_read_data<T, Handler>;
boost::asio::async_read(socket_, boost::asio::buffer(inbound_data_),
boost::bind(f, this,
boost::asio::placeholders::error, boost::ref(t), handler));
}
}
/// Handle a completed read of message data.
template <typename T, typename Handler>
void handle_read_data(const boost::system::error_code& e,
T& t, boost::tuple<Handler> handler)
{
if (e)
{
boost::get<0>(handler)(e);
}
else
{
// Extract the data structure from the data just received.
try
{
std::string archive_data(&inbound_data_[0], inbound_data_.size());
std::istringstream archive_stream(archive_data);
boost::archive::text_iarchive archive(archive_stream);
archive >> t;
}
catch (std::exception& e)
{
// Unable to decode data.
boost::system::error_code error(boost::asio::error::invalid_argument);
boost::get<0>(handler)(error);
return;
}
// Inform caller that data has been received ok.
boost::get<0>(handler)(e);
}
}
private:
/// The underlying socket.
boost::asio::ip::tcp::socket socket_;
/// The size of a fixed length header.
enum { header_length = 8 };
/// Holds an outbound header.
std::string outbound_header_;
/// Holds the outbound data.
std::string outbound_data_;
/// Holds an inbound header.
char inbound_header_[header_length];
/// Holds the inbound data.
std::vector<char> inbound_data_;
};
typedef boost::shared_ptr<connection> connection_ptr;
} // namespace s11n_example
#endif // SERIALIZATION_CONNECTION_HPP
Like last time, this function makes no sense:
template <typename Archive>
void serialize(Archive& ar, const unsigned int version)
{
int a;
int b;
}
It just declares to integers and doesn't even touch the archive. Like before the fix for that is to ... serialize the data:
struct Packet {
int a, b;
template <typename Ar>
void serialize(Ar& ar, unsigned) { ar& a& b; }
};
Also, like last time, you're using a very old version of the sample:
1.49 is very old. It's very unlikely that's actually the version you are using. Try a more recent version (like https://www.boost.org/doc/libs/1_80_0/doc/html/boost_asio/examples/cpp03_examples.html#boost_asio.examples.cpp03_examples.serialization) or the one matching your version of boost.

Avoiding global variables making boost asio socket wrapper

i have this socket-tcp.h with an wrapper of socket implementation using boost asio:
#include <boost/asio.hpp>
#include <iostream>
#include <boost/thread.hpp>
static const std::string PORT = "65432";
static const std::string HOST = "127.0.0.1";
struct Client
{
boost::asio::io_service& io_service;
boost::asio::ip::tcp::socket socket;
Client(boost::asio::io_service& svc, std::string const& host, std::string const& port)
: io_service(svc), socket(io_service)
{
boost::asio::ip::tcp::resolver resolver(io_service);
boost::asio::ip::tcp::resolver::iterator endpoint = resolver.resolve(boost::asio::ip::tcp::resolver::query(host, port));
boost::asio::connect(this->socket, endpoint);
};
void send(std::string const& message) {
socket.send(boost::asio::buffer(message));
}
};
boost::asio::io_service svc;
Client client(svc, HOST, PORT);
class TcpSocket
{
private:
std::string HOST;
std::string PORT;
public:
TcpSocket (std::string const& host, std::string const& port): HOST{ host },PORT{ port }
{
}
void send(std::string const& message)
{
boost::thread t(client_thread,boost::ref(message),boost::ref(HOST),boost::ref(PORT));
t.join();
}
static void client_thread(std::string const& message,std::string const& host,std::string const& port)
{
client.send(message);
}
};
so that my main file looks like:
#include "socket-tcp.h"
int main()
{
TcpSocket socket(PORT,HOST);
std::string message = "socket implemented using global variables";
while (true)
{
socket.send(message);
}
}
I'm trying to figure out a way of implement this without the global variables
boost::asio::io_service svc;
Client client(svc, HOST, PORT);
such that TcpSocket be like:
class TcpSocket
{
//Object* myObject; // Will not try to call the constructor or do any initializing
//myObject = new Object(...); // Initialised now
private:
std::string HOST;
std::string PORT;
boost::asio::io_service svc;
public:
TcpSocket (std::string const& host, std::string const& port): HOST{ host },PORT{ port }
{
Client client(svc, HOST, PORT);
}
void send(std::string const& message)
{
boost::thread t(client_thread,boost::ref(message),boost::ref(HOST),boost::ref(PORT));
t.join();
}
static void client_thread(std::string const& message,std::string const& host,std::string const& port)
{
client.send(message);
}
};
but i end up with the runtime error:
terminate called after throwing an instance of 'boost::exception_detail::clone_impl<boost::exception_detail::error_info_injector<boost::system::system_error> >'
what(): resolve: Service not found
is there a way to avoid using these global variables (objects), and keeping the same socket open all the time, without closing and opening it again at each new message?
I accept better implementations or suggestions for this wrapper, but the goal is to keep main as simple and clear as possible.
The "service not found" merely means that the port is not a valid service. That's because you swapped host and port parameters.
But that is the least of your worries.
You're playing fast and loose with object lifetimes. For example, you pass message all the way to another thread &&by reference** (std::string const&), but the object referenced lives on the stack (as a function argument) so that invokes Undefined Behaviour.
Besides, it's not clear how the thread (or client_thread) is supposed to access the Client instance (that your code constructs as a local variable in the TcpSocket constructor only). Let alone that it would be unsafe to share that client across threads.
Medium Fix
Live On Coliru
#include <boost/asio.hpp>
#include <iostream>
#include <boost/thread.hpp>
static const std::string HOST = "127.0.0.1";
static const std::string PORT = "65432";
using boost::asio::ip::tcp;
struct Client
{
tcp::socket socket;
//template <typename Executor>
using Executor = boost::asio::io_service&;
Client(Executor executor, std::string const& host, std::string const& port)
: socket(executor)
{
std::cout << std::quoted(host) << std::endl;
std::cout << std::quoted(port) << std::endl;
auto ep = tcp::resolver{socket.get_executor()}.resolve(host, port);
connect(socket, ep);
};
void send(std::string const& message) {
socket.send(boost::asio::buffer(message));
}
};
class TcpSocket {
private:
std::string _host;
std::string _port;
boost::asio::io_service _svc;
public:
TcpSocket(std::string const& host, std::string const& port)
: _host{host}
, _port{port}
{
}
void send(std::string const& message)
{
boost::thread t( //
client_thread, boost::ref(_svc), message, _host, _port);
t.join();
}
static void client_thread( //
boost::asio::io_service& svc,
std::string message, // NOT A REFERENCE
std::string host, // same
std::string port)
{
Client client(svc, host, port);
client.send(message);
}
};
//#include "socket-tcp.h"
int main()
{
TcpSocket socket(HOST, PORT);
std::string const message = "socket implemented using global variables";
while (true) {
socket.send(message);
}
}
More Notes
The thread is by definition useless, since you join it immediately. But if you really wanted that, please consider c++11 style:
void send(std::string const& message)
{
boost::thread t([=, &_svc] {
Client client(_svc, _host, _port);
client.send(message);
});
t.join();
}
It makes far more sense to do it on the main thread:
void send(std::string const& message)
{
Client client(_svc, _host, _port);
client.send(message);
}
Or to use async IO. Note that you should probably prefer to pass executors rather than sharing references to an execution context. Also, prefer io_context because io_service is deprecated.
Here's a simplified program that still does the same: Live On Coliru
BONUS - Async With A Thread Pool
Seems like you wanted the messages to be delivered in async fashion. However, you didn't know how to get threads in the background. Regardless, it wouldn't be a safe idea to "just create a new thread" each time. The professional approach is to use a thread pool.
This example uses a ddefault boost::asio::thread_pool and throws in a strand (even though it's not currently required) and a Session object to hold all the state for a single message. Note that we now cache the resolver results. That can be a good thing ,or a bad thing depending on your application.
Live On Coliru
#include <boost/asio.hpp>
#include <iostream>
#include <boost/thread.hpp>
#include <boost/enable_shared_from_this.hpp>
using boost::asio::ip::tcp;
using boost::system::error_code;
class TcpClient {
public:
TcpClient(std::string const& host, std::string const& port)
: _ep(tcp::resolver{_io}.resolve(host, port))
{ }
void send(std::string message)
{
boost::make_shared<Session>( //
make_strand(_io.get_executor()), std::move(message), _ep)
->run();
}
~TcpClient() {
//_io.stop(); // optionally
_io.join();
}
private:
boost::asio::thread_pool _io;
tcp::resolver::results_type _ep ;
struct Session : boost::enable_shared_from_this<Session> {
Session(auto executor, std::string message,
tcp::resolver::results_type ep)
: _socket(executor)
, _message(std::move(message))
, _ep(ep)
{
}
void run() {
async_connect( //
_socket, _ep,
[this, self = shared_from_this()](error_code ec,
tcp::endpoint) {
async_write(_socket, boost::asio::buffer(_message),
[this, self](error_code ec, size_t) {});
});
}
tcp::socket _socket;
std::string _message;
tcp::resolver::results_type _ep;
};
};
//#include "socket-tcp.h"
int main()
{
TcpClient socket("127.0.0.1", "65432");
for (int i = 0; i<100; ++i) {
socket.send("socket implemented using async IO on thread pool " +
std::to_string(i) + "\n");
}
}

boost ASIO server segmentation fault

I created a server using Boost ASIO. It builds fine but as soon as I run it, it gives segmentation fault. Can't really figure out this behaviour.
Also, I read that this may be due to me not initialising the io_service object explicitly. If, that's the case then how do I modify this code so that I don't have to pass io_service object from outside the class.
Below is my code:
#include <iostream>
#include <string>
#include <memory>
#include <array>
#include <boost/asio.hpp>
using namespace boost::asio;
//Connection Class
class Connection : public std::enable_shared_from_this<Connection>{
ip::tcp::socket m_socket;
std::array<char, 2056> m_acceptMessage;
std::string m_acceptMessageWrapper;
std::string m_buffer;
public:
Connection(io_service& ioService): m_socket(ioService) { }
virtual ~Connection() { }
static std::shared_ptr<Connection> create(io_service& ioService){
return std::shared_ptr<Connection>(new Connection(ioService));
}
std::string& receiveMessage() {
size_t len = boost::asio::read(m_socket, boost::asio::buffer(m_acceptMessage));
m_acceptMessageWrapper = std::string(m_acceptMessage.begin(), m_acceptMessage.begin() + len);
return m_acceptMessageWrapper;
}
void sendMessage(const std::string& message) {
boost::asio::write(m_socket, boost::asio::buffer(message));
}
ip::tcp::socket& getSocket(){
return m_socket;
}
};
//Server Class
class Server {
ip::tcp::acceptor m_acceptor;
io_service m_ioService ;
public:
Server(int port):
m_acceptor(m_ioService, ip::tcp::endpoint(ip::tcp::v4(), port)){ }
virtual ~Server() { }
std::shared_ptr<Connection> createConnection(){
std::shared_ptr<Connection> newConnection = Connection::create(m_ioService);
m_acceptor.accept(newConnection->getSocket());
return newConnection;
}
void runService() {
m_ioService.run();
}
};
int main(int argc, char* argv[]) {
Server s(5000);
auto c1 = s.createConnection();
//do soething
s.runService();
return 0;
}
You are facing initialisation order issues. In your class Server, you have declared m_acceptor before m_ioService and using the uninitialized io_service object to construct the acceptor.
Just reorder the declarations inside the class. Surprisingly clang did not give any warning for this.
class Server {
io_service m_ioService ;
ip::tcp::acceptor m_acceptor;
public:
Server(int port):
m_acceptor(m_ioService, ip::tcp::endpoint(ip::tcp::v4(), port)){ }
virtual ~Server() { }
std::shared_ptr<Connection> createConnection(){
std::shared_ptr<Connection> newConnection = Connection::create(m_ioService);
m_acceptor.accept(newConnection->getSocket());
return newConnection;
}
void runService() {
m_ioService.run();
}
};

c++ saving bound object and using it after asio

I'm trying to save the result of bind to std:function, then pass it as parameter to another function, and store it as data member. Then I use asio async_wait, but when i return from the wait, and try to operate the function i saved i get segmentation fault. any Idea why?
#include <memory>
#include <iostream>
#include <asio/io_service.hpp>
#include <functional>
#include <asio/deadline_timer.hpp>
using namespace std;
typedef std::function<void (const std::error_code& error)> TM_callback;
class Timer {
public:
Timer(asio::io_service& io_service) :_timer(io_service) {}
void start(TM_callback cb) {
_cb = cb;
_timer.expires_from_now(boost::posix_time::milliseconds(1000));
TM_callback timeoutFunc = std::bind(&Timer::onTimeout, this, std::placeholders::_1);
_timer.async_wait(timeoutFunc);
}
private:
void onTimeout(const std::error_code& error) {
(_cb)(error); // <-- here i get segmentation fault
}
TM_callback _cb;
asio::deadline_timer _timer;
};
class COL {
public:
COL(asio::io_service& io_service): _inTimer(io_service){}
void startInTimer() {
TM_callback cb = std::bind(&COL::onInTimeout, this, std::placeholders::_1);
_inTimer.start(cb);
}
private:
void onInTimeout(const std::error_code& error) {cout<<error.message();}
Timer _inTimer;
};
int main()
{
asio::io_service io_service;
COL col(io_service);
col.startInTimer();
return 0;
}
Ok, the most likely problem is in the code you don't show. As you can see #m.s. didn't "imagine" your problem. He forgot the io_service::run() too:
int main() {
asio::io_service io_service;
COL col(io_service);
col.startInTimer();
io_service.run();
}
Still no problem. Live On Coliru
The problem starts when inTimer is not guaranteed to live until the completion handler is executed:
int main() {
asio::io_service io_service;
{
COL col(io_service);
col.startInTimer();
}
io_service.run();
}
Now you have Undefined Behaviour: Live On Coliru
Solution
The easiest solution is to make the COL (what is that?) object live long enough. The more structural/idiomatic way would to let the bind keep the Timer object alive, e.g. using a shared_ptr:
Live On Coliru
#include <iostream>
#include <boost/bind.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/make_shared.hpp>
#include <boost/asio.hpp>
using namespace std;
typedef std::function<void(const boost::system::error_code &error)> TM_callback;
namespace asio = boost::asio;
class Timer : public boost::enable_shared_from_this<Timer> {
public:
Timer(asio::io_service &io_service) : _timer(io_service) {}
void start(TM_callback cb) {
_cb = cb;
_timer.expires_from_now(boost::posix_time::milliseconds(1000));
TM_callback timeoutFunc = boost::bind(&Timer::onTimeout, shared_from_this(), boost::asio::placeholders::error);
_timer.async_wait(timeoutFunc);
}
private:
void onTimeout(const boost::system::error_code &error) {
(_cb)(error);
}
TM_callback _cb;
asio::deadline_timer _timer;
};
class COL : public boost::enable_shared_from_this<COL> {
public:
COL(asio::io_service &io_service) : _svc(io_service) {}
void startInTimer() {
TM_callback cb = boost::bind(&COL::onInTimeout, shared_from_this(), boost::asio::placeholders::error);
boost::shared_ptr<Timer> _inTimer = boost::make_shared<Timer>(_svc);
_inTimer->start(cb);
}
private:
void onInTimeout(const boost::system::error_code &error) { cout << error.message(); }
asio::io_service& _svc;
};
int main() {
asio::io_service io_service;
{
boost::make_shared<COL>(io_service)->startInTimer();
}
io_service.run();
}
Note that this subtly also fixes the problem that more than one timer couldn't be in flight at a give time (scheduling a new timer would cancel the pending one).

Using boost::asio::io_service as class member field

I have class where I use boost asio library:
Header:
class TestIOService {
public:
void makeConnection();
static TestIOService getInst();
private:
TestIOService(std::string address);
std::string address;
// boost::asio::io_service service;
};
Impl:
#include <boost/asio/ip/address.hpp>
#include <boost/asio/ip/udp.hpp>
#include "TestIOService.h"
void TestIOService::makeConnection() {
boost::asio::io_service service;
boost::asio::ip::udp::socket socket(service);
boost::asio::ip::udp::endpoint endpoint(boost::asio::ip::address::from_string("192.168.1.2"), 1234);
socket.connect(endpoint);
socket.close();
}
TestIOService::TestIOService(std::string address) : address(address) { }
TestIOService TestIOService::getInst() {
return TestIOService("192.168.1.2");
}
And main:
int main(void)
{
TestIOService service = TestIOService::getInst();
service.makeConnection();
}
When I have service defined in makeConnection method with this line:
boost::asio::io_service service;
there is no problem, but when I have it as class field member(commented out in code) I get this error:
note: ‘TestIOService::TestIOService(TestIOService&&)’ is implicitly
deleted because the default definition would be ill-formed:
class TestIOService {
io_service is not copyable.
You can make it shared quickly by wrapping it in shared_ptr<io_service>, but you should really reconsider the design first.
If your class needs to be copyable, it would logically not contain the io_service object
E.g. the following sample does create two instances of the test class not sharing a connection:
Live On Coliru
#include <boost/asio.hpp>
#include <boost/make_shared.hpp>
#include <iostream>
class TestIOService {
public:
void makeConnection();
static TestIOService getInst();
private:
TestIOService(std::string address);
std::string address;
boost::shared_ptr<boost::asio::ip::udp::socket> socket;
boost::shared_ptr<boost::asio::io_service> service;
};
void TestIOService::makeConnection() {
using namespace boost::asio;
service = boost::make_shared<io_service>();
socket = boost::make_shared<ip::udp::socket>(*service);
socket->connect({ip::address::from_string("192.168.1.2"), 1234 });
//socket->close();
}
TestIOService::TestIOService(std::string address)
: address(address) { }
TestIOService TestIOService::getInst() {
return TestIOService("192.168.1.2");
}
int main() {
auto test1 = TestIOService::getInst();
auto test2 = TestIOService::getInst();
}