2011-09-07 252 views
9

我正在使用Python脚本向服务器发送UDP数据包,该服务器将注册我的脚本以接收来自服务器的通知。该协议要求我发送我自己的IP地址和端口,我希望接收这些通知,因此这应该是从服务器网络可以访问的地址。Python:获取用于将IP数据发送到特定远程IP地址的本地IP地址

解决方案应该至少可以在Windows XP及更高版本上运行,最好是Mac OS X,因为这是我的开发平台。

第一步是获取我的任何接口的IP地址。我目前正在使用通常建议的方法来解析机器自己的主机名:

def get_local_address(): 
    return socket.gethostbyname(socket.gethostname()) 

这只有时才有效。当前它返回172.16.249.1,这是VM Ware使用的虚拟接口的地址,所以这是一个问题。

更好的办法是获取默认界面的IP地址,这应该是大多数时候的正确界面。

更好的办法是通过面向连接的协议(如TCP)获得用于连接到服务器的实际IP地址。实际上,我可以得到该地址,但不能没有试图打开连接:

def get_address_to_connect_to(server_addr): 
    non_open_port = 50000 
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 

    try: 
     s.connect((server_addr, non_open_port)) 
    except socket.error: 
     pass 
    else: 
     s.close() # just in case 

    return s.getsockname()[0] 

这将阻塞,直到有TCP超时在服务器无法访问或与一个邪恶的防火墙封锁ICMP的情况下已经达到数据包。有没有更好的方法来获取这些信息?

回答

11

我不得不一次解决同样的问题,花了相当多的时间试图找到更好的方法,但没有成功。我也开始使用gethostname,但是像你一样发现它并不总是返回正确的结果,甚至可以在hosts文件有问题的情况下抛出异常。

我能找到的跨平台可靠工作的唯一解决方案就是尝试连接,就像你在做的那样。代码的一个改进是使用UDP而不是TCP,即SOCK_DGRAM而不是SOCK_STREAM。这避免了连接超时,并且该功能立即完成。

如果您将此代码部署到可能正在运行防火墙的客户端位置,请确保记录了它执行此检查的事实。否则,他们可能会看到针对端口50000的出站连接的防火墙警告,不明白其用途,并认为它是一个错误。

编辑:这里是我大致使用的代码:

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 

try: 
    s.connect((host, 9)) 
    client = s.getsockname()[0] 
except socket.error: 
    client = "Unknown IP" 
finally: 
    del s 
return client 

我认为你得到0.0.0.0因为你调用getsockname之前关闭套接字。另一个提示是我使用的端口9;它是RFC863的UDP丢弃端口,因此比一些随机的高编号端口更少怪异。

+0

啊,甜蜜的同理心!使用'SOCK_DGRAM',它将近乎完美!但's.getsockname()'返回'('0.0.0.0',51770)'在我的情况下。我试过's = socket.socket(socket.AF_INET,socket.SOCK_DGRAM); s.sendto(b'hello',('1.2.3.4',50000)); s.getsockname()[0]'。如果你将你的代码添加到你的答案中,那会很棒! – Feuermurmel

+0

查看我编辑的答案。 – DNS

+0

完美的作品;它甚至使用我不知道的[Discard Protocol](http://en.wikipedia.org/wiki/Discard_Protocol)!我用'with'语句取代了'del s'。 – Feuermurmel

0

发送方是否需要知道其IP地址?

在发送UDP“注册”后,客户端只能监听它试图订阅的数据(在所有接口上),并让服务器开始将数据发送到数据报所源自的地址?

+0

异端!该服务可以做很多事情,让我的生活更轻松。但同样的问题出现在也是该项目一部分的SIP实施中。它必须在'REGISTER'请求的'Contact'头部中发送自己的IP地址。 – Feuermurmel