构思并制作聪明的WiFi动态限速工具

项目地址: https://github.com/hwding/themis-von-wifi

首先设想一下这个情境: 端午前夕, 大学宿舍中A\B\C\D四个人, A和B明天就要和同学去旅游了, 因此B在疯狂地用迅雷囤积电影, 然而无所事事的屌丝C正在某酷上看游戏视频. 宿舍网速的极限在2.8MB/s, B的迅雷会员离线+加速通道全开, 导致C的视频缓冲速度只有12KB/s( <-- 没错这个人就是我啦, 抓狂中...), 而A和D仅仅再用手机刷些网页, 但是速度极慢以至于到了严重影响体验的程度.

除了B其余人不爽中...

通过分析我们可以发现, B与C对网速具有同样的高要求, 但是速度分配的结果极不公平, 并且B与C的网速和几乎占满极限网速从而导致A和D的网页浏览之类的低速操作也难以及时完成.因此本小白希望能够在出现高需求竞争的时候公平地分配网速, 同时也保证其他低需求接入点的基础速度.

以上就是本小白的困境与设想, 现在我们来看一看如何简单地实现.

项目地址: https://github.com/hwding/themis-von-wifi (WiFi正义之神!), 编码水平有限, 有更好的想法与算法上的建议欢迎共同开发!

预览

如果想控制接入点的限速, 我们必须首先研究后台控制页面的登入方式.

本小白宿舍使用TP-LINK TL-WR842N, 登陆界面是这样的

现在使用Firefox的开发人员工具看一看访问后台需要哪些文件我们重新刷新一下页面

js和css页面引入眼帘. 我们再来尝试登录一下, 看一看登录凭据的格式

登录时填写的密码被前端的js脚本加密了, 我们必须找到其中的加密算法

(省略一万字...)加密算法在classs.js文件中, 我们将此文件从路由端下载下来后, 拖出其中的 this.securityEncode 和 this.orgAuthPwd 两个函数, 把明文密码传进去然后拿到加密后的密文

 1 ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
 2         ScriptEngine scriptEngine = scriptEngineManager.getEngineByName("javascript");
 3         scriptEngine.put("a", router_background_password);
 4         String functionAlpha = extractFunctionFromJSFile("this.securityEncode");
 5         String functionBeta  = extractFunctionFromJSFile("this.orgAuthPwd");
 6         try {
 7             scriptEngine.eval(functionAlpha);
 8             scriptEngine.eval(functionBeta);
 9             scriptEngine.eval("result=this.orgAuthPwd(\""+ router_background_password +"\")");
10         } catch (ScriptException e) {
11             e.printStackTrace();
12             System.exit(0);
13         }
14         PASSWORD_ENCRYPTED = scriptEngine.get("result").toString();

登录成功后路由器会返回一个token, 叫做stok, 只要拿着这串token作为凭证发送请求的时候接在URL后面就OK了

时间有限...其后关于收集接入WiFi的设备的信息以及如何告诉管理后台去限速这些技术细节暂且不表, 具体实现请点击文首GitHub地址或者私信我都是可以的...

我们来看看如何实现文首设想的功能

大体逻辑是这样的:

首先观测一段时间, 收集WiFi的速度极限(maxSpeed)是多少(此处假设为2048KB/s)

然后通过收集设备信息得知已接入的设备数量(hostCount)(此处假设为7, 即3电脑+4手机, 宿舍只有四个人, 哈哈羡慕吧...)

此后每3秒(可在配置文件中修改)收集一次设备信息

每12秒(可在配置文件中修改)重新分析并调整各设备限速

为了保证不活跃的设备拥有最低保证的速度, 我们将取WiFi的速度极限90%作为可调整的速度

首次判定时, 由于情报不足并不能直接做出判断, 因次我们先将WiFi的速度极限平均除以已接入的设备数量, 即给每一台设备分配(maxSpeed * 0.9 / hostCount)的速度

此处等待12秒进入下一次分析判定

(12秒之后...)

发现其中一台(正在暴力下电影的)电脑貌似对现在的限速不满意(即(实际速度 / 限速) > 80% ), 在这里我将它称为饥饿状态

又发现其他的设备正处于不活跃的状态, 即处于饱食状态.

