Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

boost asio multiple async_send on udp socket

Is it safe to have fire and forget approach to udp socket in boost::asio?

so e.g if i have such code

 socket.async_send(buffer(somedata1),write_handler);
 socket.async_send(buffer(somedata2),write_handler);
 socket.async_send(buffer(somedata3),write_handler);
 socket.async_send(buffer(somedata4),write_handler);

am i guaranteed that this will not fail - that means that at the receiving endpoint i will get 4 packets containing somedata1,somedata2,somedata3,somedata4 ?

like image 725
javascrub Avatar asked Mar 22 '23 21:03

javascrub


2 Answers

No, it most certainly isn't safe, none of the asio async_* functions are documented to be "fire and forget".

The boost asio reference for the basic_datagram_socket::async_send buffers states: “Although the buffers object may be copied as necessary, ownership of the underlying memory blocks is retained by the caller, which must guarantee that they remain valid until the handler is called.”

If you need a "fire and forget" approach, then you'll need to a class to manage your connection and buffer the packets for you. Here's an example using a deque to buffer the packets:

class Connection : public boost::enable_shared_from_this<Connection>
{
  boost::asio::ip::udp::socket socket_;
  std::deque<std::vector<char> > tx_queue_;

  /// transmit the packet at the head of the queue
  void transmit()
  {
    socket_.async_send(
      boost::asio::buffer(&tx_queue_.front()[0], tx_queue_.front().size()),
      boost::bind(&Connection::write_callback,
                  boost::weak_ptr<Connection>(shared_from_this()),
                  boost::asio::placeholders::error,
                  boost::asio::placeholders::bytes_transferred));
  }

  /// The function called whenever a write event is received.
  void write_handler(boost::system::error_code const& error,
                     size_t /* bytes_transferred */)
  {
    tx_queue_.pop_front();
    if (error)
      ; // handle the error, it may be a disconnect.
    else
      if (!tx_queue_.empty())
        transmit();
  }

  /// Static callback function.
  /// It ensures that the object still exists and the event is valid
  /// before calling the write handler.
  static void write_callback(boost::weak_ptr<Connection> ptr,
                             boost::system::error_code const& error,
                             size_t bytes_transferred)
  {
    boost::shared_ptr<Connection> pointer(ptr.lock());
    if (pointer && (boost::asio::error::operation_aborted != error))
      pointer->write_handler(error, bytes_transferred);
  }

  /// Private constructor to enusure the class is created as a shared_ptr.
  explicit Connection(boost::asio::io_service& io_service) :
    socket_(io_service),
    tx_queue_()
  {}

public:

  /// Factory method to create an instance of this class.
  static boost::shared_ptr<Connection> create(boost::asio::io_service& io_service)
  { return boost::shared_ptr<Connection>(new Connection(io_service)); }

  /// Destructor, closes the socket to cancel the write callback 
  /// (by calling it with error = boost::asio::error::operation_aborted)
  /// and free the weak_ptr held by the call to bind in the transmit function.
  ~Connection()
  { socket_.close(); }

  /// add the packet to the end of the queue and send it ASAP.
#if defined(BOOST_ASIO_HAS_MOVE)
  void send(std::vector<char>&& packet)
#else
  void send(const std::vector<char>& packet)
#endif
  {
    bool queue_empty(tx_queue_.empty());
    tx_queue_.push_back(packet);
    if (queue_empty)
      transmit();
  }
};
like image 151
kenba Avatar answered Mar 27 '23 23:03

kenba


There is no guarantee simply because the underlying protocol makes no guarantee.

As long as the underlying memory provided to buffer during socket.async_send() remains valid until the handler is called, and no other thread is making concurrent calls to socket, then the criteria for socket.async_send() has been satisfied and it should be safe.


For implementation details, the basic_datagram_socket::async_send() will have its service create a single non-composed operation (reactive_socket_send_op or win_iocp_socket_send_op). The service will then have its reactor start the operation. Some reactor implementations may attempt to run the operation immediately; otherwise, the operation is added to a queue specific to the socket file descriptor based on the operation type (read or write). The operation queues allow for multiple read or write operations to be outstanding for a given file descriptor.

like image 23
Tanner Sansbury Avatar answered Mar 28 '23 01:03

Tanner Sansbury