【问题标题】:May boost::asio::ip::udp::socket::send_to even fail?可能 boost::asio::ip::udp::socket::send_to 甚至会失败?
【发布时间】:2021-01-21 21:30:02
【问题描述】:

请考虑以下代码 sn-p。

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

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

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 吗?

【问题讨论】:

  • UPD 协议是“即发即弃”类型。您不会收到已收到数据报的确认信息(如在 TCP 协议中)。例如,当您的机器失去与网络的连接时,您可能会收到错误消息。

标签: c++ udp boost-asio


【解决方案1】:

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

但是,首先要做的事情:


您可能会收到段错误,因为地址越界进入未知地址空间。根据您的平台,它可能显示为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

请注意您是如何创建命名冲突的,其中 buffer 由于 using namespace 而隐藏了 boost::asio::buffer。你对io_context 做了同样的事情。我建议不要在 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

【讨论】:

  • 感谢您提供如此详细的回答,但我想澄清一下我的问题,以准确突出我感兴趣的内容。
  • 所以,我想知道如果调用 send_to 可能会失败,如果它通过了有效的端点和足够小的缓冲区。
  • @DmytroStarosud 仅在网络(或硬件)出现故障时。
  • 我认为您的部分答案(关于 posix 错误代码)很好地回答了我的问题。不过,其他部分可能仍然有用。您能否重新格式化您的答案以突出显示对我的问题很重要的部分?
猜你喜欢
  • 1970-01-01
  • 2012-05-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-06-08
  • 1970-01-01
  • 2017-04-27
  • 1970-01-01
相关资源
最近更新 更多