所以我们在保证不低于最低限速(可在配置文件中修改)的情况下拿走未被利用的速度给正处于饥饿状态的host

是不是感觉这个机制并没有什么卵用?

(过了一小时...)

C突然想上Bilibili看个游戏解说, 无奈B正占着几乎90%的速度下着电影

于是C只能开始在低限速下先以最高速度缓冲视频

12秒之后, 程序发现C的限速利用率几乎到了100%, 所以它立即将C的host标记为饥饿状态

紧接着, 判定方法发现同时存在多个(>1)处于饥饿状态的host, 因此他立即抽走处于饱食状态的host那部分未被利用的速度, 压缩独占网速的那台host的速度, 并将可分配速度平均发给每一个饥饿的host

如此反复观测与判定, 程序便对每个host的速度需求就更加了解, 在出现网速竞争的时候分配也更加公平

这B就能做出小小的牺牲下着电影, C也能流畅的看视频, 同时A和D刷网页也不会收到影响啦!

分析器部分代码

 1 static JSONObject retrieveSuggestion(HashMap<String, Long> speedSummation, int maxSpeed, HashMap<String, Integer> currentSpeedLimit) {
 2         System.out.println("analyzing...");
 3         Set<String> keySet = speedSummation.keySet();
 4         JSONObject jsonObject_suggestSpeedLimit = new JSONObject();
 5         hostCurrentSpeedLimitMap = currentSpeedLimit;
 6         if (!isStartUp) {
 7             int freeSpeed = maxSpeed;
 8             for (String each : keySet) {
 9                 int averageSpeedOfThisHost = (int) (speedSummation.get(each) / ((int) judge_interval / watch_interval));
10                 hostAverageSpeedMap.put(each, averageSpeedOfThisHost);
11                 freeSpeed -= averageSpeedOfThisHost;
12             }
13
14             //COLLECT NEEDS FOR MORE SPEED
15             int hungryCount = 0;
16             for (String each : keySet) {
17                 hostNeedMoreSpeedMap.put(each, null);
18                 int thisCurrentSpeedLimit = currentSpeedLimit.get(each);
19                 if (thisCurrentSpeedLimit == 0)
20                     thisCurrentSpeedLimit = maxSpeed;
21                 double thisRate = (double) hostAverageSpeedMap.get(each) / (double) thisCurrentSpeedLimit;
22                 if (thisRate > 0.8) {
23                     hostNeedMoreSpeedMap.replace(each, true);
24                     hungryCount++;
25                 } else if (thisRate < 0.2)
26                     hostNeedMoreSpeedMap.replace(each, false);
27             }
28             //GIVE OUT FREE SPEED
29             if (hungryCount > 1) {
30                 int hungryHostSpeedLimitSummation;
31                 for (String each : keySet) {
32                     if (hostNeedMoreSpeedMap.get(each) == null)
33                         continue;
34                     if (hostNeedMoreSpeedMap.get(each)) {
35                         hungryHostSpeedLimitSummation = hostCurrentSpeedLimitMap.get(each);
36                         hungryHostSpeedLimitSummation += (freeSpeed * 0.9) / hungryCount;
37                         int thisTargetSpeedLimit = hungryHostSpeedLimitSummation;
38                         if (thisTargetSpeedLimit < minSpeedLimit)
39                             thisTargetSpeedLimit = minSpeedLimit;
40                         jsonObject_suggestSpeedLimit.put(each, thisTargetSpeedLimit);
41                     }
42                     else if (!hostNeedMoreSpeedMap.get(each)) {
43                         int thisTargetSpeedLimit = (int) (freeSpeed * 0.1);
44                         if (thisTargetSpeedLimit < minSpeedLimit)
45                             thisTargetSpeedLimit = minSpeedLimit;
46                         jsonObject_suggestSpeedLimit.put(each, thisTargetSpeedLimit);
47                     }
48                 }
49             } else if (hungryCount == 1) {
50                 int moreSpeedPerHungryHost = (int) (freeSpeed * 0.9);
51                 for (String each : keySet) {
52                     if (hostNeedMoreSpeedMap.get(each) == null)
53                         continue;
54                     if (hostNeedMoreSpeedMap.get(each)) {
55                         int thisTargetSpeedLimit = moreSpeedPerHungryHost;
56                         if (thisTargetSpeedLimit < minSpeedLimit)
57                             thisTargetSpeedLimit = minSpeedLimit;
58                         jsonObject_suggestSpeedLimit.put(each, thisTargetSpeedLimit);
59                     }
60                     else if (!hostNeedMoreSpeedMap.get(each)) {
61                         int thisTargetSpeedLimit = (int) (freeSpeed * 0.1);
62                         if (thisTargetSpeedLimit < minSpeedLimit)
63                             thisTargetSpeedLimit = minSpeedLimit;
64                         jsonObject_suggestSpeedLimit.put(each, thisTargetSpeedLimit);
65                     }
66                 }
67             }
68         }
69         else {
70             for (String each : keySet) {
71                 int thisTargetSpeedLimit = (int) ((maxSpeed * 0.9) / keySet.size());
72                 if (thisTargetSpeedLimit < minSpeedLimit)
73                     thisTargetSpeedLimit = minSpeedLimit;
74                 jsonObject_suggestSpeedLimit.put(each, thisTargetSpeedLimit);
75             }
76             isStartUp = false;
77         }
78         cleanUp();
79         return jsonObject_suggestSpeedLimit;
80     }

