فهرست منبع

rebuild download

mazhuang 4 سال پیش
والد
کامیت
f1a9b5c966
8فایلهای تغییر یافته به همراه287 افزوده شده و 390 حذف شده
  1. 4 3
      IPRangeLoader.go
  2. 69 183
      main.go
  3. 149 0
      task/download.go
  4. 4 0
      task/ip.go
  5. 25 7
      task/tcping.go
  6. 0 167
      tcping.go
  7. 35 8
      utils/csv.go
  8. 1 22
      utils/progress.go

+ 4 - 3
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))

+ 69 - 183
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)
-
-	// 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) // 显示最快结果
-}
+	fmt.Printf("开始延迟测速(模式:TCP %s,端口:%d ,平均延迟上限:%v,平均延迟下限:%v)\n", ipVersion, task.TCPPort, utils.InputMaxDelay, utils.InputMinDelay)
 
-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) // 显示最快结果
 }
 
 // 检查更新

+ 149 - 0
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)
+
+}

+ 4 - 0
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"
 )

+ 25 - 7
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),
 	}

+ 0 - 167
tcping.go

@@ -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)
-
-}

+ 35 - 8
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])
+	}
+}

+ 1 - 22
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()
-		}
-	}
-}
+}