Please consider following code snippet.
It first resolves address of remote host, then opens socket and sends some data to it. Note, it throws immediately when error occurs.
No concurrency involved. Message fits to 1K. Basically the only difference between this code snippet and "real" code is following: message may be sent in several seconds after endpoint was resolved and socket is opened.
using namespace boost::asio;
io_context io_context;
ip::udp::resolver resolver{io_context};
const auto endpoints = resolver.resolve(ip::udp::v4(), "host", "port");
if (endpoints.empty())
throw std::runtime_error("No endpoints found");
const auto endpoint = endpoints->endpoint();
ip::udp::socket socket{io_context};
socket.open(ip::udp::v4());
const auto message = buffer("asdf"); // fits to 1K
// may the line below fail provided the code above is executed successfully?
socket.send_to(message, endpoint);
For me, provided the endpoint is valid and socket is opened successfully, it seems that call to socket.send_to
should be always successful, even if remote host becomes unavailable (since UDP is used).
ASIO implements this in terms of underlying operating system calls. On POSIX, that would be sendto
. The possible error conditions are documented (see below).
However, first things first:
You could receive a segfault for addressing out of bounds into unknown address space. Depending on your platform it might manifest as EFAULT
(boost::asio::error::fault
).
const_buffer buffer{"asdf", 10};
The preferred spelling here is:
auto buffer = boost::asio::buffer("asdf"); // char[5] includes NUL
Which will send the char[]
(including terminating NUL character) (see overload). If you don't want that, consider e.g. boost::asio::buffer("asdf"sv)
, which uses a string view, without you having to call strlen
.
Note how you create naming conflicts where
buffer
hidesboost::asio::buffer
because ofusing namespace
. You did the same withio_context
. I'd advise against this level of flirting with danger in C++
if (ec)
throw std::system_error(ec);
Is not needed. If you don't supply ec
, the exception boost::system::system_error
(but from boost) will already be raised in the same fashion.
size_t sent = socket.send_to(
ba::buffer("asdf"),
endpoints->endpoint());
You use endpoints->endpoint()
without validating the resolver results. Depending on situation zero or more resolutions might exist. You might be dereferencing an invalid iterator. This, again, can cause error conditions.
You can get inspiration from the POSIX documentation: https://pubs.opengroup.org/onlinepubs/009695399/functions/sendto.html
The vast majority of the conditions are not applicable, partly due to it
However a few remain:
EACCESS
can occur if you use a regular endpoint as if it were multicastEDESTADDRREQ
if you pass an invalid endpoint (e.g. default constructed)EINTR
unless you have signals ignoredENOBUFS
(when the adapter is jammed - doesn't happen on linux where packets are just dropped)Depending on the actual arguments in your real code:
EMSGSIZE
if your buffer exceeds the limit that can be sent atomicallyEOPNOTSUPP
if you pass invalid flagsThe real question is: do you anticipate any errors that you should deal with? If not simply accept the exceptions (I suggest not passing error_code
parameters).
The only such condition I can think of is a failure to resolve the hostname. However, a quick test tells me the resultset will not be empty, but instead resolve
throws Host not found (authoritative)
.
So, just simplify:
#include <boost/asio.hpp>
using namespace std::literals;
namespace ba = boost::asio;
using ba::ip::udp;
int main() {
ba::io_context io;
udp::resolver resolver{io};
auto endpoints = resolver.resolve(udp::v4(), "127.0.0.1", "6767");
udp::socket socket{io};
socket.open(udp::v4());
return socket.send_to(ba::buffer("asdf"sv), endpoints->endpoint());
}
With
nc -u -l -p 6767 & sleep 1; ./a.out
Prints
asdf