一、背景
当我们在一个可能会出现断网的环境,或者需要对主机进行实时存活检测时,就需要对主机发送ping信号来检测主机是否在线
,注意有的内部网络会对ping命令进行一定的拦截,当发送的ICMP(ping)包达到一定的上限时会进行封禁或者拦截等。
二、代码实现
基本想法,通过判断局域网内指定数量的主机,只要有一台主机断开连接,则表示内部网络已经断开。
判断条件可以进行动态调整
import os
import sys
import json
import time
import redis
import subprocess
from datetime import datetime
if getattr(sys, 'frozen', False): # 我们对python程序打包时会将一个变量frozen注入到sys中
APPLICATION_PATH = os.path.dirname(sys.executable)
else:
APPLICATION_PATH = os.path.dirname('.')
config = json.load(open(os.path.join(APPLICATION_PATH, "config.json"), encoding='utf-8'))
REDIS_CONF = config.get("REDIS",{})
"""
0 表示连接成功
1 表示连接失败
"""
class HostAlive(object):
def __init__(self,
host=REDIS_CONF.get("HOST",None),
port=int(REDIS_CONF.get('PORT',6379)),
db=int(REDIS_CONF.get("DB",2)),
password=REDIS_CONF.get("PASSWORD",None)):
self.host = host
self.port = port
self.db = db
self.password = password
if not hasattr(HostAlive,'pool'):
HostAlive.create_pool(host,port,db,password)
self._connection = redis.Redis(connection_pool = HostAlive.pool)
@staticmethod
def create_pool(host,port,db,password):
HostAlive.pool = redis.ConnectionPool(
host = host,
port = port,
db = db,
password = password,
socket_connect_timeout = 2
)
def save_redis(self,status):
if status == 1:
print("内部网络连接已断开")
else:
print("内部网络连接正常")
ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
data = {
"ts": ts,
"status": status
}
self._connection.hmset("network_status",data)
def ping_host(self,ip):
res = subprocess.run(['ping', '-n', '2', '-w', '500', ip],stdout=subprocess.PIPE)
return_ret = str(res.stdout.decode('gbk'))
if '时间' in return_ret and '字节' in return_ret: # 0 表示成功
status = 0
print(f"[{ip}] 连接成功")
else:
print(f"[{ip}] 连接失败")
status = 1 # 1 表示失败
host_status.append(status)
return status
if __name__ == '__main__':
ip_list = [f"192.168.0.{i}" for i in range(1,9)]
ha = HostAlive()
while True:
host_status = []
for ip in ip_list:
status = ha.ping_host(ip)
if status == 1: # 若存在一台主机断网则表示内部网络已断开并写入redis
break
ha.save_redis(status)
time.sleep(10)
关于使用subprocess模块,而不是用os.system() 这个方法
因为os.system()返回的结果不是很准确,只返回0或者1来表示连接是否从或者失败,一定情况下会存在检测结果错误。
subprocess 可以对取出执行的结果,操作性更高。stdout=subprocess.PIPE
表示不打印执行的内容
- Ping 命令参数
C:\Users\RION>ping -h
选项 -h 不正确。
用法: ping [-t] [-a] [-n count] [-l size] [-f] [-i TTL] [-v TOS]
[-r count] [-s count] [[-j host-list] | [-k host-list]]
[-w timeout] [-R] [-S srcaddr] [-c compartment] [-p]
[-4] [-6] target_name
选项:
-t Ping 指定的主机,直到停止。
若要查看统计信息并继续操作,请键入 Ctrl+Break;
若要停止,请键入 Ctrl+C。
-a 将地址解析为主机名。
-n count 要发送的回显请求数。
-l size 发送缓冲区大小。
-f 在数据包中设置“不分段”标记(仅适用于 IPv4)。
-i TTL 生存时间。
-v TOS 服务类型(仅适用于 IPv4。该设置已被弃用,
对 IP 标头中的服务类型字段没有任何
影响)。
-r count 记录计数跃点的路由(仅适用于 IPv4)。
-s count 计数跃点的时间戳(仅适用于 IPv4)。
-j host-list 与主机列表一起使用的松散源路由(仅适用于 IPv4)。
-k host-list 与主机列表一起使用的严格源路由(仅适用于 IPv4)。
-w timeout 等待每次回复的超时时间(毫秒)。
-R 同样使用路由标头测试反向路由(仅适用于 IPv6)。
根据 RFC 5095,已弃用此路由标头。
如果使用此标头,某些系统可能丢弃
回显请求。
-S srcaddr 要使用的源地址。
-c compartment 路由隔离舱标识符。
-p Ping Hyper-V 网络虚拟化提供程序地址。
-4 强制使用 IPv4。
-6 强制使用 IPv6。