boost::asio::ip::udp::socket::send_to 甚至会失败吗?

May boost::asio::ip::udp::socket::send_to even fail?

请考虑以下代码片段。

它首先解析远程主机的地址,然后打开套接字并向其发送一些数据。注意,它会在发生错误时立即抛出。

不涉及并发。消息适合 1K。基本上,此代码片段与“真实”代码之间的唯一区别如下:消息可能会在解析端点并打开套接字后的几秒钟内发送。

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);

对我来说,只要端点有效且套接字打开成功,似乎对 socket.send_to 的调用应该总是成功的,即使远程主机变得不可用(因为使用了 UDP)。

  1. 我应该在最后一行期待什么异常?
  2. 我可以假设不会出现任何错误吗?
  3. 我是否应该期待 任何 IO 相关的错误代码,因为我们无论如何都在做 IO?

ASIO 在底层操作系统调用方面实现了这一点。在 POSIX 上,那将是 sendto。记录了可能的错误情况(见下文)。

但是,首先要做的是:


您可能会收到一个段错误,因为地址越界进入未知地址 space。根据您的平台,它可能显示为 EFAULT (boost::asio::error::fault).

const_buffer buffer{"asdf", 10};

这里的首选拼写是:

auto buffer = boost::asio::buffer("asdf"); // char[5] includes NUL

将发送 char[](包括终止 NUL 字符)(参见 overload)。如果您不想要那样,请考虑例如boost::asio::buffer("asdf"sv),它使用字符串视图,无需调用 strlen.

Note how you create naming conflicts where buffer hides boost::asio::buffer because of using namespace. You did the same with io_context. I'd advise against this level of flirting with danger in C++

其他注意事项

if (ec)
    throw std::system_error(ec);

不需要。如果您不提供 ec,异常 boost::system::system_error(但来自 boost)将以相同的方式引发。

size_t sent = socket.send_to(
        ba::buffer("asdf"),
        endpoints->endpoint());

您在未验证解析器结果的情况下使用 endpoints->endpoint()。根据情况,可能存在零个或多个解决方案。您可能正在取消引用无效的迭代器。这又会导致错误情况。

其他错误代码

您可以从 POSIX 文档中获得灵感:https://pubs.opengroup.org/onlinepubs/009695399/functions/sendto.html

绝大多数条件不适用,部分原因在于

  • 是数据报,不是流协议
  • 此处套接字未处于非阻塞模式(或被 ASIO 抽象掉)
  • 套接字“已知良好”(假设没有您未显示的并发代码)

但是还有一些:

  • EACCESS 如果您像使用多播一样使用常规端点,则可能会发生
  • EDESTADDRREQ 如果你传递了一个无效的端点(例如默认构造的)
  • EINTR 除非你忽略了信号
  • ENOBUFS(当适配器卡住时 - 在 linux 上不会发生数据包被丢弃的情况)

取决于您实际代码中的实际参数:

  • EMSGSIZE 如果您的缓冲区超出了可以自动发送的限制
  • EOPNOTSUPP 如果你传递了无效的标志

摘要

真正的问题是:您是否预料到任何应该处理的错误?如果不是简单地接受异常(我建议不要传递error_code参数)。

我能想到的唯一一种情况是无法解析主机名。但是,快速测试告诉我结果集不会为空,而是 resolve 抛出 Host not found (authoritative).

所以,简化一下:

Live On Coliru

#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());
}

nc -u -l -p 6767 & sleep 1; ./a.out

版画

asdf