rebuild download

This commit is contained in:
mazhuang 2021-11-10 17:12:12 +08:00
parent 4d64abb94d
commit f1a9b5c966
8 changed files with 287 additions and 390 deletions

View File

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

252
main.go
View File

@ -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) // 显示最快结果
}
// 检查更新

149
task/download.go Normal file
View File

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

View File

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

View File

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

167
tcping.go
View File

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

View File

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

View File

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