Python 在线程中对多个IP的ICMP ping实现?

2 投票
1 回答
3481 浏览
提问于 2025-04-17 10:24

我在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

我的问题:

  1. 有没有人能重现这个问题?

  2. ping的行为应该是这样的么?我觉得不应该。

  3. 有没有现成的ICMP ping库可以在python中使用,不会出现这种情况?
    或者,你能想到一个简单的解决办法吗?比如不断调用receive_one_ping,直到我们的目标地址和接收地址匹配?

编辑:我在python-ping的github页面上创建了一个问题。

1 个回答

5

这件事发生是因为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

另外,可以查看这个问题和回答的评论线程以获取更多信息。

撰写回答