From f1a9b5c9661518879245572423e08f73053559cd Mon Sep 17 00:00:00 2001 From: mazhuang Date: Wed, 10 Nov 2021 17:12:12 +0800 Subject: [PATCH] rebuild download --- IPRangeLoader.go | 7 +- main.go | 252 +++++++++++++--------------------------------- task/download.go | 149 +++++++++++++++++++++++++++ task/ip.go | 4 + task/tcping.go | 32 ++++-- tcping.go | 167 ------------------------------ utils/csv.go | 43 ++++++-- utils/progress.go | 23 +---- 8 files changed, 287 insertions(+), 390 deletions(-) create mode 100644 task/download.go delete mode 100644 tcping.go diff --git a/IPRangeLoader.go b/IPRangeLoader.go index 2dc00aa..b144263 100644 --- a/IPRangeLoader.go +++ b/IPRangeLoader.go @@ -1,6 +1,7 @@ package main import ( + "CloudflareSpeedTest/task" "bufio" "log" "net" @@ -65,7 +66,7 @@ func loadFirstIPOfRangeFromFile(ipFile string) []net.IPAddr { for scanner.Scan() { IPString := scanner.Text() if !strings.Contains(IPString, "/") { // 如果不含有 / 则代表不是 IP 段,而是一个单独的 IP,因此需要加上 /32 /128 子网掩码 - if ipv6Mode { + if task.IPv6 { IPString += "/128" } else { IPString += "/32" @@ -77,12 +78,12 @@ func loadFirstIPOfRangeFromFile(ipFile string) []net.IPAddr { if err != nil { log.Fatal(err) } - if !ipv6Mode { // IPv4 + if !task.IPv6 { // IPv4 minIP, maxIP := getCidrIPRange(IPString) // 获取 IP 最后一段最小值和最大值 Mask, _ := strconv.Atoi(strings.Split(IPString, "/")[1]) // 获取子网掩码 MaxIPNum := getCidrHostNum(Mask) // 根据子网掩码获取主机数量 for IPRange.Contains(firstIP) { - if allip { // 如果是测速全部 IP + if task.TestAll { // 如果是测速全部 IP for i := minIP; i <= maxIP; i++ { // 遍历 IP 最后一段最小值到最大值 firstIP[15] = i firstIPCopy := make([]byte, len(firstIP)) diff --git a/main.go b/main.go index 3b3a621..fe82167 100644 --- a/main.go +++ b/main.go @@ -7,20 +7,14 @@ import ( "net/http" "os" "runtime" - "sort" - "sync" "time" "CloudflareSpeedTest/task" - - "github.com/cheggaaa/pb/v3" + "CloudflareSpeedTest/utils" ) var ( - version, ipFile, outputFile, versionNew string - disableDownload, ipv6Mode, allip bool - tcpPort, printResultNum, downloadSecond int - timeLimit, timeLimitLow, speedLimit float64 + version, versionNew string ) func init() { @@ -67,27 +61,23 @@ https://github.com/XIU2/CloudflareSpeedTest 打印帮助说明 ` - flag.IntVar(&pingRoutine, "n", 200, "测速线程数量") - flag.IntVar(&pingTime, "t", 4, "延迟测速次数") - flag.IntVar(&tcpPort, "tp", 443, "延迟测速端口") - flag.IntVar(&downloadTestCount, "dn", 20, "下载测速数量") - flag.IntVar(&downloadSecond, "dt", 10, "下载测速时间") - flag.StringVar(&url, "url", "https://cf.xiu2.xyz/Github/CloudflareSpeedTest.png", "下载测速地址") - flag.Float64Var(&timeLimit, "tl", 9999, "平均延迟上限") - flag.Float64Var(&timeLimitLow, "tll", 0, "平均延迟下限") - flag.Float64Var(&speedLimit, "sl", 0, "下载速度下限") - flag.IntVar(&printResultNum, "p", 20, "显示结果数量") - flag.BoolVar(&disableDownload, "dd", false, "禁用下载测速") - flag.BoolVar(&ipv6Mode, "ipv6", false, "启用IPv6") - flag.BoolVar(&allip, "allip", false, "测速全部 IP") - flag.StringVar(&ipFile, "f", "ip.txt", "IP 数据文件") - flag.StringVar(&outputFile, "o", "result.csv", "输出结果文件") + flag.IntVar(&task.Routines, "n", 200, "测速线程数量") + flag.IntVar(&task.PingTimes, "t", 4, "延迟测速次数") + flag.IntVar(&task.TCPPort, "tp", 443, "延迟测速端口") + flag.DurationVar(&utils.InputMaxDelay, "tl", 9999*time.Millisecond, "平均延迟上限") + flag.DurationVar(&utils.InputMinDelay, "tll", time.Duration(0), "平均延迟下限") + flag.DurationVar(&task.Timeout, "dt", 10*time.Second, "下载测速时间") + flag.IntVar(&task.TestCount, "dn", 20, "下载测速数量") + flag.StringVar(&task.URL, "url", "https://cf.xiu2.xyz/Github/CloudflareSpeedTest.png", "下载测速地址") + flag.BoolVar(&task.Disable, "dd", false, "禁用下载测速") + flag.BoolVar(&task.IPv6, "ipv6", false, "启用IPv6") + flag.BoolVar(&task.TestAll, "allip", false, "测速全部 IP") + flag.StringVar(&task.IPFile, "f", "ip.txt", "IP 数据文件") + flag.Float64Var(&task.MinSpeed, "sl", 0, "下载速度下限") + flag.IntVar(&utils.PrintNum, "p", 20, "显示结果数量") + flag.StringVar(&utils.Output, "o", "result.csv", "输出结果文件") flag.BoolVar(&printVersion, "v", false, "打印程序版本") - task.TCPPort = tcpPort - task.IPv6 = ipv6Mode - task.DefaultRoutine = pingRoutine - flag.Usage = func() { fmt.Print(help) } flag.Parse() if printVersion { @@ -101,184 +91,80 @@ https://github.com/XIU2/CloudflareSpeedTest } os.Exit(0) } - if pingRoutine <= 0 { - pingRoutine = 200 - } - 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 { - timeLimit = 9999 - } - if timeLimitLow < 0 || timeLimitLow > 9999 { - timeLimitLow = 0 - } - 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 - mu sync.Mutex - data = make([]CloudflareIPData, 0) - data2 = make([]CloudflareIPData, 0) - ) - downloadTestTime = time.Duration(downloadSecond) * time.Second + ips := loadFirstIPOfRangeFromFile(task.IPFile) // 读入IP // 开始延迟测速 fmt.Printf("# XIU2/CloudflareSpeedTest %s \n", version) ipVersion := "IPv4" - if ipv6Mode { // IPv6 模式判断 + if task.IPv6 { // IPv6 模式判断 ipVersion = "IPv6" } - fmt.Printf("开始延迟测速(模式:TCP %s,端口:%d ,平均延迟上限:%.2f ms,平均延迟下限:%.2f ms):\n", ipVersion, tcpPort, timeLimit, timeLimitLow) + fmt.Printf("开始延迟测速(模式:TCP %s,端口:%d ,平均延迟上限:%v,平均延迟下限:%v)\n", ipVersion, task.TCPPort, utils.InputMaxDelay, utils.InputMinDelay) - // ping := task.NewPing(ips) - // ping.Run() - 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() - // data := ping.Data() - // sort.Sort(utils.CloudflareIPDataSet(data)) - sort.Sort(CloudflareIPDataSet(data)) // 排序(按延迟,从低到高,不同丢包率会分开单独按延迟和丢包率排序) - - // 延迟测速完毕后,以 [平均延迟上限] + [平均延迟下限] 条件过滤结果 - if timeLimit != 9999 || timeLimitLow != 0 { - for i := 0; i < len(data); i++ { - if float64(data[i].pingTime) > timeLimit { // 平均延迟上限 - break - } - if float64(data[i].pingTime) <= timeLimitLow { // 平均延迟下限 - continue - } - data2 = append(data2, data[i]) // 延迟满足条件时,添加到新数组中 - } - data = data2 - data2 = []CloudflareIPData{} - } - - // 开始下载测速 - if !disableDownload { // 如果禁用下载测速就跳过 - testDownloadSpeed(data, data2, bar) - } - - if len(data2) > 0 { // 如果该数组有内容,说明指定了 [下载测速下限] 条件,且最少有 1 个满足条件的 IP - data = data2 - } - sort.Sort(CloudflareIPDataSetD(data)) // 排序(按下载速度,从高到低) - if outputFile != "" { - ExportCsv(outputFile, data) // 输出结果到文件 - } - printResult(data) // 显示最快结果 -} - -func testDownloadSpeed(data, data2 []CloudflareIPData, bar *pb.ProgressBar) { - if len(data) <= 0 { // IP数组长度(IP数量) 大于 0 时才会继续下载测速 - fmt.Println("\n[信息] 延迟测速结果 IP 数量为 0,跳过下载测速。") - return - } - if len(data) < downloadTestCount { // 如果IP数组长度(IP数量) 小于下载测速数量(-dn),则次数修正为IP数 - downloadTestCount = len(data) - } - // 临时的下载测速次数,即实际的下载测速数量 - downloadTestCount2 := downloadTestCount - // 如果没有指定 [下载速度下限] 条件,则临时变量为下载测速数量(-dn) - if speedLimit > 0 { - // 如果指定了 [下载速度下限] 条件,则临时变量改为总数量(即一直测速下去,直到凑够下载测速数量 -dn) - downloadTestCount2 = len(data) - } - fmt.Printf("开始下载测速(下载速度下限:%.2f MB/s,下载测速数量:%d,下载测速队列:%d):\n", speedLimit, downloadTestCount, downloadTestCount2) - bar = pb.Simple.Start(downloadTestCount) - for i := 0; i < downloadTestCount2; i++ { - _, speed := DownloadSpeedHandler(data[i].ip) - data[i].downloadSpeed = speed - // 在每个 IP 下载测速后,以 [下载速度下限] 条件过滤结果 - if float64(speed) >= speedLimit*1024*1024 { - data2 = append(data2, data[i]) // 高于下载速度下限时,添加到新数组中 - bar.Add(1) - if len(data2) == downloadTestCount { // 凑够满足条件的 IP 时(下载测速数量 -dn),就跳出循环 - break - } - } - } - bar.Finish() -} - -// 显示最快结果 -func printResult(data []CloudflareIPData) { - sysType := runtime.GOOS - if printResultNum <= 0 { // 如果禁止直接输出结果就跳过 - fmt.Printf("完整测速结果已写入 %v 文件,请使用记事本/表格软件查看。\n", outputFile) - return - } - dateString := convertToString(data) // 转为多维数组 [][]String - if len(dateString) <= 0 { // IP数组长度(IP数量) 大于 0 时继续 - fmt.Println("\n[信息] 完整测速结果 IP 数量为 0,跳过输出结果。") - return - } - if len(dateString) < printResultNum { // 如果IP数组长度(IP数量) 小于 打印次数,则次数改为IP数量 - printResultNum = len(dateString) - } - resHeader := []interface{}{"IP 地址", "已发送", "已接收", "丢包率", "平均延迟", "下载速度 (MB/s)"} - if ipv6Mode { // IPv6 太长了,所以需要调整一下间隔 - fmt.Printf("%-40s%-5s%-5s%-5s%-6s%-11s\n", resHeader...) - 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", resHeader...) - for i := 0; i < printResultNum; i++ { - fmt.Printf("%-18s%-8s%-8s%-8s%-15s%-15s\n", ipPadding(dateString[i][0]), dateString[i][1], dateString[i][2], dateString[i][3], dateString[i][4], dateString[i][5]) - } - } + pingData := task.NewPing(ips).Run().FilterDelay() + speedData := task.TestDownloadSpeed(pingData) + utils.ExportCsv(speedData) + speedData.Print(task.IPv6) if versionNew != "" { fmt.Printf("\n*** 发现新版本 [%s]!请前往 [https://github.com/XIU2/CloudflareSpeedTest] 更新! ***\n", versionNew) } - if outputFile != "" { - fmt.Printf("完整测速结果已写入 %v 文件,请使用记事本/表格软件查看。\n", outputFile) + if utils.Output != "" { + fmt.Printf("完整测速结果已写入 %v 文件,请使用记事本/表格软件查看。\n", utils.Output) } - if sysType == "windows" { // 如果是 Windows 系统,则需要按下 回车键 或 Ctrl+C 退出(避免通过双击运行时,测速完毕后直接关闭) + if runtime.GOOS == "windows" { // 如果是 Windows 系统,则需要按下 回车键 或 Ctrl+C 退出(避免通过双击运行时,测速完毕后直接关闭) fmt.Printf("按下 回车键 或 Ctrl+C 退出。\n") var pause int fmt.Scanln(&pause) } + + // 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() + // data := ping.Data() + // sort.Sort(utils.CloudflareIPDataSet(data)) + // sort.Sort(CloudflareIPDataSet(data)) // 排序(按延迟,从低到高,不同丢包率会分开单独按延迟和丢包率排序) + + // 延迟测速完毕后,以 [平均延迟上限] + [平均延迟下限] 条件过滤结果 + // if timeLimit != 9999 || timeLimitLow != 0 { + // for i := 0; i < len(data); i++ { + // if float64(data[i].pingTime) > timeLimit { // 平均延迟上限 + // break + // } + // if float64(data[i].pingTime) <= timeLimitLow { // 平均延迟下限 + // continue + // } + // data2 = append(data2, data[i]) // 延迟满足条件时,添加到新数组中 + // } + // data = data2 + // data2 = []CloudflareIPData{} + // } + + // 开始下载测速 + // if !disableDownload { // 如果禁用下载测速就跳过 + // testDownloadSpeed(data, data2, bar) + // } + + // if len(data2) > 0 { // 如果该数组有内容,说明指定了 [下载测速下限] 条件,且最少有 1 个满足条件的 IP + // data = data2 + // } + // sort.Sort(CloudflareIPDataSetD(data)) // 排序(按下载速度,从高到低) + // if outputFile != "" { + // ExportCsv(outputFile, data) // 输出结果到文件 + // } + // printResult(data) // 显示最快结果 } // 检查更新 diff --git a/task/download.go b/task/download.go new file mode 100644 index 0000000..072c615 --- /dev/null +++ b/task/download.go @@ -0,0 +1,149 @@ +package task + +import ( + "context" + "fmt" + "io" + "net" + "net/http" + "sort" + "time" + + "CloudflareSpeedTest/utils" + + "github.com/VividCortex/ewma" +) + +const ( + bufferSize = 1024 + defaultURL = "https://cf.xiu2.xyz/Github/CloudflareSpeedTest.png" + defaultTimeout = 10 * time.Second + defaultDisableDownlaod = false + defaultTestNum = 20 + defaultMinSpeed float64 = 0.0 +) + +var ( + // download test url + URL = defaultURL + // download timeout + Timeout = defaultTimeout + // disable download + Disable = defaultDisableDownlaod + + TestCount = defaultTestNum + MinSpeed = defaultMinSpeed +) + +func checkDownloadDefault() { + if URL == "" { + URL = defaultURL + } + if Timeout <= 0 { + Timeout = defaultTimeout + } + if TestCount <= 0 { + TestCount = defaultTestNum + } + if MinSpeed <= 0.0 { + MinSpeed = defaultMinSpeed + } +} + +func TestDownloadSpeed(ipSet utils.PingDelaySet) (sppedSet utils.DownloadSpeedSet) { + checkDownloadDefault() + if Disable { + return utils.DownloadSpeedSet(ipSet) + } + if len(ipSet) <= 0 { // IP数组长度(IP数量) 大于 0 时才会继续下载测速 + fmt.Println("\n[信息] 延迟测速结果 IP 数量为 0,跳过下载测速。") + return + } + testNum := TestCount + if len(ipSet) < TestCount || MinSpeed > 0 { // 如果IP数组长度(IP数量) 小于下载测速数量(-dn),则次数修正为IP数 + testNum = len(ipSet) + } + + fmt.Printf("开始下载测速(下载速度下限:%.2f MB/s,下载测速数量:%d,下载测速队列:%d):\n", MinSpeed, TestCount, testNum) + bar := utils.NewBar(TestCount) + for i := 0; i < testNum; i++ { + speed := downloadSpeedHandler(&ipSet[i].IP) + ipSet[i].DownloadSpeed = speed + // 在每个 IP 下载测速后,以 [下载速度下限] 条件过滤结果 + if speed >= MinSpeed*1024*1024 { + sppedSet = append(sppedSet, ipSet[i]) // 高于下载速度下限时,添加到新数组中 + bar.Grow(1) + if len(sppedSet) == TestCount { // 凑够满足条件的 IP 时(下载测速数量 -dn),就跳出循环 + break + } + } + } + bar.Done() + // 按速度排序 + sort.Sort(sppedSet) + return +} + +func getDialContext(ip *net.IPAddr) func(ctx context.Context, network, address string) (net.Conn, error) { + fakeSourceAddr := ip.String() + ":443" + if IPv6 { // IPv6 需要加上 [] + fakeSourceAddr = "[" + ip.String() + "]:443" + } + return func(ctx context.Context, network, address string) (net.Conn, error) { + return (&net.Dialer{}).DialContext(ctx, network, fakeSourceAddr) + } +} + +//bool : can download,float32 downloadSpeed +func downloadSpeedHandler(ip *net.IPAddr) float64 { + client := &http.Client{ + Transport: &http.Transport{DialContext: getDialContext(ip)}, + Timeout: Timeout, + } + response, err := client.Get(URL) + if err != nil { + return 0.0 + } + defer response.Body.Close() + if response.StatusCode != 200 { + return 0.0 + } + timeStart := time.Now() + timeEnd := timeStart.Add(Timeout) + + contentLength := response.ContentLength + buffer := make([]byte, bufferSize) + + var ( + contentRead int64 = 0 + timeSlice = Timeout / 100 + timeCounter = 1 + lastContentRead int64 = 0 + ) + + var nextTime = timeStart.Add(timeSlice * time.Duration(timeCounter)) + e := ewma.NewMovingAverage() + + for contentLength != contentRead { + currentTime := time.Now() + if currentTime.After(nextTime) { + timeCounter++ + 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 + } + e.Add(float64(contentRead-lastContentRead) / (float64(nextTime.Sub(currentTime)) / float64(timeSlice))) + } + } + return e.Value() / (Timeout.Seconds() / 120) + +} diff --git a/task/ip.go b/task/ip.go index 5815c5a..c2ff808 100644 --- a/task/ip.go +++ b/task/ip.go @@ -3,4 +3,8 @@ package task var ( // IPv6 IP version is 6 IPv6 = false + // TestAll test all ip + TestAll = false + // IPFile is the filename of IP Rangs + IPFile = "ip.txt" ) diff --git a/task/tcping.go b/task/tcping.go index b065e9d..9795f0b 100644 --- a/task/tcping.go +++ b/task/tcping.go @@ -3,6 +3,7 @@ package task import ( "fmt" "net" + "sort" "sync" "time" @@ -12,12 +13,15 @@ import ( const ( tcpConnectTimeout = time.Second * 1 maxRoutine = 1000 + defaultRoutines = 200 + defaultPort = 443 + defaultPingTimes = 4 ) var ( - DefaultRoutine = 200 - TCPPort int = 443 - PingTimes int = 4 + Routines = defaultRoutines + TCPPort int = defaultPort + PingTimes int = defaultPingTimes ) type Ping struct { @@ -29,14 +33,27 @@ type Ping struct { bar *utils.Bar } +func checkPingDefault() { + if Routines <= 0 { + Routines = defaultRoutines + } + if TCPPort <= 0 || TCPPort >= 65535 { + TCPPort = defaultPort + } + if PingTimes <= 0 { + PingTimes = defaultPingTimes + } +} + func NewPing(ips []net.IPAddr) *Ping { + checkPingDefault() return &Ping{ wg: &sync.WaitGroup{}, m: &sync.Mutex{}, ips: ips, csv: make(utils.PingDelaySet, 0), - control: make(chan bool, DefaultRoutine), - bar: utils.NewBar(len(ips) * PingTimes), + control: make(chan bool, Routines), + bar: utils.NewBar(len(ips)), } } @@ -48,6 +65,7 @@ func (p *Ping) Run() utils.PingDelaySet { } p.wg.Wait() p.bar.Done() + sort.Sort(p.csv) return p.csv } @@ -112,7 +130,7 @@ func (p *Ping) tcpingHandler(ip net.IPAddr) { break } } - p.bar.Grow(PingTimes) + p.bar.Grow(1) if !ipCanConnect { return } @@ -126,7 +144,7 @@ func (p *Ping) tcpingHandler(ip net.IPAddr) { // } data := &utils.PingData{ IP: ip, - Sended: PingTimes, + Sended: PingTimes, Received: pingRecv, Delay: delay / time.Duration(pingRecv), } diff --git a/tcping.go b/tcping.go deleted file mode 100644 index 1cbacd0..0000000 --- a/tcping.go +++ /dev/null @@ -1,167 +0,0 @@ -package main - -import ( - "context" - "fmt" - "io" - "net" - "net/http" - "sync" - "time" - - "github.com/VividCortex/ewma" -) - -//bool connectionSucceed float32 time -func tcping(ip net.IPAddr, tcpPort int) (bool, time.Duration) { - startTime := time.Now() - fullAddress := fmt.Sprintf("%s:%d", ip.String(), tcpPort) - //fmt.Println(ip.String()) - if ipv6Mode { // IPv6 需要加上 [] - fullAddress = fmt.Sprintf("[%s]:%d", ip.String(), tcpPort) - } - conn, err := net.DialTimeout("tcp", fullAddress, tcpConnectTimeout) - if err != nil { - return false, 0 - } - defer conn.Close() - duration := time.Since(startTime) - return true, duration -} - -//pingReceived pingTotalTime -func checkConnection(ip net.IPAddr, tcpPort int) (pingRecv int, pingTime time.Duration) { - for i := 1; i <= failTime; i++ { - pingSucceed, pingTimeCurrent := tcping(ip, tcpPort) - if pingSucceed { - pingRecv++ - pingTime += pingTimeCurrent - } - } - return -} - -//return Success packetRecv averagePingTime specificIPAddr -func tcpingHandler(ip net.IPAddr, tcpPort, pingCount int, progressHandler func(e progressEvent)) (bool, int, time.Duration, net.IPAddr) { - ipCanConnect := false - pingRecv := 0 - var pingTime time.Duration - 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(NoAvailableIPFound) - return false, 0, 0, net.IPAddr{} - } - progressHandler(AvailableIPFound) - for i := failTime; i < pingCount; i++ { - fmt.Println("failTime", failTime) - pingSuccess, pingTimeCurrent := tcping(ip, tcpPort) - progressHandler(NormalPing) - if pingSuccess { - pingRecv++ - pingTime += pingTimeCurrent - } - } - return true, pingRecv, pingTime / time.Duration(pingRecv), ip -} - -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() - // fmt.Println(ip.String()) - 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: downloadTestTime, - } - 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 - } - defer response.Body.Close() - if response.StatusCode != 200 { - return false, 0 - } - timeStart := time.Now() - timeEnd := timeStart.Add(downloadTestTime) - - contentLength := response.ContentLength - buffer := make([]byte, downloadBufferSize) - - var ( - contentRead int64 = 0 - timeSlice = downloadTestTime / 100 - timeCounter = 1 - 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++ - 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 - } - e.Add(float64(contentRead-lastContentRead) / (float64(nextTime.Sub(currentTime)) / float64(timeSlice))) - } - } - return true, float32(e.Value()) / (float32(downloadTestTime.Seconds()) / 120) - -} diff --git a/utils/csv.go b/utils/csv.go index f93452d..651cb69 100644 --- a/utils/csv.go +++ b/utils/csv.go @@ -2,20 +2,24 @@ package utils import ( "encoding/csv" + "fmt" "log" "net" "os" - "sort" "strconv" "time" ) +const defaultOutput = "result.csv" + var ( MaxDelay = 9999 * time.Millisecond MinDelay = time.Duration(0) InputMaxDelay = MaxDelay InputMinDelay = MinDelay + Output = defaultOutput + PrintNum = 20 ) type PingData struct { @@ -28,7 +32,7 @@ type PingData struct { type CloudflareIPData struct { *PingData recvRate float32 - downloadSpeed float32 + DownloadSpeed float64 } func (cf *CloudflareIPData) getRecvRate() float32 { @@ -46,14 +50,17 @@ func (cf *CloudflareIPData) toString() []string { result[2] = strconv.Itoa(cf.Received) result[3] = strconv.FormatFloat(float64(cf.getRecvRate()), 'f', 2, 32) result[4] = cf.Delay.String() - result[5] = strconv.FormatFloat(float64(cf.downloadSpeed)/1024/1024, 'f', 2, 32) + result[5] = strconv.FormatFloat(cf.DownloadSpeed/1024/1024, 'f', 2, 32) return result } -func ExportCsv(filePath string, data []CloudflareIPData) { - fp, err := os.Create(filePath) +func ExportCsv(data []CloudflareIPData) { + if Output == "" { + Output = defaultOutput + } + fp, err := os.Create(Output) if err != nil { - log.Fatalf("创建文件[%s]失败:%v", filePath, err) + log.Fatalf("创建文件[%s]失败:%v", Output, err) return } defer fp.Close() @@ -74,7 +81,6 @@ func convertToString(data []CloudflareIPData) [][]string { type PingDelaySet []CloudflareIPData func (s PingDelaySet) FilterDelay() (data PingDelaySet) { - sort.Sort(s) if InputMaxDelay >= MaxDelay || InputMinDelay <= MinDelay { return s } @@ -114,9 +120,30 @@ func (s DownloadSpeedSet) Len() int { } func (s DownloadSpeedSet) Less(i, j int) bool { - return s[i].downloadSpeed > s[j].downloadSpeed + return s[i].DownloadSpeed > s[j].DownloadSpeed } func (s DownloadSpeedSet) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +func (s DownloadSpeedSet) Print(ipv6 bool) { + if len(s) <= 0 { // IP数组长度(IP数量) 大于 0 时继续 + fmt.Println("\n[信息] 完整测速结果 IP 数量为 0,跳过输出结果。") + return + } + dateString := convertToString(s) // 转为多维数组 [][]String + if len(dateString) < PrintNum { // 如果IP数组长度(IP数量) 小于 打印次数,则次数改为IP数量 + PrintNum = len(dateString) + } + headFormat := "%-16s%-5s%-5s%-5s%-6s%-11s\n" + dataFormat := "%-18s%-8s%-8s%-8s%-15s%-15s\n" + if ipv6 { // IPv6 太长了,所以需要调整一下间隔 + headFormat = "%-40s%-5s%-5s%-5s%-6s%-11s\n" + dataFormat = "%-42s%-8s%-8s%-8s%-10s%-15s\n" + } + fmt.Printf(headFormat, "IP 地址", "已发送", "已接收", "丢包率", "平均延迟", "下载速度 (MB/s)") + for i := 0; i < PrintNum; i++ { + fmt.Printf(dataFormat, dateString[i][0], dateString[i][1], dateString[i][2], dateString[i][3], dateString[i][4], dateString[i][5]) + } +} \ No newline at end of file diff --git a/utils/progress.go b/utils/progress.go index 5fc3be5..85010c3 100644 --- a/utils/progress.go +++ b/utils/progress.go @@ -2,14 +2,6 @@ package utils import "github.com/cheggaaa/pb/v3" -type ProgressEvent int - -const ( - NoAvailableIPFound ProgressEvent = iota - AvailableIPFound - NormalPing -) - type Bar struct { pb *pb.ProgressBar } @@ -24,17 +16,4 @@ func (b *Bar) Grow(num int) { func (b *Bar) Done() { b.pb.Finish() -} - -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() - } - } -} +} \ No newline at end of file