From 0d54b65f33ef5c6bf3a92d70a67ba7e6be0cb6a4 Mon Sep 17 00:00:00 2001 From: xiu2 <54703944+XIU2@users.noreply.github.com> Date: Sat, 5 Dec 2020 16:03:21 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E6=B5=8B=E9=80=9F?= =?UTF-8?q?=E5=85=A8=E9=83=A8=20IP=E3=80=81=E6=A3=80=E6=9F=A5=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IPRangeLoader.go | 246 ++++++++++++---------- README.md | 324 ++++++++++++++--------------- main.go | 515 +++++++++++++++++++++++++---------------------- tcping.go | 345 +++++++++++++++---------------- util.go | 296 +++++++++++++-------------- 5 files changed, 896 insertions(+), 830 deletions(-) diff --git a/IPRangeLoader.go b/IPRangeLoader.go index 8d8e1ea..eb77780 100644 --- a/IPRangeLoader.go +++ b/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 +} diff --git a/README.md b/README.md index 2284d2e..ae774e7 100644 --- a/README.md +++ b/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. diff --git a/main.go b/main.go index f961406..9adb0af 100644 --- a/main.go +++ b/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) + } + } + } +} diff --git a/tcping.go b/tcping.go index a4b8b7b..fba5808 100644 --- a/tcping.go +++ b/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 + } + } +} diff --git a/util.go b/util.go index e1b0999..142f1d8 100644 --- a/util.go +++ b/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] +}