公司里装了两条不同的宽带,根据使用的情况,比如拨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我们大概可以推测下这个程序的工作步骤:

  1.  从speedtest.net读取配置文件。
  2. 获得一个server列表。
  3. 找到最近的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))



  1. 在线程中读取读取图片资源。
  2. 把线程放入阻塞的队列中。
  3. 从队列中把线程的结果一个个读取出来。
  4. 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;
};



找到了某个特定属性所对应的值,我们就可以很容易找到目标适配器了。我是这样查找的:

  1. 右键目标网络适配器。
  2. 选择配置。
  3. 点击详细。
  4. 看一下属性列表及对应的值。

最后选择了设备描述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文件。



安装

Py2exe



转换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 文件夹:

python ip网关 python获取网关_Windows

记得执行程序的时候必须要用管理员权限。



源码

https://github.com/yushulx/switch-windows-gateway