API使用sockaddr_storage

20

我正在尝试编写一些与IP无关的代码,根据各种来源的建议,我尝试使用sockaddr_storage。然而,所有的API调用(getaddrinfo、getnameinfo)仍然依赖于struct sockaddr。并且它们之间进行转换并不是一个好的选择,会引起很多其他问题。

将其分别转换为sockaddr_in和sockaddr_in6也有点违背了我使用sockaddr_storage的初衷。

有没有人能够有效地在开发简单的客户端服务器套接字应用程序中使用sockaddr_storage。

2个回答

26
使用IPV6和IPV4编程的问题在于,一个纯 sockaddr 结构体本身不足以容纳一个 sockaddr_in6。因此,如果您需要盲目传递一个可能是 sockaddr_in 或 sockaddr_in6 的地址,sockaddr_storage 更易于使用。
无论您使用 sockaddr_in、sockaddr_in6 还是 sockaddr_storage,最终都必须将这些指针强制转换为 sendto、recvfrom、connect、accept 和许多其他套接字函数的调用。这只是套接字编程的已知细微差别。放下做一些不安全的事情的感觉吧,您的代码会没问题的。
现在,当编写旨在同时适用于 IPV4 和 IPV6 的网络代码时,您很容易陷入处理不同网络类型的大量 switch 语句的陷阱中。代码会变得混乱,如下所示:
if (addr.ss_family == AF_INET)
    sendto(sock, buffer, len, 0, (sockaddr*)&addr, sizeof(sockaddr_in))
else (addr.ss_family == AF_INET6)
    sendto(sock, buffer, len, 0, (sockaddr*)&addr, sizeof(sockaddr_in6));

然后,这种“if family == AF_INET”的表达式很容易重复出现。这是你想要避免的。

假设您正在使用C++,您会发现一个用于套接字地址对象的抽象类非常有用。我在github上有一个示例这里这里。CSocketAddress类由{sockaddr、sockaddr_in、sockaddr_in6}的联合支持,并且可以使用sockaddr_storage构造。如果我在开始编写此类之前就知道了sockaddr_storage,我将使用它来代替联合。无论如何,它使我能够编写以下代码:

CSocketAddress addr;
...
sendto(sock, buffer, len, 0, addr.GetSockAddr(), addr.GetSockAddrLength());

同样地,"接受"语句看起来像这样:

sockaddr_storage addrstorage = {};
int len = sizeof(sockaddr_storage);
accept(sock, (sockaddr*)&addrstorage, &len);

CSocketAdddress addr(addrstorage); // construct an address object to pass around everywhere else

这对于调用bind、send和recv的代码路径非常有帮助。现在,我的STUN服务器和客户端代码路径不再需要了解套接字地址的族类型。它们只使用“CSocketAddress”对象。仅在客户端和服务器初始化时才有IPV4和IPV6特定代码 - 当实际构建地址对象时。幸运的是,这也部分抽象出来了。
您可能还想浏览此处的帮助程序函数。这里有一些更有用的内容,例如以IP无关的方式解析主机名、枚举适配器等。这是Linux代码,但其中的某些内容应该可以映射到Windows和winsock上。
我快要完成将TCP支持添加到此代码库中。在添加对SOCK_STREAM的支持过程中,我没有必须进行任何更改或添加任何新代码来处理IPV4和IPV6地址结构的差异。

3
我通常不觉得需要使用struct sockaddr_storage,它的作用是为任何给定协议的sockaddr结构分配足够的空间,但在IP版本无关的代码中,你有多少次需要这样做?通常情况下,您调用getaddrinfo(),然后它会给您一堆struct sockaddr *,您不关心它们是否是sockaddr_in还是sockaddr_in6,只需将它们原样传递到bind()connect()(无需转换)。
在典型的客户端/服务器代码中,我想到struct sockaddr_storage有用的主要地方是为accept()的第二个参数保留空间。在这种情况下,我同意必须将其强制转换为struct sockaddr *两次,一次用于accept(),另一次用于getnameinfo()。但我看不出有避免这些转换的方法。这是C,结构继承总是涉及到很多转换。

请注意,gethostbyname() 返回的内容与 sockaddr_in 不兼容——它返回指向四字节主机地址的指针,而不是完整的 sockaddr 实例。因此,您需要将 h_addr_list[0] 字段复制到 sockaddr_in 的 sin_addr 字段中(用于 IPV6),或者复制到 sockaddr_ipv6 的 sin6_addr 字段中。 - Jon Watte
@JonWatte: 确实,这就是不使用 gethostbyname() 的另一个原因。应该使用 getaddrinfo() 代替。 - Celada
是的,在我有自由的代码库中,那样做更好。 - Jon Watte

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接