公司里装了两条不同的宽带,根据使用的情况,比如拨VPN,或者使用人数过多,网速不行了就要切换下。每次手动去切换很不方便,而且网速也很难判断。这里分享下如何用Python检测网速,然后自动切换网关。
参考原文:How to Change Windows Gateway by Broadband Speed in Python
作者:Xiao Ling
翻译:yushulx
使用speedtest-cli测试网速
相信很多人都用过speedtest.net来测试网络速度,speedtest-cli是一个Python开源项目。通过链接speedtest.net来检测上传速度和下载速度。
安装
pip install speedtest-cli
pip install speedtest-cli
或者
easy_install speedtest-cli
easy_install speedtest-cli
测试网速
安装之后就可以通过命令行来测试下网速:
speedtest-cli --bytes
speedtest-cli --bytes
根据log我们大概可以推测下这个程序的工作步骤:
- 从speedtest.net读取配置文件。
- 获得一个server列表。
- 找到最近的server作为最优选择用来测试速度。
这种最优方式适合检测网络的最大下载速度。而我现在需要的是根据网络访问速度切换网关,比如我需要经常访问国外某个网站,这个最大下载速度就没有意义了。要检测速度,我可以随意设置一些这个网站上的图片资源作为测试数据,而并不需要使用speedtest.net。
自定义函数用于检测网速
通过学习源码,我们可以自己重写一个网速测试函数。找到 {Python Installation Directory}\Lib\site-packages\speedtest_cli.py,根据speedtest()创建的函数很简单:
def testSpeed(urls):
speedtest_cli.shutdown_event = threading.Event()
signal.signal(signal.SIGINT, speedtest_cli.ctrl_c)
print "Start to test download speed: "
dlspeed = speedtest_cli.downloadSpeed(urls)
dlspeed = (dlspeed / 1000 / 1000)
print('Download: %0.2f M%s/s' % (dlspeed, 'B'))
return dlspeed
def testSpeed(urls):
speedtest_cli.shutdown_event = threading.Event()
signal.signal(signal.SIGINT, speedtest_cli.ctrl_c)
print "Start to test download speed: "
dlspeed = speedtest_cli.downloadSpeed(urls)
dlspeed = (dlspeed / 1000 / 1000)
print('Download: %0.2f M%s/s' % (dlspeed, 'B'))
return dlspeed
原始代码是先找到最佳服务器,然后从上面获取URL资源。这里只需要随便设置我需要的资源:
urls = ["http://www.dynamsoft.com/assets/images/logo-index-dwt.png",
"http://www.dynamsoft.com/assets/images/logo-index-dnt.png",
"http://www.dynamsoft.com/assets/images/logo-index-ips.png",
"http://www.codepool.biz/wp-content/uploads/2015/06/django_dwt.png",
"http://www.codepool.biz/wp-content/uploads/2015/07/drag_element.png"]
urls = ["http://www.dynamsoft.com/assets/images/logo-index-dwt.png",
"http://www.dynamsoft.com/assets/images/logo-index-dnt.png",
"http://www.dynamsoft.com/assets/images/logo-index-ips.png",
"http://www.codepool.biz/wp-content/uploads/2015/06/django_dwt.png",
"http://www.codepool.biz/wp-content/uploads/2015/07/drag_element.png"]
看看源码中的下载速度是如何计算的?
def downloadSpeed(files, quiet=False):
"""Function to launch FileGetter threads and calculate download speeds"""
start = timeit.default_timer()
def producer(q, files):
for file in files:
thread = FileGetter(file, start)
thread.start()
q.put(thread, True)
if not quiet and not shutdown_event.isSet():
sys.stdout.write('.')
sys.stdout.flush()
finished = []
def consumer(q, total_files):
while len(finished) < total_files:
thread = q.get(True)
while thread.isAlive():
thread.join(timeout=0.1)
finished.append(sum(thread.result))
del thread
q = Queue(6)
prod_thread = threading.Thread(target=producer, args=(q, files))
cons_thread = threading.Thread(target=consumer, args=(q, len(files)))
start = timeit.default_timer()
prod_thread.start()
cons_thread.start()
while prod_thread.isAlive():
prod_thread.join(timeout=0.1)
while cons_thread.isAlive():
cons_thread.join(timeout=0.1)
return (sum(finished) / (timeit.default_timer() - start))
def downloadSpeed(files, quiet=False):
"""Function to launch FileGetter threads and calculate download speeds"""
start = timeit.default_timer()
def producer(q, files):
for file in files:
thread = FileGetter(file, start)
thread.start()
q.put(thread, True)
if not quiet and not shutdown_event.isSet():
sys.stdout.write('.')
sys.stdout.flush()
finished = []
def consumer(q, total_files):
while len(finished) < total_files:
thread = q.get(True)
while thread.isAlive():
thread.join(timeout=0.1)
finished.append(sum(thread.result))
del thread
q = Queue(6)
prod_thread = threading.Thread(target=producer, args=(q, files))
cons_thread = threading.Thread(target=consumer, args=(q, len(files)))
start = timeit.default_timer()
prod_thread.start()
cons_thread.start()
while prod_thread.isAlive():
prod_thread.join(timeout=0.1)
while cons_thread.isAlive():
cons_thread.join(timeout=0.1)
return (sum(finished) / (timeit.default_timer() - start))
- 在线程中读取读取图片资源。
- 把线程放入阻塞的队列中。
- 从队列中把线程的结果一个个读取出来。
- speed = total file sizes / total time cost
查询网络适配器设置,并设置网关
如何用Python设置网关?在StackOverflow上可以找到解答。推荐的方法是使用WMI (Windows Management Instrumentation)。
安装
WMIPython for Windows Extensions
Win32网络适配器设置
很多人的电脑上会看到很多的网络适配器,比如无线网卡的,虚拟网卡的,以太网卡的等等。每个适配器都有很多属性,看下微软的定义Win32_NetworkAdapterConfiguration class::
[Provider("CIMWin32")]class Win32_NetworkAdapterConfiguration : CIM_Setting
{
boolean ArpAlwaysSourceRoute;
boolean ArpUseEtherSNAP;
string Caption;
string DatabasePath;
boolean DeadGWDetectEnabled;
string DefaultIPGateway[];
uint8 DefaultTOS;
uint8 DefaultTTL;
string Description;
boolean DHCPEnabled;
datetime DHCPLeaseExpires;
datetime DHCPLeaseObtained;
string DHCPServer;
string DNSDomain;
string DNSDomainSuffixSearchOrder[];
boolean DNSEnabledForWINSResolution;
string DNSHostName;
string DNSServerSearchOrder[];
boolean DomainDNSRegistrationEnabled;
uint32 ForwardBufferMemory;
boolean FullDNSRegistrationEnabled;
uint16 GatewayCostMetric[];
uint8 IGMPLevel;
uint32 Index;
uint32 InterfaceIndex;
string IPAddress[];
uint32 IPConnectionMetric;
boolean IPEnabled;
boolean IPFilterSecurityEnabled;
boolean IPPortSecurityEnabled;
string IPSecPermitIPProtocols[];
string IPSecPermitTCPPorts[];
string IPSecPermitUDPPorts[];
string IPSubnet[];
boolean IPUseZeroBroadcast;
string IPXAddress;
boolean IPXEnabled;
uint32 IPXFrameType[];
uint32 IPXMediaType;
string IPXNetworkNumber[];
string IPXVirtualNetNumber;
uint32 KeepAliveInterval;
uint32 KeepAliveTime;
string MACAddress;
uint32 MTU;
uint32 NumForwardPackets;
boolean PMTUBHDetectEnabled;
boolean PMTUDiscoveryEnabled;
string ServiceName;
string SettingID;
uint32 TcpipNetbiosOptions;
uint32 TcpMaxConnectRetransmissions;
uint32 TcpMaxDataRetransmissions;
uint32 TcpNumConnections;
boolean TcpUseRFC1122UrgentPointer;
uint16 TcpWindowSize;
boolean WINSEnableLMHostsLookup;
string WINSHostLookupFile;
string WINSPrimaryServer;
string WINSScopeID;
string WINSSecondaryServer;
};
[Provider("CIMWin32")]class Win32_NetworkAdapterConfiguration : CIM_Setting
{
boolean ArpAlwaysSourceRoute;
boolean ArpUseEtherSNAP;
string Caption;
string DatabasePath;
boolean DeadGWDetectEnabled;
string DefaultIPGateway[];
uint8 DefaultTOS;
uint8 DefaultTTL;
string Description;
boolean DHCPEnabled;
datetime DHCPLeaseExpires;
datetime DHCPLeaseObtained;
string DHCPServer;
string DNSDomain;
string DNSDomainSuffixSearchOrder[];
boolean DNSEnabledForWINSResolution;
string DNSHostName;
string DNSServerSearchOrder[];
boolean DomainDNSRegistrationEnabled;
uint32 ForwardBufferMemory;
boolean FullDNSRegistrationEnabled;
uint16 GatewayCostMetric[];
uint8 IGMPLevel;
uint32 Index;
uint32 InterfaceIndex;
string IPAddress[];
uint32 IPConnectionMetric;
boolean IPEnabled;
boolean IPFilterSecurityEnabled;
boolean IPPortSecurityEnabled;
string IPSecPermitIPProtocols[];
string IPSecPermitTCPPorts[];
string IPSecPermitUDPPorts[];
string IPSubnet[];
boolean IPUseZeroBroadcast;
string IPXAddress;
boolean IPXEnabled;
uint32 IPXFrameType[];
uint32 IPXMediaType;
string IPXNetworkNumber[];
string IPXVirtualNetNumber;
uint32 KeepAliveInterval;
uint32 KeepAliveTime;
string MACAddress;
uint32 MTU;
uint32 NumForwardPackets;
boolean PMTUBHDetectEnabled;
boolean PMTUDiscoveryEnabled;
string ServiceName;
string SettingID;
uint32 TcpipNetbiosOptions;
uint32 TcpMaxConnectRetransmissions;
uint32 TcpMaxDataRetransmissions;
uint32 TcpNumConnections;
boolean TcpUseRFC1122UrgentPointer;
uint16 TcpWindowSize;
boolean WINSEnableLMHostsLookup;
string WINSHostLookupFile;
string WINSPrimaryServer;
string WINSScopeID;
string WINSSecondaryServer;
};
找到了某个特定属性所对应的值,我们就可以很容易找到目标适配器了。我是这样查找的:
- 右键目标网络适配器。
- 选择配置。
- 点击详细。
- 看一下属性列表及对应的值。
最后选择了设备描述Realtek PCIe GBE Family Controller。
用Python查询Windows IP和网关
wmiObj = wmi.WMI()
sql = "select IPAddress,DefaultIPGateway from Win32_NetworkAdapterConfiguration where Description=\"Realtek PCIe GBE Family Controller\" and IPEnabled=TRUE"
configurations = wmiObj.query(sql)
wmiObj = wmi.WMI()
sql = "select IPAddress,DefaultIPGateway from Win32_NetworkAdapterConfiguration where Description=\"Realtek PCIe GBE Family Controller\" and IPEnabled=TRUE"
configurations = wmiObj.query(sql)
设置Windows网关
configurations = wmiObj.Win32_NetworkAdapterConfiguration(Description="Realtek PCIe GBE Family Controller", IPEnabled=True)
configuration = configurations[0]
ret = configuration.SetGateways(DefaultIPGateway=[gateway])
configurations = wmiObj.Win32_NetworkAdapterConfiguration(Description="Realtek PCIe GBE Family Controller", IPEnabled=True)
configuration = configurations[0]
ret = configuration.SetGateways(DefaultIPGateway=[gateway])
请注意脚本必须用管理员权限来执行,不然设置会失败。记得查看返回值。
for gateway in gateways:
settingReturn = setGateway(wmiObj, gateway)
if (settingReturn[0] != 0):
print "Setting failed"
return
print "Set gateway: " + gateway
dlspeed = testSpeed(urls)
option = (gateway, dlspeed)
print "Network option: " + str(option)
if (option[1] > bestChoice[1]):
bestChoice = option
for gateway in gateways:
settingReturn = setGateway(wmiObj, gateway)
if (settingReturn[0] != 0):
print "Setting failed"
return
print "Set gateway: " + gateway
dlspeed = testSpeed(urls)
option = (gateway, dlspeed)
print "Network option: " + str(option)
if (option[1] > bestChoice[1]):
bestChoice = option
把Python脚本转换成可执行EXE
如何把Python程序分发给别人使用如果他们并没有安装Python程序以及依赖库?使用Py2exe 可以很容易把Python脚本转换成exe文件。
安装
转换Python脚本
新建setup.py:
from distutils.core import setup
import py2exe
setup(console=['network.py'])
from distutils.core import setup
import py2exe
setup(console=['network.py'])
执行下面的命令:
python setup.py py2exe
python setup.py py2exe
目录中会生成一个dist 文件夹:
记得执行程序的时候必须要用管理员权限。
源码
https://github.com/yushulx/switch-windows-gateway