浏览代码

新增 测速全部 IP、检查版本更新

xiu2 4 年之前
父节点
当前提交
0d54b65f33
共有 5 个文件被更改,包括 896 次插入830 次删除
  1. 140 106
      IPRangeLoader.go
  2. 163 161
      README.md
  3. 272 243
      main.go
  4. 173 172
      tcping.go
  5. 148 148
      util.go

+ 140 - 106
IPRangeLoader.go

@@ -1,106 +1,140 @@
-package main
-
-import (
-	"bufio"
-	"log"
-	"net"
-	"os"
-)
-
-func loadFirstIPOfRangeFromFile(ipFile string) []net.IPAddr {
-	file, err := os.Open(ipFile)
-	if err != nil {
-		log.Fatal(err)
-	}
-	firstIPs := make([]net.IPAddr, 0)
-	scanner := bufio.NewScanner(file)
-	scanner.Split(bufio.ScanLines)
-	for scanner.Scan() {
-		IPString := scanner.Text()
-		firstIP, IPRange, err := net.ParseCIDR(IPString)
-		if err != nil {
-			log.Fatal(err)
-		}
-		if ipv6Mode { // IPv6
-			var tempIP uint8
-			for IPRange.Contains(firstIP) {
-				//fmt.Println(firstIP)
-				//fmt.Println(firstIP[0], firstIP[1], firstIP[2], firstIP[3], firstIP[4], firstIP[5], firstIP[6], firstIP[7], firstIP[8], firstIP[9], firstIP[10], firstIP[11], firstIP[12], firstIP[13], firstIP[14], firstIP[15])
-				firstIP[15] = randipEndWith() // 随机 IP 的最后一段
-				firstIP[14] = randipEndWith() // 随机 IP 的最后一段
-				firstIPCopy := make([]byte, len(firstIP))
-				copy(firstIPCopy, firstIP)
-				firstIPs = append(firstIPs, net.IPAddr{IP: firstIPCopy})
-				tempIP = firstIP[13]
-				firstIP[13] += randipEndWith()
-				if firstIP[13] < tempIP {
-					tempIP = firstIP[12]
-					firstIP[12] += randipEndWith()
-					if firstIP[12] < tempIP {
-						tempIP = firstIP[11]
-						firstIP[11] += randipEndWith()
-						if firstIP[11] < tempIP {
-							tempIP = firstIP[10]
-							firstIP[10] += randipEndWith()
-							if firstIP[10] < tempIP {
-								tempIP = firstIP[9]
-								firstIP[9] += randipEndWith()
-								if firstIP[9] < tempIP {
-									tempIP = firstIP[8]
-									firstIP[8] += randipEndWith()
-									if firstIP[8] < tempIP {
-										tempIP = firstIP[7]
-										firstIP[7] += randipEndWith()
-										if firstIP[7] < tempIP {
-											tempIP = firstIP[6]
-											firstIP[6] += randipEndWith()
-											if firstIP[6] < tempIP {
-												tempIP = firstIP[5]
-												firstIP[5] += randipEndWith()
-												if firstIP[5] < tempIP {
-													tempIP = firstIP[4]
-													firstIP[4] += randipEndWith()
-													if firstIP[4] < tempIP {
-														tempIP = firstIP[3]
-														firstIP[3] += randipEndWith()
-														if firstIP[3] < tempIP {
-															tempIP = firstIP[2]
-															firstIP[2] += randipEndWith()
-															if firstIP[2] < tempIP {
-																tempIP = firstIP[1]
-																firstIP[1] += randipEndWith()
-																if firstIP[1] < tempIP {
-																	tempIP = firstIP[0]
-																	firstIP[0] += randipEndWith()
-																}
-															}
-														}
-													}
-												}
-											}
-										}
-									}
-								}
-							}
-						}
-					}
-				}
-			}
-		} else { //IPv4
-			for IPRange.Contains(firstIP) {
-				firstIP[15] = randipEndWith() // 随机 IP 的最后一段 0.0.0.X
-				firstIPCopy := make([]byte, len(firstIP))
-				copy(firstIPCopy, firstIP)
-				firstIPs = append(firstIPs, net.IPAddr{IP: firstIPCopy})
-				firstIP[14]++ // 0.0.(X+1).X
-				if firstIP[14] == 0 {
-					firstIP[13]++ // 0.(X+1).X.X
-					if firstIP[13] == 0 {
-						firstIP[12]++ // (X+1).X.X.X
-					}
-				}
-			}
-		}
-	}
-	return firstIPs
-}
+package main
+
+import (
+	"bufio"
+	"log"
+	"net"
+	"os"
+	"strconv"
+	"strings"
+)
+
+func getCidrHostNum(maskLen int) int {
+	cidrIpNum := int(0)
+	var i int = int(32 - maskLen - 1)
+	for ; i >= 1; i-- {
+		cidrIpNum += 1 << i
+	}
+	return cidrIpNum
+}
+
+func loadFirstIPOfRangeFromFile(ipFile string) []net.IPAddr {
+	file, err := os.Open(ipFile)
+	if err != nil {
+		log.Fatal(err)
+	}
+	firstIPs := make([]net.IPAddr, 0)
+	scanner := bufio.NewScanner(file)
+	scanner.Split(bufio.ScanLines)
+	for scanner.Scan() {
+		IPString := scanner.Text()
+		firstIP, IPRange, err := net.ParseCIDR(IPString)
+		//fmt.Println(firstIP)
+		//fmt.Println(IPRange)
+		Mask, _ := strconv.Atoi(strings.Split(scanner.Text(), "/")[1])
+		MaxIPNum := getCidrHostNum(Mask) - 1
+		if MaxIPNum > 253 {
+			MaxIPNum = 253
+		}
+		//fmt.Println(MaxIPNum)
+		if err != nil {
+			log.Fatal(err)
+		}
+		if ipv6Mode { // IPv6
+			var tempIP uint8
+			for IPRange.Contains(firstIP) {
+				//fmt.Println(firstIP)
+				//fmt.Println(firstIP[0], firstIP[1], firstIP[2], firstIP[3], firstIP[4], firstIP[5], firstIP[6], firstIP[7], firstIP[8], firstIP[9], firstIP[10], firstIP[11], firstIP[12], firstIP[13], firstIP[14], firstIP[15])
+				firstIP[15] = randipEndWith(MaxIPNum) // 随机 IP 的最后一段
+				firstIP[14] = randipEndWith(MaxIPNum) // 随机 IP 的最后一段
+				firstIPCopy := make([]byte, len(firstIP))
+				copy(firstIPCopy, firstIP)
+				firstIPs = append(firstIPs, net.IPAddr{IP: firstIPCopy})
+				tempIP = firstIP[13]
+				firstIP[13] += randipEndWith(MaxIPNum)
+				if firstIP[13] < tempIP {
+					tempIP = firstIP[12]
+					firstIP[12] += randipEndWith(MaxIPNum)
+					if firstIP[12] < tempIP {
+						tempIP = firstIP[11]
+						firstIP[11] += randipEndWith(MaxIPNum)
+						if firstIP[11] < tempIP {
+							tempIP = firstIP[10]
+							firstIP[10] += randipEndWith(MaxIPNum)
+							if firstIP[10] < tempIP {
+								tempIP = firstIP[9]
+								firstIP[9] += randipEndWith(MaxIPNum)
+								if firstIP[9] < tempIP {
+									tempIP = firstIP[8]
+									firstIP[8] += randipEndWith(MaxIPNum)
+									if firstIP[8] < tempIP {
+										tempIP = firstIP[7]
+										firstIP[7] += randipEndWith(MaxIPNum)
+										if firstIP[7] < tempIP {
+											tempIP = firstIP[6]
+											firstIP[6] += randipEndWith(MaxIPNum)
+											if firstIP[6] < tempIP {
+												tempIP = firstIP[5]
+												firstIP[5] += randipEndWith(MaxIPNum)
+												if firstIP[5] < tempIP {
+													tempIP = firstIP[4]
+													firstIP[4] += randipEndWith(MaxIPNum)
+													if firstIP[4] < tempIP {
+														tempIP = firstIP[3]
+														firstIP[3] += randipEndWith(MaxIPNum)
+														if firstIP[3] < tempIP {
+															tempIP = firstIP[2]
+															firstIP[2] += randipEndWith(MaxIPNum)
+															if firstIP[2] < tempIP {
+																tempIP = firstIP[1]
+																firstIP[1] += randipEndWith(MaxIPNum)
+																if firstIP[1] < tempIP {
+																	tempIP = firstIP[0]
+																	firstIP[0] += randipEndWith(MaxIPNum)
+																}
+															}
+														}
+													}
+												}
+											}
+										}
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+		} else { //IPv4
+			for IPRange.Contains(firstIP) {
+				//fmt.Println(firstIP)
+				//fmt.Println(firstIP[15])
+				if allip {
+					for i := 1; i < MaxIPNum+2; i++ {
+						firstIP[15] = uint8(i) // 随机 IP 的最后一段 0.0.0.X
+						//fmt.Println(firstIP)
+						firstIPCopy := make([]byte, len(firstIP))
+						copy(firstIPCopy, firstIP)
+						firstIPs = append(firstIPs, net.IPAddr{IP: firstIPCopy})
+					}
+				} else {
+					if firstIP[15] == 0 {
+						firstIP[15] = randipEndWith(MaxIPNum) // 随机 IP 的最后一段 0.0.0.X
+					}
+					firstIPCopy := make([]byte, len(firstIP))
+					copy(firstIPCopy, firstIP)
+					firstIPs = append(firstIPs, net.IPAddr{IP: firstIPCopy})
+				}
+				firstIP[15] = 0
+				firstIP[14]++ // 0.0.(X+1).X
+				if firstIP[14] == 0 {
+					firstIP[13]++ // 0.(X+1).X.X
+					if firstIP[13] == 0 {
+						firstIP[12]++ // (X+1).X.X.X
+					}
+				}
+			}
+		}
+	}
+	return firstIPs
+}

+ 163 - 161
README.md

@@ -1,161 +1,163 @@
-# XIU2/CloudflareSpeedTest
-
-[![Go Version](https://img.shields.io/github/go-mod/go-version/XIU2/CloudflareSpeedTest.svg?style=flat-square&label=Go&color=00ADD8)](https://github.com/XIU2/CloudflareSpeedTest/blob/master/go.mod)
-[![Release Version](https://img.shields.io/github/v/release/XIU2/CloudflareSpeedTest.svg?style=flat-square&label=Release&color=1784ff)](https://github.com/XIU2/CloudflareSpeedTest/releases/latest)
-[![GitHub license](https://img.shields.io/github/license/XIU2/CloudflareSpeedTest.svg?style=flat-square&label=License&color=f38020)](https://github.com/XIU2/CloudflareSpeedTest/blob/master/LICENSE)
-[![GitHub Star](https://img.shields.io/github/stars/XIU2/CloudflareSpeedTest.svg?style=flat-square&label=Star&color=f38020)](https://github.com/XIU2/CloudflareSpeedTest/stargazers)
-[![GitHub Fork](https://img.shields.io/github/forks/XIU2/CloudflareSpeedTest.svg?style=flat-square&label=Fork&color=f38020)](https://github.com/XIU2/CloudflareSpeedTest/network/members)
-
-国外很多网站都在使用 Cloudflare CDN,但分配给中国访客的 IP 并不友好。  
-虽然 Cloudflare 公开了所有 [IP 段](https://www.cloudflare.com/ips/) ,但想要在这么多 IP 中找到适合自己的,怕是要累死,所以就有了这个软件。  
-
-该软件可以**测试 Cloudflare CDN 所有 IP 的延迟和速度,获得最快 IP**!觉得好用请**点个⭐鼓励一下下~**  
-将 IP 添加到 `Hosts` 文件或 DNS 程序中,以提高访问使用 Cloudflare CDN 的网站速度!  
-
-> 本项目也**适用于其他 CDN**,但是需要自行寻找 **CDN IP 段及下载测速地址**(否则只能延迟测速)!
-
-****
-## 快速使用
-
-### 下载运行
-
-1. 下载编译好的可执行文件 [蓝奏云](https://xiu.lanzoux.com/b0742hkxe) / [Github](https://github.com/XIU2/CloudflareSpeedTest/releases) 并解压。  
-2. 双击运行 `CloudflareST.exe`文件(Windows),等待测速...  
-
->  **注意:Linux 系统**请先赋予执行权限 `chmod +x CloudflareST` ,然后再执行 `./CloudflareST` 。  
-
-### 结果示例
-
-测速完毕后,会直接显示**最快的 20 个 IP**,示例:  
-
-```
-IP 地址           已发送  已接收  丢包率  平均延迟  下载速度 (MB/s)
-104.27.198.101    4       4       0.00    126.52    12.71
-104.22.43.157     4       4       0.00    129.38    16.74
-104.27.214.140    4       4       0.00    132.02    4.65
-104.22.42.165     4       4       0.00    133.63    12.00
-104.22.35.177     4       4       0.00    135.75    3.92
-104.22.87.44      4       4       0.00    136.00    5.86
-104.22.67.122     4       4       0.00    136.50    9.47
-104.22.88.154     4       4       0.00    140.75    13.00
-104.22.69.218     4       4       0.00    142.00    19.07
-104.27.184.10     4       4       0.00    148.02    21.05
-...
-```
-
-完整结果保存在当前目录下的 `result.csv` 文件中,用**记事本/表格软件**打开,排序为**延迟由低到高**,分别是:  
-
-```
-IP 地址, 已发送, 已接收, 丢包率, 平均延迟, 下载速度 (MB/s)
-104.27.199.141, 4, 4, 0.00, 139.52, 11.71
-```
-> 大家可以按照自己的需求,对完整测速数据**进一步筛选处理**!
-
-选择一个平均延迟与下载速度都不错的 IP 放到 `Hosts` 文件中(指向使用 Cloudflare CDN 的网站域名)。  
-
-****
-## 进阶使用
-
-直接双击运行使用的是默认参数,如果想要测试速度更快、测试结果更全面,可以自定义参数。  
-
-``` cmd
-C:\>CloudflareST.exe -h
-
-CloudflareSpeedTest vX.X.X
-测试 Cloudflare CDN 所有 IP 的延迟和速度,获取最快 IP!
-https://github.com/XIU2/CloudflareSpeedTest
-
-参数:
-    -n 500
-        测速线程数量;数值越大速度越快,请勿超过 1000(结果误差大);(默认 500)
-    -t 4
-        延迟测速次数;单个 IP 测速次数,为 1 时将过滤丢包的IP,TCP协议;(默认 4)
-    -tp 443
-        延迟测速端口;延迟测速 TCP 协议的端口;(默认 443)
-    -dn 20
-        下载测速数量;延迟测速并排序后,从最低延迟起下载测速数量,请勿太多(速度慢);(默认 20)
-    -dt 5
-        下载测速时间;单个 IP 测速最长时间,单位:秒;(默认 5)
-    -url https://cf.xiu2.xyz/Github/CloudflareSpeedTest.png
-        下载测速地址;用来 Cloudflare CDN 测速的文件地址,如含有空格请加上引号;
-    -tl 200
-        延迟时间上限;只输出指定延迟时间以下的结果,数量为 -dn 参数的值,单位:ms;
-    -sl 5
-        下载速度下限;只输出指定下载速度以上的结果,数量为 -dn 参数的值,单位:MB/s;
-    -p 20
-        显示结果数量;测速后直接显示指定数量的结果,为 0 时不显示结果直接退出;(默认 20)
-    -f ip.txt
-        IP 数据文件;如含有空格请加上引号;支持其他 CDN IP段,记得禁用下载测速;(默认 ip.txt)
-    -o result.csv
-        输出结果文件;如含有空格请加上引号;为空格时不输出结果文件(-o " ");允许其他后缀;(默认 result.csv)
-    -dd
-        禁用下载测速;如果带上该参数就是禁用下载测速;(默认 启用)
-    -ipv6
-        IPv6 测速模式;请确保 IP 数据文件内只包含 IPv6 IP段,软件不支持同时测速 IPv4+IPv6;(默认 IPv4)
-    -v
-        打印程序版本
-    -h
-        打印帮助说明
-```
-
-> 如果**下载速度都是 0.00**,那说明默认的**下载测速地址**用的人太多又到上限了,**请去这个 [Issues](https://github.com/XIU2/CloudflareSpeedTest/issues/6) 获得解决方法!**  
-
-### 使用示例
-
-在 CMD 中运行,或者把启动参数添加到快捷方式中。  
-
-``` bash
-# 命令行示例
-# 注意:各参数均有默认值,只有不使用默认值时,才需要手动指定参数的值(按需选择),参数不分前后顺序。  
-# 提示: Linux 系统只需要把下面命令中的 CloudflareST.exe 改为 ./CloudflareST 即可。  
-
-# 指定 IPv4 数据文件,不显示结果直接退出(-p 值为 0)
-CloudflareST.exe -p 0 -f ip.txt -dd
-
-# 指定 IPv6 数据文件( ipv6.txt ),不显示结果直接退出(-p 值为 0)
-CloudflareST.exe -p 0 -f ipv6.txt -dd -ipv6
-
-# 指定 IPv4 数据文件,不输出结果到文件,直接显示结果(-p 值为 10 条)
-CloudflareST.exe -p 10 -f ip.txt -o " " -dd
-
-# 指定 IPv4 数据文件 及 输出结果到文件(相对路径,即当前目录下,如果包含空格请加上引号)
-CloudflareST.exe -f ip.txt -o result.csv -dd
-
-# 指定 IPv4 数据文件 及 输出结果到文件(绝对路径,即 C:\abc\ 目录下,如果包含空格请加上引号)
-CloudflareST.exe -f C:\abc\ip.txt -o C:\abc\result.csv -dd
-
-# 指定下载测速地址(要求:可以直接下载、文件大小超过 200MB、用的是 Cloudflare CDN),如果包含空格请加上引号
-CloudflareST.exe -url https://cf.xiu2.xyz/Github/CloudflareSpeedTest.png
-
-# 指定测速条件(只有同时满足三个条件时才会停止测速):
-# 延迟时间上限:200 ms,下载速度下限:0 MB/s,数量:10 个
-CloudflareST.exe -tl 200 -dn 10
-
-# 延迟时间上限:0 ms,下载速度下限:5 MB/s,数量:10 个
-CloudflareST.exe -sl 5 -dn 10
-
-# 延迟时间上限:200 ms,下载速度下限:5 MB/s,数量:10 个
-CloudflareST.exe -tl 200 -sl 5 -dn 10
-
-# 如果一直凑不够指定数量,会一直测速下去。  
-# 建议指定下载速度下限时,同时指定延迟时间上限,如果测试到指定延迟还没凑够数,就会终止测速。
-# 如果一个满足条件的 IP 都没有,那么就会正常输出结果(和不指定条件一样)。
-# 如果你需要通过外部程序进一步筛选处理,那么只需要判断测速结果数量,如果上千个说明一个满足条件的 IP 都没有。
-```
-
-``` cmd
-# Windows 快捷方式示例(右键快捷方式 - 目标)
-## 如果有引号就放在引号外面,记得引号和 - 之间有空格。
-### 如果要不输出结果文件,那么请加上 -o " ",引号里的是空格。
-"D:\Program Files\CloudflareST\CloudflareST.exe" -n 500 -t 4 -dn 20 -dt 5
-```
-
-****
-## 感谢项目
-* https://github.com/Spedoske/CloudflareScanner
-
-意外发现了这个项目,看了之后发现正好解决了我的问题,但是我更喜欢用户命令行方式运行,这样会更方便、有更多使用姿势,于是我临时学了下 Golang 并 Fork 按照我自己的需求修改了一下(包括但不限于命令行方式交互、直接输出结果等),如果有什么问题可以告诉我,虽然我不一定会~
-
-****
-## 许可证
-The GPL-3.0 License.
+# XIU2/CloudflareSpeedTest
+
+[![Go Version](https://img.shields.io/github/go-mod/go-version/XIU2/CloudflareSpeedTest.svg?style=flat-square&label=Go&color=00ADD8)](https://github.com/XIU2/CloudflareSpeedTest/blob/master/go.mod)
+[![Release Version](https://img.shields.io/github/v/release/XIU2/CloudflareSpeedTest.svg?style=flat-square&label=Release&color=1784ff)](https://github.com/XIU2/CloudflareSpeedTest/releases/latest)
+[![GitHub license](https://img.shields.io/github/license/XIU2/CloudflareSpeedTest.svg?style=flat-square&label=License&color=f38020)](https://github.com/XIU2/CloudflareSpeedTest/blob/master/LICENSE)
+[![GitHub Star](https://img.shields.io/github/stars/XIU2/CloudflareSpeedTest.svg?style=flat-square&label=Star&color=f38020)](https://github.com/XIU2/CloudflareSpeedTest/stargazers)
+[![GitHub Fork](https://img.shields.io/github/forks/XIU2/CloudflareSpeedTest.svg?style=flat-square&label=Fork&color=f38020)](https://github.com/XIU2/CloudflareSpeedTest/network/members)
+
+国外很多网站都在使用 Cloudflare CDN,但分配给中国访客的 IP 并不友好。  
+虽然 Cloudflare 公开了所有 [IP 段](https://www.cloudflare.com/ips/) ,但想要在这么多 IP 中找到适合自己的,怕是要累死,所以就有了这个软件。  
+
+该软件可以**测试 Cloudflare CDN 所有 IP 的延迟和速度,获得最快 IP**!觉得好用请**点个⭐鼓励一下下~**  
+将 IP 添加到 `Hosts` 文件或 DNS 程序中,以提高访问使用 Cloudflare CDN 的网站速度!  
+
+> 本项目也**适用于其他 CDN**,但是需要自行寻找 **CDN IP 段及下载测速地址**(否则只能延迟测速)!
+
+****
+## 快速使用
+
+### 下载运行
+
+1. 下载编译好的可执行文件 [蓝奏云](https://xiu.lanzoux.com/b0742hkxe) / [Github](https://github.com/XIU2/CloudflareSpeedTest/releases) 并解压。  
+2. 双击运行 `CloudflareST.exe`文件(Windows),等待测速...  
+
+>  **注意:Linux 系统**请先赋予执行权限 `chmod +x CloudflareST` ,然后再执行 `./CloudflareST` 。  
+
+### 结果示例
+
+测速完毕后,会直接显示**最快的 20 个 IP**,示例:  
+
+```
+IP 地址           已发送  已接收  丢包率  平均延迟  下载速度 (MB/s)
+104.27.198.101    4       4       0.00    126.52    12.71
+104.22.43.157     4       4       0.00    129.38    16.74
+104.27.214.140    4       4       0.00    132.02    4.65
+104.22.42.165     4       4       0.00    133.63    12.00
+104.22.35.177     4       4       0.00    135.75    3.92
+104.22.87.44      4       4       0.00    136.00    5.86
+104.22.67.122     4       4       0.00    136.50    9.47
+104.22.88.154     4       4       0.00    140.75    13.00
+104.22.69.218     4       4       0.00    142.00    19.07
+104.27.184.10     4       4       0.00    148.02    21.05
+...
+```
+
+完整结果保存在当前目录下的 `result.csv` 文件中,用**记事本/表格软件**打开,排序为**延迟由低到高**,分别是:  
+
+```
+IP 地址, 已发送, 已接收, 丢包率, 平均延迟, 下载速度 (MB/s)
+104.27.199.141, 4, 4, 0.00, 139.52, 11.71
+```
+> 大家可以按照自己的需求,对完整测速数据**进一步筛选处理**!
+
+选择一个平均延迟与下载速度都不错的 IP 放到 `Hosts` 文件中(指向使用 Cloudflare CDN 的网站域名)。  
+
+****
+## 进阶使用
+
+直接双击运行使用的是默认参数,如果想要测试速度更快、测试结果更全面,可以自定义参数。  
+
+``` cmd
+C:\>CloudflareST.exe -h
+
+CloudflareSpeedTest vX.X.X
+测试 Cloudflare CDN 所有 IP 的延迟和速度,获取最快 IP!
+https://github.com/XIU2/CloudflareSpeedTest
+
+参数:
+    -n 500
+        测速线程数量;数值越大速度越快,请勿超过 1000(结果误差大);(默认 500)
+    -t 4
+        延迟测速次数;单个 IP 测速次数,为 1 时将过滤丢包的IP,TCP协议;(默认 4)
+    -tp 443
+        延迟测速端口;延迟测速 TCP 协议的端口;(默认 443)
+    -dn 20
+        下载测速数量;延迟测速并排序后,从最低延迟起下载测速数量,请勿太多(速度慢);(默认 20)
+    -dt 5
+        下载测速时间;单个 IP 测速最长时间,单位:秒;(默认 5)
+    -url https://cf.xiu2.xyz/Github/CloudflareSpeedTest.png
+        下载测速地址;用来 Cloudflare CDN 测速的文件地址,如含有空格请加上引号;
+    -tl 200
+        延迟时间上限;只输出指定延迟时间以下的结果,数量为 -dn 参数的值,单位:ms;
+    -sl 5
+        下载速度下限;只输出指定下载速度以上的结果,数量为 -dn 参数的值,单位:MB/s;
+    -p 20
+        显示结果数量;测速后直接显示指定数量的结果,为 0 时不显示结果直接退出;(默认 20)
+    -f ip.txt
+        IP 数据文件;如含有空格请加上引号;支持其他 CDN IP段,记得禁用下载测速;(默认 ip.txt)
+    -o result.csv
+        输出结果文件;如含有空格请加上引号;为空格时不输出结果文件(-o " ");允许其他后缀;(默认 result.csv)
+    -dd
+        禁用下载测速;如果带上该参数就是禁用下载测速;(默认 启用)
+    -ipv6
+        IPv6 测速模式;请确保 IP 数据文件内只包含 IPv6 IP段,软件不支持同时测速 IPv4+IPv6;(默认 IPv4)
+    -allip
+        测速全部 IP;如果带上该参数将会对每个 IP 进行测速;(默认 每个IP段随机测速一个 IP)
+    -v
+        打印程序版本+检查版本更新
+    -h
+        打印帮助说明
+```
+
+> 如果**下载速度都是 0.00**,那说明默认的**下载测速地址**用的人太多又到上限了,**请去这个 [Issues](https://github.com/XIU2/CloudflareSpeedTest/issues/6) 获得解决方法!**  
+
+### 使用示例
+
+在 CMD 中运行,或者把启动参数添加到快捷方式中。  
+
+``` bash
+# 命令行示例
+# 注意:各参数均有默认值,只有不使用默认值时,才需要手动指定参数的值(按需选择),参数不分前后顺序。  
+# 提示: Linux 系统只需要把下面命令中的 CloudflareST.exe 改为 ./CloudflareST 即可。  
+
+# 指定 IPv4 数据文件,不显示结果直接退出(-p 值为 0)
+CloudflareST.exe -p 0 -f ip.txt -dd
+
+# 指定 IPv6 数据文件( ipv6.txt ),不显示结果直接退出(-p 值为 0)
+CloudflareST.exe -p 0 -f ipv6.txt -dd -ipv6
+
+# 指定 IPv4 数据文件,不输出结果到文件,直接显示结果(-p 值为 10 条)
+CloudflareST.exe -p 10 -f ip.txt -o " " -dd
+
+# 指定 IPv4 数据文件 及 输出结果到文件(相对路径,即当前目录下,如果包含空格请加上引号)
+CloudflareST.exe -f ip.txt -o result.csv -dd
+
+# 指定 IPv4 数据文件 及 输出结果到文件(绝对路径,即 C:\abc\ 目录下,如果包含空格请加上引号)
+CloudflareST.exe -f C:\abc\ip.txt -o C:\abc\result.csv -dd
+
+# 指定下载测速地址(要求:可以直接下载、文件大小超过 200MB、用的是 Cloudflare CDN),如果包含空格请加上引号
+CloudflareST.exe -url https://cf.xiu2.xyz/Github/CloudflareSpeedTest.png
+
+# 指定测速条件(只有同时满足三个条件时才会停止测速):
+# 延迟时间上限:200 ms,下载速度下限:0 MB/s,数量:10 个
+CloudflareST.exe -tl 200 -dn 10
+
+# 延迟时间上限:0 ms,下载速度下限:5 MB/s,数量:10 个
+CloudflareST.exe -sl 5 -dn 10
+
+# 延迟时间上限:200 ms,下载速度下限:5 MB/s,数量:10 个
+CloudflareST.exe -tl 200 -sl 5 -dn 10
+
+# 如果一直凑不够指定数量,会一直测速下去。  
+# 建议指定下载速度下限时,同时指定延迟时间上限,如果测试到指定延迟还没凑够数,就会终止测速。
+# 如果一个满足条件的 IP 都没有,那么就会正常输出结果(和不指定条件一样)。
+# 如果你需要通过外部程序进一步筛选处理,那么只需要判断测速结果数量,如果上千个说明一个满足条件的 IP 都没有。
+```
+
+``` cmd
+# Windows 快捷方式示例(右键快捷方式 - 目标)
+## 如果有引号就放在引号外面,记得引号和 - 之间有空格。
+### 如果要不输出结果文件,那么请加上 -o " ",引号里的是空格。
+"D:\Program Files\CloudflareST\CloudflareST.exe" -n 500 -t 4 -dn 20 -dt 5
+```
+
+****
+## 感谢项目
+* https://github.com/Spedoske/CloudflareScanner
+
+意外发现了这个项目,看了之后发现正好解决了我的问题,但是我更喜欢用户命令行方式运行,这样会更方便、有更多使用姿势,于是我临时学了下 Golang 并 Fork 按照我自己的需求修改了一下(包括但不限于命令行方式交互、直接输出结果等),如果有什么问题可以告诉我,虽然我不一定会~
+
+****
+## 许可证
+The GPL-3.0 License.

+ 272 - 243
main.go

@@ -1,243 +1,272 @@
-package main
-
-import (
-	"flag"
-	"fmt"
-	"os"
-	"runtime"
-	"sort"
-	"strconv"
-	"sync"
-	"time"
-
-	"github.com/cheggaaa/pb/v3"
-)
-
-var version string
-var disableDownload bool
-var ipv6Mode bool
-var tcpPort int
-var ipFile string
-var outputFile string
-var printResultNum int
-var timeLimit int
-var speedLimit int
-
-func init() {
-	var downloadSecond int64
-	var printVersion bool
-	var help = `
-CloudflareSpeedTest ` + version + `
-测试 Cloudflare CDN 所有 IP 的延迟和速度,获取最快 IP!
-https://github.com/XIU2/CloudflareSpeedTest
-
-参数:
-    -n 500
-        测速线程数量;数值越大速度越快,请勿超过 1000(结果误差大);(默认 500)
-    -t 4
-        延迟测速次数;单个 IP 测速次数,为 1 时将过滤丢包的IP,TCP协议;(默认 4)
-    -tp 443
-        延迟测速端口;延迟测速 TCP 协议的端口;(默认 443)
-    -dn 20
-        下载测速数量;延迟测速并排序后,从最低延迟起下载测速数量,请勿太多(速度慢);(默认 20)
-    -dt 5
-        下载测速时间;单个 IP 测速最长时间,单位:秒;(默认 5)
-    -url https://cf.xiu2.xyz/Github/CloudflareSpeedTest.png
-        下载测速地址;用来 Cloudflare CDN 测速的文件地址,如含有空格请加上引号;
-    -tl 200
-        延迟时间上限;只输出指定延迟时间以下的结果,数量为 -dn 参数的值,单位:ms;
-    -sl 5
-        下载速度下限;只输出指定下载速度以上的结果,数量为 -dn 参数的值,单位:MB/s;
-    -p 20
-        显示结果数量;测速后直接显示指定数量的结果,值为 0 时不显示结果直接退出;(默认 20)
-    -f ip.txt
-        IP 数据文件;如含有空格请加上引号;支持其他 CDN IP段,记得禁用下载测速;(默认 ip.txt)
-    -o result.csv
-        输出结果文件;如含有空格请加上引号;为空格时不输出结果文件(-o " ");允许其他后缀;(默认 result.csv)
-    -dd
-        禁用下载测速;如果带上该参数就是禁用下载测速;(默认 启用下载测速)
-    -ipv6
-        IPv6 测速模式;请确保 IP 数据文件内只包含 IPv6 IP段,软件不支持同时测速 IPv4+IPv6;(默认 IPv4)
-    -v
-        打印程序版本
-    -h
-        打印帮助说明
-`
-
-	flag.IntVar(&pingRoutine, "n", 500, "测速线程数量")
-	flag.IntVar(&pingTime, "t", 4, "延迟测速次数")
-	flag.IntVar(&tcpPort, "tp", 443, "延迟测速端口")
-	flag.IntVar(&downloadTestCount, "dn", 20, "下载测速数量")
-	flag.Int64Var(&downloadSecond, "dt", 5, "下载测速时间")
-	flag.StringVar(&url, "url", "https://cf.xiu2.xyz/Github/CloudflareSpeedTest.png", "下载测速地址")
-	flag.IntVar(&timeLimit, "tl", 0, "延迟时间上限")
-	flag.IntVar(&speedLimit, "sl", 0, "下载速度下限")
-	flag.IntVar(&printResultNum, "p", 20, "显示结果数量")
-	flag.BoolVar(&disableDownload, "dd", false, "禁用下载测速")
-	flag.BoolVar(&ipv6Mode, "ipv6", false, "禁用下载测速")
-	flag.StringVar(&ipFile, "f", "ip.txt", "IP 数据文件")
-	flag.StringVar(&outputFile, "o", "result.csv", "输出结果文件")
-	flag.BoolVar(&printVersion, "v", false, "打印程序版本")
-
-	downloadTestTime = time.Duration(downloadSecond) * time.Second
-
-	flag.Usage = func() { fmt.Print(help) }
-	flag.Parse()
-	if printVersion {
-		println(version)
-		os.Exit(0)
-	}
-	if pingRoutine <= 0 {
-		pingRoutine = 500
-	}
-	if pingTime <= 0 {
-		pingTime = 4
-	}
-	if tcpPort < 1 || tcpPort > 65535 {
-		tcpPort = 443
-	}
-	if downloadTestCount <= 0 {
-		downloadTestCount = 20
-	}
-	if downloadSecond <= 0 {
-		downloadSecond = 10
-	}
-	if url == "" {
-		url = "https://cf.xiu2.xyz/Github/CloudflareSpeedTest.png"
-	}
-	if timeLimit <= 0 {
-		timeLimit = 9999
-	}
-	if speedLimit < 0 {
-		speedLimit = 0
-	}
-	if printResultNum < 0 {
-		printResultNum = 20
-	}
-	if ipFile == "" {
-		ipFile = "ip.txt"
-	}
-	if outputFile == " " {
-		outputFile = ""
-	}
-}
-
-func main() {
-	initRandSeed()                            // 置随机数种子
-	failTime = pingTime                       // 设置接收次数
-	ips := loadFirstIPOfRangeFromFile(ipFile) // 读入IP
-	pingCount := len(ips) * pingTime          // 计算进度条总数(IP*测试次数)
-	bar := pb.Simple.Start(pingCount)         // 进度条总数
-	var wg sync.WaitGroup
-	var mu sync.Mutex
-	var data = make([]CloudflareIPData, 0)
-	var data_2 = make([]CloudflareIPData, 0)
-
-	fmt.Println("# XIU2/CloudflareSpeedTest " + version + "\n")
-	if ipv6Mode {
-		fmt.Println("开始延迟测速(模式:TCP IPv6,端口:" + strconv.Itoa(tcpPort) + "):")
-	} else {
-		fmt.Println("开始延迟测速(模式:TCP IPv4,端口:" + strconv.Itoa(tcpPort) + "):")
-	}
-	control := make(chan bool, pingRoutine)
-	for _, ip := range ips {
-		wg.Add(1)
-		control <- false
-		handleProgress := handleProgressGenerator(bar) // 多线程进度条
-		go tcpingGoroutine(&wg, &mu, ip, tcpPort, pingTime, &data, control, handleProgress)
-	}
-	wg.Wait()
-	bar.Finish()
-
-	sort.Sort(CloudflareIPDataSet(data)) // 排序
-
-	// 下载测速
-	if !disableDownload { // 如果禁用下载测速就跳过
-		if len(data) > 0 { // IP数组长度(IP数量) 大于 0 时继续
-			if len(data) < downloadTestCount { // 如果IP数组长度(IP数量) 小于 下载测速次数,则次数改为IP数
-				//fmt.Println("\n[信息] IP 数量小于下载测速次数(" + strconv.Itoa(downloadTestCount) + " < " + strconv.Itoa(len(data)) + "),下载测速次数改为IP数。\n")
-				downloadTestCount = len(data)
-			}
-			var downloadTestCount_2 int // 临时的下载测速次数
-			if timeLimit == 9999 && speedLimit == 0 {
-				downloadTestCount_2 = downloadTestCount // 如果没有指定条件,则临时的下载次数变量为下载测速次数
-				fmt.Println("开始下载测速:")
-			} else if timeLimit > 0 || speedLimit >= 0 {
-				downloadTestCount_2 = len(data) // 如果指定了任意一个条件,则临时的下载次数变量改为总数量
-				fmt.Println("开始下载测速(延迟时间上限:" + strconv.Itoa(timeLimit) + " ms,下载速度下限:" + strconv.Itoa(speedLimit) + " MB/s):")
-			}
-			bar = pb.Simple.Start(downloadTestCount_2)
-			for i := 0; i < downloadTestCount_2; i++ {
-				_, speed := DownloadSpeedHandler(data[i].ip)
-				data[i].downloadSpeed = speed
-				bar.Add(1)
-				if int(data[i].pingTime) <= timeLimit && int(float64(speed)/1024/1024) >= speedLimit {
-					data_2 = append(data_2, data[i])      // 延迟和速度均满足条件时,添加到新数组中
-					if len(data_2) == downloadTestCount { // 满足条件的 IP =下载测速次数,则跳出循环
-						break
-					}
-				} else if int(data[i].pingTime) > timeLimit {
-					break
-				}
-			}
-			bar.Finish()
-		} else {
-			fmt.Println("\n[信息] IP数量为 0,跳过下载测速。")
-		}
-	}
-
-	if len(data_2) > 0 { // 如果该数字有内容,说明进行过指定条件的下载测速
-		if outputFile != "" {
-			ExportCsv(outputFile, data_2) // 输出结果到文件(指定延迟时间或下载速度的)
-		}
-		printResult(data_2) // 显示最快结果(指定延迟时间或下载速度的)
-	} else {
-		if outputFile != "" {
-			ExportCsv(outputFile, data) // 输出结果到文件
-		}
-		printResult(data) // 显示最快结果
-	}
-}
-
-// 显示最快结果
-func printResult(data []CloudflareIPData) {
-	sysType := runtime.GOOS
-	if printResultNum > 0 { // 如果禁止直接输出结果就跳过
-		dateString := convertToString(data) // 转为多维数组 [][]String
-		if len(dateString) > 0 {            // IP数组长度(IP数量) 大于 0 时继续
-			if len(dateString) < printResultNum { // 如果IP数组长度(IP数量) 小于  打印次数,则次数改为IP数量
-				//fmt.Println("\n[信息] IP 数量小于显示结果数量(" + strconv.Itoa(printResultNum) + " < " + strconv.Itoa(len(dateString)) + "),显示结果数量改为IP数量。\n")
-				printResultNum = len(dateString)
-			}
-			if ipv6Mode { // IPv6 太长了,所以需要调整一下间隔
-				fmt.Printf("%-40s%-5s%-5s%-5s%-6s%-11s\n", "IP 地址", "已发送", "已接收", "丢包率", "平均延迟", "下载速度 (MB/s)")
-				for i := 0; i < printResultNum; i++ {
-					fmt.Printf("%-42s%-8s%-8s%-8s%-10s%-15s\n", ipPadding(dateString[i][0]), dateString[i][1], dateString[i][2], dateString[i][3], dateString[i][4], dateString[i][5])
-				}
-			} else {
-				fmt.Printf("%-16s%-5s%-5s%-5s%-6s%-11s\n", "IP 地址", "已发送", "已接收", "丢包率", "平均延迟", "下载速度 (MB/s)")
-				for i := 0; i < printResultNum; i++ {
-					fmt.Printf("%-18s%-8s%-8s%-8s%-10s%-15s\n", ipPadding(dateString[i][0]), dateString[i][1], dateString[i][2], dateString[i][3], dateString[i][4], dateString[i][5])
-				}
-			}
-
-			if sysType == "windows" { // 如果是 Windows 系统,则需要按下 回车键 或 Ctrl+C 退出
-				if outputFile != "" {
-					fmt.Printf("\n完整测速结果已写入 %v 文件,请使用记事本/表格软件查看。\n按下 回车键 或 Ctrl+C 退出。", outputFile)
-				} else {
-					fmt.Printf("\n按下 回车键 或 Ctrl+C 退出。")
-				}
-				var pause int
-				fmt.Scanln(&pause)
-			} else { // 其它系统直接退出
-				if outputFile != "" {
-					fmt.Println("\n完整测速结果已写入 " + outputFile + " 文件,请使用记事本/表格软件查看。")
-				}
-			}
-		} else {
-			fmt.Println("\n[信息] IP数量为 0,跳过输出结果。")
-		}
-	} else {
-		fmt.Println("\n完整测速结果已写入 " + outputFile + " 文件,请使用记事本/表格软件查看。")
-	}
-}
+package main
+
+import (
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"os"
+	"runtime"
+	"sort"
+	"strconv"
+	"sync"
+	"time"
+
+	"github.com/cheggaaa/pb/v3"
+)
+
+var version, ipFile, outputFile, versionNew string
+var disableDownload, ipv6Mode, allip bool
+var tcpPort, printResultNum, timeLimit, speedLimit int
+
+func init() {
+	var downloadSecond int64
+	var printVersion bool
+	var help = `
+CloudflareSpeedTest ` + version + `
+测试 Cloudflare CDN 所有 IP 的延迟和速度,获取最快 IP!
+https://github.com/XIU2/CloudflareSpeedTest
+
+参数:
+    -n 500
+        测速线程数量;数值越大速度越快,请勿超过 1000(结果误差大);(默认 500)
+    -t 4
+        延迟测速次数;单个 IP 测速次数,为 1 时将过滤丢包的IP,TCP协议;(默认 4)
+    -tp 443
+        延迟测速端口;延迟测速 TCP 协议的端口;(默认 443)
+    -dn 20
+        下载测速数量;延迟测速并排序后,从最低延迟起下载测速数量,请勿太多(速度慢);(默认 20)
+    -dt 5
+        下载测速时间;单个 IP 测速最长时间,单位:秒;(默认 5)
+    -url https://cf.xiu2.xyz/Github/CloudflareSpeedTest.png
+        下载测速地址;用来 Cloudflare CDN 测速的文件地址,如含有空格请加上引号;
+    -tl 200
+        延迟时间上限;只输出指定延迟时间以下的结果,数量为 -dn 参数的值,单位:ms;
+    -sl 5
+        下载速度下限;只输出指定下载速度以上的结果,数量为 -dn 参数的值,单位:MB/s;
+    -p 20
+        显示结果数量;测速后直接显示指定数量的结果,值为 0 时不显示结果直接退出;(默认 20)
+    -f ip.txt
+        IP 数据文件;如含有空格请加上引号;支持其他 CDN IP段,记得禁用下载测速;(默认 ip.txt)
+    -o result.csv
+        输出结果文件;如含有空格请加上引号;为空格时不输出结果文件(-o " ");允许其他后缀;(默认 result.csv)
+    -dd
+        禁用下载测速;如果带上该参数将会禁用下载测速;(默认 启用下载测速)
+    -ipv6
+        IPv6 测速模式;请确保 IP 数据文件内只包含 IPv6 IP段,软件不支持同时测速 IPv4+IPv6;(默认 IPv4)
+    -allip
+        测速全部 IP;如果带上该参数将会对每个 IP 进行测速;(默认 每个IP段随机测速一个 IP)
+    -v
+        打印程序版本+检查版本更新
+    -h
+        打印帮助说明
+`
+
+	flag.IntVar(&pingRoutine, "n", 500, "测速线程数量")
+	flag.IntVar(&pingTime, "t", 4, "延迟测速次数")
+	flag.IntVar(&tcpPort, "tp", 443, "延迟测速端口")
+	flag.IntVar(&downloadTestCount, "dn", 20, "下载测速数量")
+	flag.Int64Var(&downloadSecond, "dt", 5, "下载测速时间")
+	flag.StringVar(&url, "url", "https://cf.xiu2.xyz/Github/CloudflareSpeedTest.png", "下载测速地址")
+	flag.IntVar(&timeLimit, "tl", 0, "延迟时间上限")
+	flag.IntVar(&speedLimit, "sl", 0, "下载速度下限")
+	flag.IntVar(&printResultNum, "p", 20, "显示结果数量")
+	flag.BoolVar(&disableDownload, "dd", false, "禁用下载测速")
+	flag.BoolVar(&ipv6Mode, "ipv6", false, "禁用下载测速")
+	flag.BoolVar(&allip, "allip", false, "测速全部 IP")
+	flag.StringVar(&ipFile, "f", "ip.txt", "IP 数据文件")
+	flag.StringVar(&outputFile, "o", "result.csv", "输出结果文件")
+	flag.BoolVar(&printVersion, "v", false, "打印程序版本")
+
+	downloadTestTime = time.Duration(downloadSecond) * time.Second
+
+	flag.Usage = func() { fmt.Print(help) }
+	flag.Parse()
+	if printVersion {
+		println(version)
+		fmt.Println("检查版本更新中...")
+		checkUpdate()
+		if versionNew != "" {
+			fmt.Println("发现新版本 [" + versionNew + "]!请前往 [https://github.com/XIU2/CloudflareSpeedTest] 更新!")
+		} else {
+			fmt.Println("当前为最新版本 [" + version + "]!")
+		}
+		os.Exit(0)
+	}
+	if pingRoutine <= 0 {
+		pingRoutine = 500
+	}
+	if pingTime <= 0 {
+		pingTime = 4
+	}
+	if tcpPort < 1 || tcpPort > 65535 {
+		tcpPort = 443
+	}
+	if downloadTestCount <= 0 {
+		downloadTestCount = 20
+	}
+	if downloadSecond <= 0 {
+		downloadSecond = 10
+	}
+	if url == "" {
+		url = "https://cf.xiu2.xyz/Github/CloudflareSpeedTest.png"
+	}
+	if timeLimit <= 0 {
+		timeLimit = 9999
+	}
+	if speedLimit < 0 {
+		speedLimit = 0
+	}
+	if printResultNum < 0 {
+		printResultNum = 20
+	}
+	if ipFile == "" {
+		ipFile = "ip.txt"
+	}
+	if outputFile == " " {
+		outputFile = ""
+	}
+}
+
+func main() {
+	go checkUpdate()                          // 检查版本更新
+	initRandSeed()                            // 置随机数种子
+	failTime = pingTime                       // 设置接收次数
+	ips := loadFirstIPOfRangeFromFile(ipFile) // 读入IP
+	pingCount := len(ips) * pingTime          // 计算进度条总数(IP*测试次数)
+	bar := pb.Simple.Start(pingCount)         // 进度条总数
+	var wg sync.WaitGroup
+	var mu sync.Mutex
+	var data = make([]CloudflareIPData, 0)
+	var data_2 = make([]CloudflareIPData, 0)
+
+	fmt.Println("# XIU2/CloudflareSpeedTest " + version + "\n")
+	if ipv6Mode {
+		fmt.Println("开始延迟测速(模式:TCP IPv6,端口:" + strconv.Itoa(tcpPort) + "):")
+	} else {
+		fmt.Println("开始延迟测速(模式:TCP IPv4,端口:" + strconv.Itoa(tcpPort) + "):")
+	}
+	control := make(chan bool, pingRoutine)
+	for _, ip := range ips {
+		wg.Add(1)
+		control <- false
+		handleProgress := handleProgressGenerator(bar) // 多线程进度条
+		go tcpingGoroutine(&wg, &mu, ip, tcpPort, pingTime, &data, control, handleProgress)
+	}
+	wg.Wait()
+	bar.Finish()
+
+	sort.Sort(CloudflareIPDataSet(data)) // 排序
+
+	// 下载测速
+	if !disableDownload { // 如果禁用下载测速就跳过
+		if len(data) > 0 { // IP数组长度(IP数量) 大于 0 时继续
+			if len(data) < downloadTestCount { // 如果IP数组长度(IP数量) 小于 下载测速次数,则次数改为IP数
+				//fmt.Println("\n[信息] IP 数量小于下载测速次数(" + strconv.Itoa(downloadTestCount) + " < " + strconv.Itoa(len(data)) + "),下载测速次数改为IP数。\n")
+				downloadTestCount = len(data)
+			}
+			var downloadTestCount_2 int // 临时的下载测速次数
+			if timeLimit == 9999 && speedLimit == 0 {
+				downloadTestCount_2 = downloadTestCount // 如果没有指定条件,则临时的下载次数变量为下载测速次数
+				fmt.Println("开始下载测速:")
+			} else if timeLimit > 0 || speedLimit >= 0 {
+				downloadTestCount_2 = len(data) // 如果指定了任意一个条件,则临时的下载次数变量改为总数量
+				fmt.Println("开始下载测速(延迟时间上限:" + strconv.Itoa(timeLimit) + " ms,下载速度下限:" + strconv.Itoa(speedLimit) + " MB/s):")
+			}
+			bar = pb.Simple.Start(downloadTestCount_2)
+			for i := 0; i < downloadTestCount_2; i++ {
+				_, speed := DownloadSpeedHandler(data[i].ip)
+				data[i].downloadSpeed = speed
+				bar.Add(1)
+				if int(data[i].pingTime) <= timeLimit && int(float64(speed)/1024/1024) >= speedLimit {
+					data_2 = append(data_2, data[i])      // 延迟和速度均满足条件时,添加到新数组中
+					if len(data_2) == downloadTestCount { // 满足条件的 IP =下载测速次数,则跳出循环
+						break
+					}
+				} else if int(data[i].pingTime) > timeLimit {
+					break
+				}
+			}
+			bar.Finish()
+		} else {
+			fmt.Println("\n[信息] IP数量为 0,跳过下载测速。")
+		}
+	}
+
+	if len(data_2) > 0 { // 如果该数字有内容,说明进行过指定条件的下载测速
+		if outputFile != "" {
+			ExportCsv(outputFile, data_2) // 输出结果到文件(指定延迟时间或下载速度的)
+		}
+		printResult(data_2) // 显示最快结果(指定延迟时间或下载速度的)
+	} else {
+		if outputFile != "" {
+			ExportCsv(outputFile, data) // 输出结果到文件
+		}
+		printResult(data) // 显示最快结果
+	}
+}
+
+// 显示最快结果
+func printResult(data []CloudflareIPData) {
+	sysType := runtime.GOOS
+	if printResultNum > 0 { // 如果禁止直接输出结果就跳过
+		dateString := convertToString(data) // 转为多维数组 [][]String
+		if len(dateString) > 0 {            // IP数组长度(IP数量) 大于 0 时继续
+			if len(dateString) < printResultNum { // 如果IP数组长度(IP数量) 小于  打印次数,则次数改为IP数量
+				//fmt.Println("\n[信息] IP 数量小于显示结果数量(" + strconv.Itoa(printResultNum) + " < " + strconv.Itoa(len(dateString)) + "),显示结果数量改为IP数量。\n")
+				printResultNum = len(dateString)
+			}
+			if ipv6Mode { // IPv6 太长了,所以需要调整一下间隔
+				fmt.Printf("%-40s%-5s%-5s%-5s%-6s%-11s\n", "IP 地址", "已发送", "已接收", "丢包率", "平均延迟", "下载速度 (MB/s)")
+				for i := 0; i < printResultNum; i++ {
+					fmt.Printf("%-42s%-8s%-8s%-8s%-10s%-15s\n", ipPadding(dateString[i][0]), dateString[i][1], dateString[i][2], dateString[i][3], dateString[i][4], dateString[i][5])
+				}
+			} else {
+				fmt.Printf("%-16s%-5s%-5s%-5s%-6s%-11s\n", "IP 地址", "已发送", "已接收", "丢包率", "平均延迟", "下载速度 (MB/s)")
+				for i := 0; i < printResultNum; i++ {
+					fmt.Printf("%-18s%-8s%-8s%-8s%-10s%-15s\n", ipPadding(dateString[i][0]), dateString[i][1], dateString[i][2], dateString[i][3], dateString[i][4], dateString[i][5])
+				}
+			}
+
+			if versionNew != "" {
+				fmt.Println("\n发现新版本 [" + versionNew + "]!请前往 [https://github.com/XIU2/CloudflareSpeedTest] 更新!")
+			}
+
+			if sysType == "windows" { // 如果是 Windows 系统,则需要按下 回车键 或 Ctrl+C 退出
+				if outputFile != "" {
+					fmt.Printf("\n完整测速结果已写入 %v 文件,请使用记事本/表格软件查看。\n按下 回车键 或 Ctrl+C 退出。", outputFile)
+				} else {
+					fmt.Printf("\n按下 回车键 或 Ctrl+C 退出。")
+				}
+				var pause int
+				fmt.Scanln(&pause)
+			} else { // 其它系统直接退出
+				if outputFile != "" {
+					fmt.Println("\n完整测速结果已写入 " + outputFile + " 文件,请使用记事本/表格软件查看。")
+				}
+			}
+		} else {
+			fmt.Println("\n[信息] IP数量为 0,跳过输出结果。")
+		}
+	} else {
+		fmt.Println("\n完整测速结果已写入 " + outputFile + " 文件,请使用记事本/表格软件查看。")
+	}
+}
+
+// 检查更新
+func checkUpdate() {
+	timeout := time.Duration(10 * time.Second)
+	client := http.Client{Timeout: timeout}
+	res, err := client.Get("https://api.xiuer.pw/ver/cloudflarespeedtest.txt")
+	if err == nil {
+		// 读取资源数据 body: []byte
+		body, err := ioutil.ReadAll(res.Body)
+		// 关闭资源流
+		res.Body.Close()
+		if err == nil {
+			if string(body) != version {
+				versionNew = string(body)
+			}
+		}
+	}
+}

+ 173 - 172
tcping.go

@@ -1,172 +1,173 @@
-package main
-
-import (
-	"context"
-	"io"
-	"net"
-	"net/http"
-	"strconv"
-	"sync"
-	"time"
-
-	"github.com/VividCortex/ewma"
-)
-
-//bool connectionSucceed float32 time
-func tcping(ip net.IPAddr, tcpPort int) (bool, float32) {
-	startTime := time.Now()
-	var fullAddress string
-	if ipv6Mode { // IPv6 需要加上 []
-		fullAddress = "[" + ip.String() + "]:" + strconv.Itoa(tcpPort)
-	} else {
-		fullAddress = ip.String() + ":" + strconv.Itoa(tcpPort)
-	}
-	conn, err := net.DialTimeout("tcp", fullAddress, tcpConnectTimeout)
-	if err != nil {
-		return false, 0
-	} else {
-		var endTime = time.Since(startTime)
-		var duration = float32(endTime.Microseconds()) / 1000.0
-		_ = conn.Close()
-		return true, duration
-	}
-}
-
-//pingReceived pingTotalTime
-func checkConnection(ip net.IPAddr, tcpPort int) (int, float32) {
-	pingRecv := 0
-	var pingTime float32 = 0.0
-	for i := 1; i <= failTime; i++ {
-		pingSucceed, pingTimeCurrent := tcping(ip, tcpPort)
-		if pingSucceed {
-			pingRecv++
-			pingTime += pingTimeCurrent
-		}
-	}
-	return pingRecv, pingTime
-}
-
-//return Success packetRecv averagePingTime specificIPAddr
-func tcpingHandler(ip net.IPAddr, tcpPort int, pingCount int, progressHandler func(e progressEvent)) (bool, int, float32, net.IPAddr) {
-	ipCanConnect := false
-	pingRecv := 0
-	var pingTime float32 = 0.0
-	for !ipCanConnect {
-		pingRecvCurrent, pingTimeCurrent := checkConnection(ip, tcpPort)
-		if pingRecvCurrent != 0 {
-			ipCanConnect = true
-			pingRecv = pingRecvCurrent
-			pingTime = pingTimeCurrent
-		} else {
-			ip.IP[15]++
-			if ip.IP[15] == 0 {
-				break
-			}
-			break
-		}
-	}
-	if ipCanConnect {
-		progressHandler(AvailableIPFound)
-		for i := failTime; i < pingCount; i++ {
-			pingSuccess, pingTimeCurrent := tcping(ip, tcpPort)
-			progressHandler(NormalPing)
-			if pingSuccess {
-				pingRecv++
-				pingTime += pingTimeCurrent
-			}
-		}
-		return true, pingRecv, pingTime / float32(pingRecv), ip
-	} else {
-		progressHandler(NoAvailableIPFound)
-		return false, 0, 0, net.IPAddr{}
-	}
-}
-
-func tcpingGoroutine(wg *sync.WaitGroup, mutex *sync.Mutex, ip net.IPAddr, tcpPort int, pingCount int, csv *[]CloudflareIPData, control chan bool, progressHandler func(e progressEvent)) {
-	defer wg.Done()
-	success, pingRecv, pingTimeAvg, currentIP := tcpingHandler(ip, tcpPort, pingCount, progressHandler)
-	if success {
-		mutex.Lock()
-		var cfdata CloudflareIPData
-		cfdata.ip = currentIP
-		cfdata.pingReceived = pingRecv
-		cfdata.pingTime = pingTimeAvg
-		cfdata.pingCount = pingCount
-		*csv = append(*csv, cfdata)
-		mutex.Unlock()
-	}
-	<-control
-}
-
-func GetDialContextByAddr(fakeSourceAddr string) func(ctx context.Context, network, address string) (net.Conn, error) {
-	return func(ctx context.Context, network, address string) (net.Conn, error) {
-		c, e := (&net.Dialer{}).DialContext(ctx, network, fakeSourceAddr)
-		return c, e
-	}
-}
-
-//bool : can download,float32 downloadSpeed
-func DownloadSpeedHandler(ip net.IPAddr) (bool, float32) {
-	var client = http.Client{
-		Transport:     nil,
-		CheckRedirect: nil,
-		Jar:           nil,
-		Timeout:       0,
-	}
-	var fullAddress string
-	if ipv6Mode { // IPv6 需要加上 []
-		fullAddress = "[" + ip.String() + "]:443"
-	} else {
-		fullAddress = ip.String() + ":443"
-	}
-	client.Transport = &http.Transport{
-		DialContext: GetDialContextByAddr(fullAddress),
-	}
-	response, err := client.Get(url)
-
-	if err != nil {
-		return false, 0
-	} else {
-		defer func() { _ = response.Body.Close() }()
-		if response.StatusCode == 200 {
-			timeStart := time.Now()
-			timeEnd := timeStart.Add(downloadTestTime)
-
-			contentLength := response.ContentLength
-			buffer := make([]byte, downloadBufferSize)
-
-			var contentRead int64 = 0
-			var timeSlice = downloadTestTime / 100
-			var timeCounter = 1
-			var lastContentRead int64 = 0
-
-			var nextTime = timeStart.Add(timeSlice * time.Duration(timeCounter))
-			e := ewma.NewMovingAverage()
-
-			for contentLength != contentRead {
-				var currentTime = time.Now()
-				if currentTime.After(nextTime) {
-					timeCounter += 1
-					nextTime = timeStart.Add(timeSlice * time.Duration(timeCounter))
-					e.Add(float64(contentRead - lastContentRead))
-					lastContentRead = contentRead
-				}
-				if currentTime.After(timeEnd) {
-					break
-				}
-				bufferRead, err := response.Body.Read(buffer)
-				contentRead += int64(bufferRead)
-				if err != nil {
-					if err != io.EOF {
-						break
-					} else {
-						e.Add(float64(contentRead-lastContentRead) / (float64(nextTime.Sub(currentTime)) / float64(timeSlice)))
-					}
-				}
-			}
-			return true, float32(e.Value()) / (float32(downloadTestTime.Seconds()) / 100)
-		} else {
-			return false, 0
-		}
-	}
-}
+package main
+
+import (
+	"context"
+	"io"
+	"net"
+	"net/http"
+	"strconv"
+	"sync"
+	"time"
+
+	"github.com/VividCortex/ewma"
+)
+
+//bool connectionSucceed float32 time
+func tcping(ip net.IPAddr, tcpPort int) (bool, float32) {
+	startTime := time.Now()
+	var fullAddress string
+	//fmt.Println(ip.String())
+	if ipv6Mode { // IPv6 需要加上 []
+		fullAddress = "[" + ip.String() + "]:" + strconv.Itoa(tcpPort)
+	} else {
+		fullAddress = ip.String() + ":" + strconv.Itoa(tcpPort)
+	}
+	conn, err := net.DialTimeout("tcp", fullAddress, tcpConnectTimeout)
+	if err != nil {
+		return false, 0
+	} else {
+		var endTime = time.Since(startTime)
+		var duration = float32(endTime.Microseconds()) / 1000.0
+		_ = conn.Close()
+		return true, duration
+	}
+}
+
+//pingReceived pingTotalTime
+func checkConnection(ip net.IPAddr, tcpPort int) (int, float32) {
+	pingRecv := 0
+	var pingTime float32 = 0.0
+	for i := 1; i <= failTime; i++ {
+		pingSucceed, pingTimeCurrent := tcping(ip, tcpPort)
+		if pingSucceed {
+			pingRecv++
+			pingTime += pingTimeCurrent
+		}
+	}
+	return pingRecv, pingTime
+}
+
+//return Success packetRecv averagePingTime specificIPAddr
+func tcpingHandler(ip net.IPAddr, tcpPort int, pingCount int, progressHandler func(e progressEvent)) (bool, int, float32, net.IPAddr) {
+	ipCanConnect := false
+	pingRecv := 0
+	var pingTime float32 = 0.0
+	for !ipCanConnect {
+		pingRecvCurrent, pingTimeCurrent := checkConnection(ip, tcpPort)
+		if pingRecvCurrent != 0 {
+			ipCanConnect = true
+			pingRecv = pingRecvCurrent
+			pingTime = pingTimeCurrent
+		} else {
+			ip.IP[15]++
+			if ip.IP[15] == 0 {
+				break
+			}
+			break
+		}
+	}
+	if ipCanConnect {
+		progressHandler(AvailableIPFound)
+		for i := failTime; i < pingCount; i++ {
+			pingSuccess, pingTimeCurrent := tcping(ip, tcpPort)
+			progressHandler(NormalPing)
+			if pingSuccess {
+				pingRecv++
+				pingTime += pingTimeCurrent
+			}
+		}
+		return true, pingRecv, pingTime / float32(pingRecv), ip
+	} else {
+		progressHandler(NoAvailableIPFound)
+		return false, 0, 0, net.IPAddr{}
+	}
+}
+
+func tcpingGoroutine(wg *sync.WaitGroup, mutex *sync.Mutex, ip net.IPAddr, tcpPort int, pingCount int, csv *[]CloudflareIPData, control chan bool, progressHandler func(e progressEvent)) {
+	defer wg.Done()
+	success, pingRecv, pingTimeAvg, currentIP := tcpingHandler(ip, tcpPort, pingCount, progressHandler)
+	if success {
+		mutex.Lock()
+		var cfdata CloudflareIPData
+		cfdata.ip = currentIP
+		cfdata.pingReceived = pingRecv
+		cfdata.pingTime = pingTimeAvg
+		cfdata.pingCount = pingCount
+		*csv = append(*csv, cfdata)
+		mutex.Unlock()
+	}
+	<-control
+}
+
+func GetDialContextByAddr(fakeSourceAddr string) func(ctx context.Context, network, address string) (net.Conn, error) {
+	return func(ctx context.Context, network, address string) (net.Conn, error) {
+		c, e := (&net.Dialer{}).DialContext(ctx, network, fakeSourceAddr)
+		return c, e
+	}
+}
+
+//bool : can download,float32 downloadSpeed
+func DownloadSpeedHandler(ip net.IPAddr) (bool, float32) {
+	var client = http.Client{
+		Transport:     nil,
+		CheckRedirect: nil,
+		Jar:           nil,
+		Timeout:       0,
+	}
+	var fullAddress string
+	if ipv6Mode { // IPv6 需要加上 []
+		fullAddress = "[" + ip.String() + "]:443"
+	} else {
+		fullAddress = ip.String() + ":443"
+	}
+	client.Transport = &http.Transport{
+		DialContext: GetDialContextByAddr(fullAddress),
+	}
+	response, err := client.Get(url)
+
+	if err != nil {
+		return false, 0
+	} else {
+		defer func() { _ = response.Body.Close() }()
+		if response.StatusCode == 200 {
+			timeStart := time.Now()
+			timeEnd := timeStart.Add(downloadTestTime)
+
+			contentLength := response.ContentLength
+			buffer := make([]byte, downloadBufferSize)
+
+			var contentRead int64 = 0
+			var timeSlice = downloadTestTime / 100
+			var timeCounter = 1
+			var lastContentRead int64 = 0
+
+			var nextTime = timeStart.Add(timeSlice * time.Duration(timeCounter))
+			e := ewma.NewMovingAverage()
+
+			for contentLength != contentRead {
+				var currentTime = time.Now()
+				if currentTime.After(nextTime) {
+					timeCounter += 1
+					nextTime = timeStart.Add(timeSlice * time.Duration(timeCounter))
+					e.Add(float64(contentRead - lastContentRead))
+					lastContentRead = contentRead
+				}
+				if currentTime.After(timeEnd) {
+					break
+				}
+				bufferRead, err := response.Body.Read(buffer)
+				contentRead += int64(bufferRead)
+				if err != nil {
+					if err != io.EOF {
+						break
+					} else {
+						e.Add(float64(contentRead-lastContentRead) / (float64(nextTime.Sub(currentTime)) / float64(timeSlice)))
+					}
+				}
+			}
+			return true, float32(e.Value()) / (float32(downloadTestTime.Seconds()) / 100)
+		} else {
+			return false, 0
+		}
+	}
+}

+ 148 - 148
util.go

@@ -1,148 +1,148 @@
-package main
-
-import (
-	"encoding/csv"
-	"log"
-	"math/rand"
-	"net"
-	"os"
-	"strconv"
-	"time"
-
-	"github.com/cheggaaa/pb/v3"
-)
-
-type CloudflareIPData struct {
-	ip            net.IPAddr
-	pingCount     int
-	pingReceived  int
-	recvRate      float32
-	downloadSpeed float32
-	pingTime      float32
-}
-
-func (cf *CloudflareIPData) getRecvRate() float32 {
-	if cf.recvRate == 0 {
-		pingLost := cf.pingCount - cf.pingReceived
-		cf.recvRate = float32(pingLost) / float32(cf.pingCount)
-	}
-	return cf.recvRate
-}
-
-func ExportCsv(filePath string, data []CloudflareIPData) {
-	fp, err := os.Create(filePath)
-	if err != nil {
-		log.Fatalf("创建文件["+filePath+"]句柄失败,%v", err)
-		return
-	}
-	defer fp.Close()
-	w := csv.NewWriter(fp) //创建一个新的写入文件流
-	w.Write([]string{"IP 地址", "已发送", "已接收", "丢包率", "平均延迟", "下载速度 (MB/s)"})
-	w.WriteAll(convertToString(data))
-	w.Flush()
-}
-
-func (cf *CloudflareIPData) toString() []string {
-	result := make([]string, 6)
-	result[0] = cf.ip.String()
-	result[1] = strconv.Itoa(cf.pingCount)
-	result[2] = strconv.Itoa(cf.pingReceived)
-	result[3] = strconv.FormatFloat(float64(cf.getRecvRate()), 'f', 2, 32)
-	result[4] = strconv.FormatFloat(float64(cf.pingTime), 'f', 2, 32)
-	result[5] = strconv.FormatFloat(float64(cf.downloadSpeed)/1024/1024, 'f', 2, 32)
-	return result
-}
-
-func convertToString(data []CloudflareIPData) [][]string {
-	result := make([][]string, 0)
-	for _, v := range data {
-		result = append(result, v.toString())
-	}
-	return result
-}
-
-var pingTime int
-var pingRoutine int
-
-type progressEvent int
-
-const (
-	NoAvailableIPFound progressEvent = iota
-	AvailableIPFound
-	NormalPing
-)
-
-var url string
-
-var downloadTestTime time.Duration
-
-const downloadBufferSize = 1024
-
-var downloadTestCount int
-
-//const defaultTcpPort = 443
-const tcpConnectTimeout = time.Second * 1
-
-var failTime int
-
-type CloudflareIPDataSet []CloudflareIPData
-
-func initRandSeed() {
-	rand.Seed(time.Now().UnixNano())
-}
-
-func randipEndWith() uint8 {
-	return uint8(rand.Intn(254) + 1)
-}
-
-func GetRandomString() string {
-	str := "0123456789abcdef"
-	bytes := []byte(str)
-	result := []byte{}
-	r := rand.New(rand.NewSource(time.Now().UnixNano()))
-	for i := 0; i < 4; i++ {
-		result = append(result, bytes[r.Intn(len(bytes))])
-	}
-	return string(result)
-}
-
-func ipPadding(ip string) string {
-	var ipLength int
-	var ipPrint string
-	ipPrint = ip
-	ipLength = len(ipPrint)
-	if ipLength < 15 {
-		for i := 0; i <= 15-ipLength; i++ {
-			ipPrint += " "
-		}
-	}
-	return ipPrint
-}
-
-func handleProgressGenerator(pb *pb.ProgressBar) func(e progressEvent) {
-	return func(e progressEvent) {
-		switch e {
-		case NoAvailableIPFound:
-			pb.Add(pingTime)
-		case AvailableIPFound:
-			pb.Add(failTime)
-		case NormalPing:
-			pb.Increment()
-		}
-	}
-}
-
-func (cfs CloudflareIPDataSet) Len() int {
-	return len(cfs)
-}
-
-func (cfs CloudflareIPDataSet) Less(i, j int) bool {
-	if (cfs)[i].getRecvRate() != cfs[j].getRecvRate() {
-		return cfs[i].getRecvRate() < cfs[j].getRecvRate()
-	}
-	return cfs[i].pingTime < cfs[j].pingTime
-}
-
-func (cfs CloudflareIPDataSet) Swap(i, j int) {
-	cfs[i], cfs[j] = cfs[j], cfs[i]
-}
+package main
+
+import (
+	"encoding/csv"
+	"log"
+	"math/rand"
+	"net"
+	"os"
+	"strconv"
+	"time"
+
+	"github.com/cheggaaa/pb/v3"
+)
+
+type CloudflareIPData struct {
+	ip            net.IPAddr
+	pingCount     int
+	pingReceived  int
+	recvRate      float32
+	downloadSpeed float32
+	pingTime      float32
+}
+
+func (cf *CloudflareIPData) getRecvRate() float32 {
+	if cf.recvRate == 0 {
+		pingLost := cf.pingCount - cf.pingReceived
+		cf.recvRate = float32(pingLost) / float32(cf.pingCount)
+	}
+	return cf.recvRate
+}
+
+func ExportCsv(filePath string, data []CloudflareIPData) {
+	fp, err := os.Create(filePath)
+	if err != nil {
+		log.Fatalf("创建文件["+filePath+"]句柄失败,%v", err)
+		return
+	}
+	defer fp.Close()
+	w := csv.NewWriter(fp) //创建一个新的写入文件流
+	w.Write([]string{"IP 地址", "已发送", "已接收", "丢包率", "平均延迟", "下载速度 (MB/s)"})
+	w.WriteAll(convertToString(data))
+	w.Flush()
+}
+
+func (cf *CloudflareIPData) toString() []string {
+	result := make([]string, 6)
+	result[0] = cf.ip.String()
+	result[1] = strconv.Itoa(cf.pingCount)
+	result[2] = strconv.Itoa(cf.pingReceived)
+	result[3] = strconv.FormatFloat(float64(cf.getRecvRate()), 'f', 2, 32)
+	result[4] = strconv.FormatFloat(float64(cf.pingTime), 'f', 2, 32)
+	result[5] = strconv.FormatFloat(float64(cf.downloadSpeed)/1024/1024, 'f', 2, 32)
+	return result
+}
+
+func convertToString(data []CloudflareIPData) [][]string {
+	result := make([][]string, 0)
+	for _, v := range data {
+		result = append(result, v.toString())
+	}
+	return result
+}
+
+var pingTime int
+var pingRoutine int
+
+type progressEvent int
+
+const (
+	NoAvailableIPFound progressEvent = iota
+	AvailableIPFound
+	NormalPing
+)
+
+var url string
+
+var downloadTestTime time.Duration
+
+const downloadBufferSize = 1024
+
+var downloadTestCount int
+
+//const defaultTcpPort = 443
+const tcpConnectTimeout = time.Second * 1
+
+var failTime int
+
+type CloudflareIPDataSet []CloudflareIPData
+
+func initRandSeed() {
+	rand.Seed(time.Now().UnixNano())
+}
+
+func randipEndWith(num int) uint8 {
+	return uint8(rand.Intn(num) + 1)
+}
+
+func GetRandomString() string {
+	str := "0123456789abcdef"
+	bytes := []byte(str)
+	result := []byte{}
+	r := rand.New(rand.NewSource(time.Now().UnixNano()))
+	for i := 0; i < 4; i++ {
+		result = append(result, bytes[r.Intn(len(bytes))])
+	}
+	return string(result)
+}
+
+func ipPadding(ip string) string {
+	var ipLength int
+	var ipPrint string
+	ipPrint = ip
+	ipLength = len(ipPrint)
+	if ipLength < 15 {
+		for i := 0; i <= 15-ipLength; i++ {
+			ipPrint += " "
+		}
+	}
+	return ipPrint
+}
+
+func handleProgressGenerator(pb *pb.ProgressBar) func(e progressEvent) {
+	return func(e progressEvent) {
+		switch e {
+		case NoAvailableIPFound:
+			pb.Add(pingTime)
+		case AvailableIPFound:
+			pb.Add(failTime)
+		case NormalPing:
+			pb.Increment()
+		}
+	}
+}
+
+func (cfs CloudflareIPDataSet) Len() int {
+	return len(cfs)
+}
+
+func (cfs CloudflareIPDataSet) Less(i, j int) bool {
+	if (cfs)[i].getRecvRate() != cfs[j].getRecvRate() {
+		return cfs[i].getRecvRate() < cfs[j].getRecvRate()
+	}
+	return cfs[i].pingTime < cfs[j].pingTime
+}
+
+func (cfs CloudflareIPDataSet) Swap(i, j int) {
+	cfs[i], cfs[j] = cfs[j], cfs[i]
+}