6

getaddrinfo()功能不仅允许客户端程序有效地找到为给定主机创建套接字的正确数据,它还允许服务器绑定到正确的套接字 - 理论上。

我刚刚了解了这一点,并开始通过 Python 使用它:

from socket import *
for i in getaddrinfo(None, 22, AF_UNSPEC, SOCK_STREAM, IPPROTO_IP, AI_PASSIVE): i

产量

(2, 1, 6, '', ('0.0.0.0', 22))
(10, 1, 6, '', ('::', 22, 0, 0))

是什么让我想知道是否有问题。

我究竟应该如何处理这些答案?我是不是该

  • 制作listen()所有这些答案的 ing 套接字,或者我应该
  • 只选择第一个真正有效的?

联机帮助页中的示例建议我只使用第一个并且如果它没有错误就对其感到满意,但是我只能通过 IPv4 在我的示例中获得连接。

但是如果我尝试所有这些,我不得不担心 2 个服务器套接字,这是不必要的,因为如果满足某些条件(操作系统、套接字标志等),IPv6 服务器套接字也会监听 IPv4。

我在哪里想错了?


编辑:显然,我没有想错,但我的电脑做错了事。/etc/gai.conf我使用OpenSUSE 附带的默认设置。如果有人能指出我正确的方向,那就太好了。

编辑 2:在给定的情况下,strace在阅读后在内部进行以下调用/etc/gai.conf(现在使用端口 54321,因为我认为使用端口 22 可能会产生一些不良影响,但事实并非如此):

socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP) = 3
connect(3, {sa_family=AF_INET6, sin6_port=htons(54321), inet_pton(AF_INET6, "::", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, 28) = 0
getsockname(3, {sa_family=AF_INET6, sin6_port=htons(38289), inet_pton(AF_INET6, "::1", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, [28]) = 0
connect(3, {sa_family=AF_UNSPEC, sa_data="\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}, 16) = 0
connect(3, {sa_family=AF_INET, sin_port=htons(54321), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
getsockname(3, {sa_family=AF_INET6, sin6_port=htons(60866), inet_pton(AF_INET6, "::ffff:127.0.0.1", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, [28]) = 0
close(3)                                = 0

显然,该决定旨在根据getsockname()通话结果进行......

顺便说一句:https : //bugs.launchpad.net/ubuntu/+source/eglibc/+bug/673708 和那里提到的其他错误报告证实了我的观察。那里有几个人声称新行为是正确的,所以我显然坚持使用AF_INET6... :-(

4

2 回答 2

3

getaddrinfo由于某种原因,您返回了错误的结果。它应该首先返回 IPv6 套接字。我唯一能想到的是,如果您的操作系统检测到您的系统具有低优先级 IPv6(6to4 或 Teredo)并避免它们,在这种情况下,IMO 是错误的。编辑:刚刚注意到我自己的电脑做同样的事情,我使用 6to4。

但是,您可以同时收听它们,也可以AF_INET6使用AF_UNSPEC. 然后你可以做 setsockopt 来禁用IPV6_V6ONLY.

getaddrinfo 在这里做了合理的事情并返回所有适用的结果(尽管顺序错误,正如我所提到的)。一个和两个侦听套接字都是有效的方法,具体取决于您的应用程序。

于 2011-11-13T19:47:29.550 回答
0

JFTR:现在看来,手册页中给出的程序是错误的。

监听这两种 IP 类型有两种可能的方法:

  1. 只创建一个 IPv6 套接字并关闭 v6 only 标志:

    from socket import *
    s = socket(AF_INET6, SOCK_STREAM)
    s.setsockopt(IPPROTO_IPV6, IPV6_V6ONLY, 0)
    s.bind(...)
    

    分别

    from socket import *
    ai = getaddrinfo(None, ..., AF_INET6, SOCK_STREAM, 0, AI_PASSIVE)[0]
    s = socket(ai[0], ai[1], ai[2])
    s.setsockopt(IPPROTO_IPV6, IPV6_V6ONLY, 0)
    s.bind(ai[4])
    

    优点:

    • 更容易处理

    缺点:

    • 在 XP (AFAIK) 下不起作用 - 有两种不同的协议栈
  2. 使用两个套接字并打开 v6only 标志:

    from socket import *
    aii = getaddrinfo(None, ..., AF_UNSPEC, SOCK_STREAM, 0, AI_PASSIVE)
    sl = []
    for ai in aii:
        s = socket(ai[0], ai[1], ai[2])
        if ai[0] == AF_INET6: s.setsockopt(IPPROTO_IPV6, IPV6_V6ONLY, 1)
        s.bind(ai[4])
        sl.append(s)
    

    sl并在接受循环中处理所有套接字(使用select()或非阻塞 IO 这样做)

    优点:

    • 使用(几乎)独立于协议的处理getaddrinfo()
    • 也可以在 XP 下工作

    缺点:

    • 处理复杂
于 2011-11-13T23:05:59.360 回答