Python TCP套接字与IPv6地址连接失败
我尝试用一个IPv6地址来绑定一个Python的TCP套接字。
self.__addr = ('fe80::224:d7ff:fe9d:9800', 5050)
self.__type = socket.AF_INTE6
self.__sock = socket.socket(self.__type, socket.SOCK_STREAM)
for family, _, _, _, sockaddr in socket.getaddrinfo( self.__addr[0], self.__addr[1], 0, 0, socket.SOL_TCP ):
if family == self.__type:
self.__addr = sockaddr
break
self.__sock.bind( self.__addr )
self.__sock.listen(1)
我使用了socket.getaddrinfo()的结果,正如其他解决方案中提到的,但总是出现这个错误:
self.__sock.bind( self.__addr )
File "/usr/lib/python2.7/socket.py", line 224, in meth
return getattr(self._sock,name)(*args)
socket.error: [Errno 22] Invalid argument
这是我网络接口的ifconfig结果
wlan0 Link encap:Ethernet Hardware Adresse 00:24:d7:9d:98:00
inet Adresse:192.168.0.103 Bcast:192.168.0.255 Maske:255.255.255.0
inet6-Adresse: fe80::224:d7ff:fe9d:9800/64
有人知道为什么会出现这个错误吗?
2 个回答
一个 fe80:
地址是链接本地地址,这意味着它总是需要指定要使用的链接。
('fe80::224:d7ff:fe9d:9800%wlan0', 5050)
可能就是你需要的格式。
通常情况下,网络前缀是不会重叠的,每个IP地址只会在一个接口上使用。不过,对于链路本地地址来说,(同样的)前缀(fe80::/10
)会在每个启用了IPv6的网络接口上使用。这意味着,仅凭链路本地地址,系统无法判断该在哪个网络接口上监听。你需要自己指定接口。
在写地址时,你可以通过在IPv6地址后面加上%
和接口的ID来做到这一点。正如glglgl所展示的,这个地址可以是fe80::224:d7ff:fe9d:9800%wlan0
。接口名称取决于你使用的系统。我在OS X的机器上有en0
和en1
这两个接口,而我的Linux机器上有eth0
,在Windows上,接口名称通常是一个整数。
在socket函数中,接口是通过范围ID来指定的。这是地址元组的一部分。在你的例子中,你使用了('fe80::224:d7ff:fe9d:9800', 5050)
。这样是可以的,因为对于IPv6,最后两个元素可以省略。完整的地址元组是('fe80::224:d7ff:fe9d:9800', 5050, 0, 4)
,其中4
就是范围ID。
你可以用一个小脚本来测试这个:
import socket
addrs = [('fe80::cafe:1%en0', 5050),
('fe80::cafe:1%en1', 5050)]
for addr in addrs:
print('Original addr: {}'.format(addr))
for res in socket.getaddrinfo(addr[0], addr[1], socket.AF_INET6,
socket.SOCK_STREAM, socket.SOL_TCP):
af, socktype, proto, canonname, sa = res
print('Full addr: {}'.format(sa))
print('')
在我的系统上运行这个脚本会显示:
Original addr: ('fe80::cafe:1%en0', 5050)
Full addr: ('fe80::cafe:1%en0', 5050, 0, 4)
Original addr: ('fe80::cafe:1%en1', 5050)
Full addr: ('fe80::cafe:1%en1', 5050, 0, 5)
在socket绑定时,%
后面的部分不再被使用,它依赖于范围ID。
这意味着以下的bind
调用会产生相同的结果:
s = socket.socket(af, socktype, proto)
s.bind(('fe80::cafe:1', 5050, 0, 4))
s.bind(('fe80::cafe:1%en0', 5050, 0, 4))
s.bind(('fe80::cafe:1%bla', 5050, 0, 4))
不过最好使用从getaddrinfo
返回的原始值。依赖这种未定义的行为并不是个好主意。像这样的简单循环应该效果最好:
addr = ('fe80::224:d7ff:fe9d:9800%wlan0', 5050)
s = None
for res in socket.getaddrinfo(addr[0], addr[1], socket.AF_INET6,
socket.SOCK_STREAM, socket.SOL_TCP):
af, socktype, proto, canonname, sa = res
try:
s = socket.socket(af, socktype, proto)
s.bind(sa)
s.listen(1)
except socket.error:
if s is not None:
s.close()
s = None
if s is None:
print('Socket opening/binding failed')
etc.