看门狗线程部分代码

  1 package thw.jellygo.com;
  2
  3 import org.json.JSONArray;
  4 import org.json.JSONObject;
  5 import java.io.UnsupportedEncodingException;
  6 import java.net.URLDecoder;
  7 import java.util.HashMap;
  8 import java.util.Set;
  9
 10 class WatchDogThread extends Thread {
 11     private static HashMap<String, JSONObject> hostsInfo = new HashMap<>();
 12     private static HashMap<String, Long> speedSummation = new HashMap<>();
 13     private static HashMap<String, Integer> currentSpeedLimit = new HashMap<>();
 14     private static ConfigLoader configLoader;
 15     private static int maxSpeed;
 16
 17     public void run() {
 18         configLoader = ConfigLoader.getInstance();
 19         long watch_interval = configLoader.getWatch_interval();
 20         long judge_interval = configLoader.getJudge_interval();
 21         int i=0;
 22         while (true) {
 23             storeHostInformation(RouterBackendManager.listOnlineHosts());
 24             try {
 25                 i++;
 26                 if (i == (int) judge_interval / watch_interval) {
 27                     judge();
 28                     i=0;
 29                 }
 30                 sleep(watch_interval);
 31             } catch (InterruptedException e) {
 32                 e.printStackTrace();
 33                 System.exit(0);
 34             }
 35         }
 36     }
 37
 38     private void storeHostInformation(JSONObject jsonObject) {
 39         configLoader = ConfigLoader.getInstance();
 40         JSONObject jsonObject_hostInfo;
 41         if (jsonObject.has("hosts_info")) {
 42             jsonObject_hostInfo = jsonObject.getJSONObject("hosts_info");
 43             JSONArray jsonArray_onlineHost = jsonObject_hostInfo.getJSONArray("online_host");
 44             adjustMaxSpeed(jsonArray_onlineHost);
 45             for (Object each : jsonArray_onlineHost) {
 46                 JSONObject jsonObject_aHost = new JSONObject(each.toString());
 47                 jsonObject_aHost = jsonObject_aHost.getJSONObject(jsonObject_aHost.keys().next());
 48                 currentSpeedLimit.put(jsonObject_aHost.getString("mac"), jsonObject_aHost.getInt("down_limit"));
 49                 if (!hostsInfo.containsKey(jsonObject_aHost.getString("mac"))) {
 50                     hostsInfo.put(jsonObject_aHost.getString("mac"), jsonObject_aHost);
 51                     speedSummation.put(jsonObject_aHost.getString("mac"), jsonObject_aHost.getLong("down_speed") / 1024);
 52                 } else {
 53                     hostsInfo.replace(jsonObject_aHost.getString("mac"), jsonObject_aHost);
 54                     speedSummation.replace(jsonObject_aHost.getString("mac"), speedSummation.get(jsonObject_aHost.getString("mac")) + jsonObject_aHost.getLong("down_speed") / 1024);
 55                 }
 56             }
 57         }
 58 //        System.out.println(speedSummary.toString());
 59     }
 60
 61     private static void judge() {
 62         HostAnalyzer.getInstance();
 63         JSONObject jsonObject_suggestSpeedLimit = HostAnalyzer.retrieveSuggestion(speedSummation, maxSpeed, currentSpeedLimit);
 64         Set<String> keySet = jsonObject_suggestSpeedLimit.keySet();
 65         for (String each : keySet)
 66             act(each, jsonObject_suggestSpeedLimit.getInt(each));
 67         speedSummation.clear();
 68         currentSpeedLimit.clear();
 69         hostsInfo.clear();
 70     }
 71
 72     private static void act(String key, int suggestSpeedLimit) {
 73         String hostname = null;
 74         try {
 75             hostname = URLDecoder.decode(hostsInfo.get(key).getString("hostname"), "UTF-8");
 76         } catch (UnsupportedEncodingException e) {
 77             e.printStackTrace();
 78             System.exit(0);
 79         }
 80         JSONObject jsonObject = new JSONObject();
 81         JSONObject jsonObject_setBlockFlag = new JSONObject();
 82         jsonObject_setBlockFlag.put("mac", key);
 83         jsonObject_setBlockFlag.put("down_limit", suggestSpeedLimit);
 84         System.out.println(hostname+" -> "+suggestSpeedLimit+" KB/s");
 85         jsonObject_setBlockFlag.put("is_blocked", "0");
 86         jsonObject_setBlockFlag.put("up_limit", "0");
 87         jsonObject_setBlockFlag.put("name", hostname);
 88         JSONObject jsonObject_hostsInfo = new JSONObject();
 89         jsonObject_hostsInfo.put("set_block_flag", jsonObject_setBlockFlag);
 90         jsonObject.put("hosts_info", jsonObject_hostsInfo);
 91         jsonObject.put("method", "do");
 92         RouterBackendManager.setLimitOnHost(jsonObject);
 93     }
 94
 95     private void adjustMaxSpeed(JSONArray jsonArray_onlineHost) {
 96         int thisMaxSpeed = 0;
 97         for (Object each : jsonArray_onlineHost) {
 98             JSONObject jsonObject_aHost = new JSONObject(each.toString());
 99             jsonObject_aHost = jsonObject_aHost.getJSONObject(jsonObject_aHost.keys().next());
100             thisMaxSpeed+=jsonObject_aHost.getLong("down_speed") / 1024;
101         }
102         if (thisMaxSpeed > maxSpeed)
103             maxSpeed = thisMaxSpeed;
104     }
105 }
时间: 2024-10-13 01:13:46

