【问题标题】:C - Create a sockaddr structC - 创建一个 sockaddr 结构
【发布时间】:2018-01-18 19:11:56
【问题描述】:

我正在尝试创建一个 void mksockaddr(int af, int proto, char addr[], struct sockaddr* dst) 来创建一个 sockaddr 结构,这就是我所做的:

void sockaddr(int af, int port, char addr[], struct sockaddr* dst) {
  if (af == AF_INET) {
    struct sockaddr_in s;
    s.sin_family = af;
    s.sin_port = htons(port);
    inet_pton(af, addr, &s.sin_addr);
    memcpy(dst, &s, sizeof(s));
  } else {
    struct sockaddr_in6 s;
    s.sin6_family = af;
    s.sin6_port = htons(port);
    s.sin6_flowinfo = 0;
    inet_pton(af, addr, &s.sin6_addr);
    memcpy(dst, &s, sizeof(s));
  }
}

这似乎用AF_INET(IPv4)没有问题,我可以bind()没有任何问题,但是当我尝试使用AF_INET6时,bind()给我无效的参数。

这是我用来bind()的代码:

int sock_fd = socket(AF_INET6, SOCK_RAW, proto);
struct sockaddr sin;
sockaddr(AF_INET6, proto, src, &sin);
if(bind(sock_fd, &sin, sizeof(sin)) < 0) {
  fprintf(stderr, "[ERR] can't bind socket: %s\n", strerror(errno));
  exit(1);
} // got Invalid argument

但是,如果我自己构造一个sockaddr_in6,我可以bind() 就好了:

struct sockaddr_in6 sin;
sin.sin6_port = htons(proto);
sin.sin6_family = AF_INET6;
inet_pton(AF_INET6, src, &sin.sin6_addr);
if(bind(sock_fd, (struct sockaddr*) &sin, sizeof(sin)) < 0) {
  fprintf(stderr, "[ERR] can't bind socket.\n");
  exit(1);
} // work just fine

所以我将函数创建的sockaddr 转换回sockaddr_in6,我可以看到除了sin6_scope_id 之外的所有字段都相同。据我了解,sin6_scope_id 无关紧要,除非我处理的是链接本地 IPv6 地址。

我这里有什么遗漏吗?

【问题讨论】:

  • 如果您正在执行的“工作正常”代码htons(proto),我认为应该是htons(port),但我不知道为什么会成败。
  • 你是如何使用这个功能的?特别是,你传递给dst 参数的是什么?请编辑您的问题,为每个支持的案例添加一个示例调用。
  • @JohnBollinger 查看第二个代码块,使用 sockaddr(AF_INET6, proto, src, &amp;sin); 调用。
  • 您应该使用 getaddrinfo 来为您处理 v4/v6 案例。

标签: c sockets struct


【解决方案1】:

从 C 的角度来看,为了确保您的代码按预期工作,调用者必须在 dst 参数中传递一个指向正确结构类型的有效指针。你的例子没有这样做。相反,它声明一个struct sockaddr,并传递一个指向它的指针。类型struct sockaddr 本身决不打算用作实际对象的类型,而且它对于所有可能的地址类型都不够大。特别是对于 IPv6 地址来说,它不够大。

另一方面,POSIX 比标准 C 对符合程序的要求要快和松一些。这在套接字地址中尤其明显。它定义了一个 struct sockaddr_storage 类型来完全满足您的目的:它足够大并且具有适当的对齐方式来保存任何受支持的套接字地址类型的数据。文档特别提到了它在一般支持 IPv4 和 IPv6 中的用途。 POSIX 还允许在不同的套接字地址指针类型之间进行强制转换,尽管这会导致违反 C 的结构别名规则。

因此,我将重写您的函数以显式使用struct sockaddr_storage,并且通过适当的强制转换进一步简化我的代码。此外,我会让我的函数告诉我地址​​结构的可用大小,它只包含初始化的那部分:

void populate_sockaddr(int af, int port, char addr[],
        struct sockaddr_storage *dst, socklent_t *addrlen) {

    if (af == AF_INET) {
        struct sockaddr_in *dst_in4 = (struct sockaddr_in *) dst;

        *addrlen = sizeof(*dst_in4);
        memset(dst_in4, 0, *addrlen);
        dst_in4->sin_family = af;
        dst_in4->sin_port = htons(port);
        inet_pton(af, addr, &dst_in4->sin_addr);

    } else if (af == AF_INET6) {
        struct sockaddr_in6 *dst_in6 = (struct sockaddr_in6 *) dst;

        *addrlen = sizeof(*dst_in6);
        memset(dst_in6, 0, *addrlen);
        dst_in6->sin6_family = af;
        dst_in6->sin6_port = htons(port);
        // unnecessary because of the memset(): dst_in6->sin6_flowinfo = 0;
        inet_pton(af, addr, &dst_in6->sin6_addr);
    } // else ...
}

然后你会像这样使用它:

struct sockaddr_strorage addr;
socklen_t addrlen;

populate_sockaddr(af, port, src, &addr, &addrlen);
if (bind(sock_fd, (struct sockaddr *) &addr, addrlen) < 0) {
    // ...
}

请注意,将&amp;addr 转换为struct sockaddr * 完全是例行公事。

【讨论】:

  • 可能想要更改Your example does not do not do this.
  • @JohnBollinger "你的例子没有这样做" 语法不好,这就是 sjsam 所指的。它应该改为“您的示例不这样做”。我进行了编辑(以及其他一些)。
  • @JohnBollinger 您的解决方案正在复制 getaddrinfo() 在传递 AI_NUMERICHOST 标志时已经执行的操作。最好使用现有的标准函数,而不是发明自定义函数。
猜你喜欢
  • 1970-01-01
  • 2011-11-16
  • 1970-01-01
  • 2011-11-06
  • 1970-01-01
  • 2015-11-30
  • 1970-01-01
  • 2018-07-23
相关资源
最近更新 更多