Python 在线程中对多个IP的ICMP ping实现?
我在Windows上使用了jedie的python ping
实现。我可能理解错了,但当我从不同的线程对两台电脑(A和B)进行ping测试时,ping会返回它收到的第一个响应,而不管是从哪个源发出的。
因为这可能是jedie的版本的问题,我回退到了之前的版本。(下面我会详细探讨这个版本)
我在receive_one_ping
中添加了一行代码:(大概是第134行)
recPacket, addr = my_socket.recvfrom(1024) # Existing line
print "dest: {}, recv addr: {}.".format(dest_addr, addr) # New line
这样我们就能看到我们收到的ping的地址了。(应该和目标IP一样,对吧?)
测试:
ping1()对一个已知的离线IP(1.2.3.4)进行ping测试,
ping2()对一个已知的在线IP(192.168.1.1 - 我的路由器)进行ping测试。
>>> from ping import do_one
>>> def ping1():
print "Offline:", do_one("1.2.3.4",1)
>>> ping1()
Offline: None
>>> def ping2():
print "Online:", do_one("192.168.1.1",1)
>>> ping2()
Online: dest: 192.168.1.1, recv addr: ('192.168.1.1', 0).
0.000403682590942
现在如果我们把它们一起做:(为了简单起见使用定时器)
>>> from threading import Timer
>>> t1 = Timer(1, ping1)
>>> t2 = Timer(1, ping2)
>>> t1.start(); t2.start()
>>> Offline:Online: dest: 192.168.1.1, recv addr: ('192.168.1.1', 0).dest: 1.2.3.4, recv addr: ('192.168.1.1', 0).
0.0004508952953870.000423517514093
结果有点混乱(因为打印输出在多线程中不太好用),所以这里稍微清晰一些:
>>> Online: dest: 192.168.1.1, recv addr: ('192.168.1.1', 0).
Offline:dest: 1.2.3.4, recv addr: ('192.168.1.1', 0). # this is the issue - I assume dest should be the same as recv address?
0.000450895295387
0.000423517514093
我的问题:
有没有人能重现这个问题?
ping的行为应该是这样的么?我觉得不应该。
有没有现成的ICMP ping库可以在python中使用,不会出现这种情况?
或者,你能想到一个简单的解决办法吗?比如不断调用receive_one_ping
,直到我们的目标地址和接收地址匹配?
编辑:我在python-ping的github页面上创建了一个问题。
1 个回答
这件事发生是因为ICMP的特性。ICMP没有端口的概念,所以所有的ICMP消息都会被所有的监听者接收到。
通常,我们会通过在ICMP的ECHO请求中设置一个独特的标识符来区分不同的消息,然后在响应中查找这个标识符。这个代码看起来是这么做的,但它使用了当前进程的ID来生成这个标识符。由于这是多线程的代码,所有线程都会共享同一个进程ID,这样当前进程中的所有监听者都会认为所有的ECHO响应都是它们自己发送的!
你需要在do_one()
函数中修改ID
变量,使其在每个线程中都是唯一的。你需要在do_one()
中更改这一行:
my_ID = os.getpid() & 0xFFFF
可能这个方法可以作为替代方案,但理想情况下你应该使用一个真正的16位哈希函数:
# add to module header
try:
from thread import get_ident
except ImportError:
try:
from _thread import get_ident
except ImportError:
def get_ident():
return 0
# now in do_one() body:
my_ID = (get_ident() ^ os.getpid()) & 0xFFFF
我不知道这个模块是否还有其他线程问题,但从初步检查来看,它似乎是没问题的。
使用jedie的实现时,你需要对Ping()
的own_id
构造函数参数做类似的修改。你可以传入一个你知道是唯一的ID(就像上面那样),自己管理Ping()
对象,或者你可以在构造函数的这一行(110行)进行修改:
self.own_id = os.getpid() & 0xFFFF
另外,可以查看这个问题和回答的评论线程以获取更多信息。