构思并制作聪明的WiFi动态限速工具的相关文章

让小米路由器变成一个聪明的WIFI(1):有人回家,它就发短信告诉你

我想让我的小米路由器变成一个聪明的WIFI,idea是这样的:当老婆回到家,小米路由器就自动发一条短信告诉我. 折腾了几天,搞定,分享一下: 我的路由器型号:小米路由器mini (129元在官网淘的) 步骤一:首先要开启小米路由器SSH, 获得root 这个方面小米是开放的,操作方法网上有,不详述了,见 点击打开链接 步骤二:用SSH登录进去, 了解小米路由器是什么样 在你的电脑上输入以下命令,登录小米路由器 (我的路由器IP是192.168.31.1) ssh [email protected

Linux库函数制作(静态库、动态库)

Linux库函数制作(静态库.动态库) 静态库与动态库 链接方式 链接分为两种:静态链接.动态链接 静态链接: 由链接器在链接时将库的内容加入到可执行程序中 静态链接的特点是: 优点: 对运行环境的依赖性较小,具有较好的兼容性 缺点: 生成的程序比较大,需要更多的系统资源,在装入内存时会消耗更多的时间 库函数有了更新,必须重新编译应用程序 动态链接: 连接器在链接时仅仅建立与所需库函数的之间的链接关系,在程序运行时才将所需资源调入可执行程序 动态链接的特点: 优点: 在需要的时候才会调入对应的资

Ubuntu 16.10 安装byzanz截取动态效果图工具

1.了解byzanz截取动态效果图工具 byzanz能制作文件小,清晰的GIF动态效果图,不足就是,目前只能通过输入命令方式来录制. byzanz主要的参数选项有: -d, --duration=SECS     动画的时间 (默认:10 秒)  -e, --exec=COMMAND      Command to execute and time  --delay=SECS            开始之前的延时(默认:1 秒)  -c, --cursor            录制鼠标光标 

使用python制作ArcGIS插件(1)工具介绍

使用python制作ArcGIS插件(1)工具介绍 by 李远祥 ArcGIS从10.0开始支持addin(ArcGIS软件中又叫作加载项)的方式进行插件制作.相对于以往9.x系列,addin的无论是从使用或者编写都更加方便快捷.通过开发语言,可以制作ArcGIS Desktop各个软件模块的插件. Addin支持多种开发语言,如.net.java和python.其中.net和java需要配合ArcGIS的SDK,使用ArcObjects进行开发.其好处是ArcObjects可以非常细粒度的控制

在Windows下制作静态库和动态库

一:静态库的创建 VC++6.0中new一个的为win32 static library工程,之后有二个选项.根据需求选吧. 具体的类或者函数的添加过程和标准的工程一样,直接创建新的类或者添加新 的.h(例:MY.h)和.CPP(例:MY.cpp)文件.然后在其中编写实现. 完成后编译会生成一个.lib文件(例如:生成的是MY.lib)我生成的文件为TEST.lib                                                                  

陈松松:推荐制作高清视频必备的3个工具

每个视频,都是你的金牌业务员 这是我写的第30篇视频营销原创文章 与其搜索十年,不如花一年的时间学习,去赚9年的高薪! 21天养成习惯,我信了! 只要能坚持下来,会有意外的收获! 其实前几天是最痛苦的,因为要跳出自己的舒适区,一旦跳出来,就很容易形成习惯,就很容易做下去! 我们做视频营销少不了视频,有的人会去借力视频,有的人会自己制作视频,今天我们就谈一谈自己制作高清视频的3个工具,这3个工具我用了四五年了,非常好用. 第一款工具:录制高清视频工具 录制视频分为2种: 第一种:录制电脑屏幕视频

对于这款动态课件工具你了解多少

有着丰富经验的老师都知道,数学里的很多知识比较抽象,学生往往在刚刚接触时不好理解,理解不了该知识点的含义,这样学生们就产生了厌学情绪,不喜欢学数学?数学知识理解不透彻,做题就没法做,所以考试就考不好. 几何画板获取地址:www.mairuan.com 去年在一位老师的推荐下,我开始使用一款叫做几何画板的软件制作动态课件,发现用了这款工具后,课堂效果非常不错.很多没法在黑板上做演示的数学知识,用它做个动画进行演示,学生们的理解变得更容易了,这样我的教学也变得轻松许多. 动态课件是目前数学教学的一大

【动态域名解析工具】tunnel,国内版的ngrok,花生壳可以睡觉了

在笔者的系列微信开发教程中,有一个比较基础且重要的一节:微信开发的调试.在文章中我推荐了两种动态域名解析的工具用于将本地的开发环境部署成服务器,一种是花生壳,一种是ngrok,但毕竟我等屌丝用不起或者不愿意付费试用花生壳,导致花生壳经常来大姨妈(解析失败).而ngrok的服务器是国外的,而天朝的网络想必大家都懂的.那在这样的一个环境下,调试微信的时候显得特别不方便.无意中呢,看到tunnel这样一个工具,看了下官网的介绍后,觉得尝试使用下,最终让我觉得放弃使用花生壳和ngrok了.至于为何,那我

动态代理工具类

我刚刚想着每次写动态代理的时候都会写非常多的代码,有点不值得,所以我写了一个实现动态代理的工具类.用户能够用JDK动态代理也能够使用CGLIB动态代理,我的ProxyUtils中提供了三种开发动态代理的方式.在实际用的过程中,能够继承Intercepter这个抽象类实如今逻辑代码前后加入控制代码.假设控制代码返回true那么证明能够通过,假设控制代码返回false说明验证不通过,假设不通过那么就返回你逻辑代码中返回的"0"值,假设你逻辑代码返回对象.那么会返回null,假设是其它类型则