AMQP-CPP/include/libboostasio.h

262 lines
8.2 KiB
C++

/**
* LibBoostAsio.h
*
* Implementation for the AMQP::TcpHandler that is optimized for boost::asio. You can
* use this class instead of a AMQP::TcpHandler class, just pass the boost asio service
* to the constructor and you're all set
*
* @author Gavin Smith <gavin.smith@coralbay.tv>
*/
/**
* Include guard
*/
#pragma once
/**
* Set up namespace
*/
namespace AMQP {
/**
* Class definition
* @note Because of a limitation with boost::asio on Windows, this will only work on POSIX based systems - see https://github.com/chriskohlhoff/asio/issues/70
*/
class LibBoostAsioHandler : public virtual TcpHandler
{
private:
/**
* Helper class that wraps a boost io_service socket monitor.
*/
class Watcher
{
private:
/**
* The boost asio io_service which is responsible for detecting events.
* @var class boost::asio::io_service&
*/
boost::asio::io_service & _ioservice;
/**
* The boost tcp socket.
* @var class boost::asio::ip::tcp::socket
* @note https://stackoverflow.com/questions/38906711/destroying-boost-asio-socket-without-closing-native-handler
*/
boost::asio::posix::stream_descriptor _socket;
/**
* A boolean that indicates if the watcher is monitoring for read events.
* @var _read True if reads are being monitored else false.
*/
bool _read{false};
/**
* A boolean that indicates if the watcher is monitoring for write events.
* @var _read True if writes are being monitored else false.
*/
bool _write{false};
/**
* Handler method that is called by boost's io_service when the socket pumps a read event.
* @param ec The status of the callback.
* @param connection The connection being watched.
* @param fd The file descriptor being watched.
* @note The handler will get called if a read is cancelled.
*/
void read_handler(boost::system::error_code ec, TcpConnection *connection, int fd)
{
if (!ec && _read)
{
connection->process(fd, AMQP::readable);
_socket.async_read_some(boost::asio::null_buffers(),
boost::bind(&Watcher::read_handler,
this,
boost::placeholders::_1,
connection,
fd));
}
}
/**
* Handler method that is called by boost's io_service when the socket pumps a write event.
* @param ec The status of the callback.
* @param connection The connection being watched.
* @param fd The file descriptor being watched.
* @note The handler will get called if a write is cancelled.
*/
void write_handler(boost::system::error_code ec, TcpConnection *connection, int fd)
{
if (!ec && _write)
{
connection->process(fd, AMQP::writable);
_socket.async_write_some(boost::asio::null_buffers(),
boost::bind(&Watcher::write_handler,
this,
boost::placeholders::_1,
connection,
fd));
}
}
public:
/**
* Constructor
* @param io_service The boost io_service
* @param connection The connection being watched
* @param fd The filedescriptor being watched
* @param events The events that should be monitored
*/
Watcher(boost::asio::io_service &io_service, TcpConnection *connection, int fd, int events) :
_ioservice(io_service),
_socket(_ioservice)
{
_socket.assign(fd);
_socket.non_blocking(true);
// configure monitoring
this->events(connection,fd,events);
}
/**
* Watchers cannot be copied or moved
*
* @param that The object to not move or copy
*/
Watcher(Watcher &&that) = delete;
Watcher(const Watcher &that) = delete;
/**
* Destructor
*/
~Watcher()
{
_socket.release();
}
/**
* Change the events for which the filedescriptor is monitored
* @param events
*/
void events(TcpConnection *connection, int fd, int events)
{
bool bRead(_read);
bool bWrite(_write);
// 1. Handle reads?
_read = ((events & AMQP::readable) != 0);
if (!bRead && _read)
{
_socket.async_read_some(boost::asio::null_buffers(),
boost::bind(&Watcher::read_handler,
this,
boost::placeholders::_1,
connection,
fd));
}
// 2. Handle writes?
_write = ((events & AMQP::writable) != 0);
if (!bWrite && _write)
{
_socket.async_write_some(boost::asio::null_buffers(),
boost::bind(&Watcher::write_handler,
this,
boost::placeholders::_1,
connection,
fd));
}
}
};
/**
* The boost asio io_service.
* @var class boost::asio::io_service&
*/
boost::asio::io_service & _ioservice;
/**
* All I/O watchers that are active, indexed by their filedescriptor
* @var std::map<int,Watcher>
*/
std::map<int, std::unique_ptr<Watcher> > _watchers;
/**
* Method that is called by AMQP-CPP to register a filedescriptor for readability or writability
* @param connection The TCP connection object that is reporting
* @param fd The filedescriptor to be monitored
* @param flags Should the object be monitored for readability or writability?
*/
void monitor(TcpConnection *connection, int fd, int flags) override
{
// do we already have this filedescriptor
auto iter = _watchers.find(fd);
// was it found?
if (iter == _watchers.end())
{
// we did not yet have this watcher - but that is ok if no filedescriptor was registered
if (flags == 0){ return; }
// construct a new pair (watcher/timer), and put it in the map
_watchers[fd] = std::make_unique<Watcher>(_ioservice, connection, fd, flags);
}
else if (flags == 0)
{
// the watcher does already exist, but we no longer have to watch this watcher
_watchers.erase(iter);
}
else
{
// Change the events on which to act.
iter->second->events(connection,fd,flags);
}
}
public:
/**
* Handler cannot be default constructed.
*
* @param that The object to not move or copy
*/
LibBoostAsioHandler() = delete;
/**
* Constructor
* @param loop The boost io_service to wrap
*/
explicit LibBoostAsioHandler(boost::asio::io_service &io_service) : _ioservice(io_service) {}
/**
* Handler cannot be copied or moved
*
* @param that The object to not move or copy
*/
LibBoostAsioHandler(LibBoostAsioHandler &&that) = delete;
LibBoostAsioHandler(const LibBoostAsioHandler &that) = delete;
/**
* Destructor
*/
~LibBoostAsioHandler() override = default;
};
/**
* End of namespace
*/
}