diff --git a/main.go b/main.go index 9efad35..3b3a621 100644 --- a/main.go +++ b/main.go @@ -11,6 +11,8 @@ import ( "sync" "time" + "CloudflareSpeedTest/task" + "github.com/cheggaaa/pb/v3" ) @@ -76,12 +78,16 @@ https://github.com/XIU2/CloudflareSpeedTest flag.Float64Var(&speedLimit, "sl", 0, "下载速度下限") flag.IntVar(&printResultNum, "p", 20, "显示结果数量") flag.BoolVar(&disableDownload, "dd", false, "禁用下载测速") - flag.BoolVar(&ipv6Mode, "ipv6", 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.BoolVar(&printVersion, "v", false, "打印程序版本") + task.TCPPort = tcpPort + task.IPv6 = ipv6Mode + task.DefaultRoutine = pingRoutine + flag.Usage = func() { fmt.Print(help) } flag.Parse() if printVersion { @@ -155,16 +161,20 @@ func main() { 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 + 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)) // 排序(按延迟,从低到高,不同丢包率会分开单独按延迟和丢包率排序) // 延迟测速完毕后,以 [平均延迟上限] + [平均延迟下限] 条件过滤结果 diff --git a/task/ip.go b/task/ip.go new file mode 100644 index 0000000..5815c5a --- /dev/null +++ b/task/ip.go @@ -0,0 +1,6 @@ +package task + +var ( + // IPv6 IP version is 6 + IPv6 = false +) diff --git a/task/tcping.go b/task/tcping.go new file mode 100644 index 0000000..b065e9d --- /dev/null +++ b/task/tcping.go @@ -0,0 +1,135 @@ +package task + +import ( + "fmt" + "net" + "sync" + "time" + + "CloudflareSpeedTest/utils" +) + +const ( + tcpConnectTimeout = time.Second * 1 + maxRoutine = 1000 +) + +var ( + DefaultRoutine = 200 + TCPPort int = 443 + PingTimes int = 4 +) + +type Ping struct { + wg *sync.WaitGroup + m *sync.Mutex + ips []net.IPAddr + csv utils.PingDelaySet + control chan bool + bar *utils.Bar +} + +func NewPing(ips []net.IPAddr) *Ping { + 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), + } +} + +func (p *Ping) Run() utils.PingDelaySet { + for _, ip := range p.ips { + p.wg.Add(1) + p.control <- false + go p.start(ip) + } + p.wg.Wait() + p.bar.Done() + return p.csv +} + +func (p *Ping) start(ip net.IPAddr) { + defer p.wg.Done() + p.tcpingHandler(ip) + <-p.control +} + +//bool connectionSucceed float32 time +func (p *Ping) tcping(ip net.IPAddr) (bool, time.Duration) { + startTime := time.Now() + fullAddress := fmt.Sprintf("%s:%d", ip.String(), TCPPort) + //fmt.Println(ip.String()) + if IPv6 { // 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 (p *Ping) checkConnection(ip net.IPAddr) (recv int, totalDelay time.Duration) { + for i := 0; i < PingTimes; i++ { + if ok, delay := p.tcping(ip); ok { + recv++ + totalDelay += delay + } + } + return +} + +func (p *Ping) appendIPData(data *utils.PingData) { + p.m.Lock() + defer p.m.Unlock() + p.csv = append(p.csv, utils.CloudflareIPData{ + PingData: data, + }) +} + +//return Success packetRecv averagePingTime specificIPAddr +func (p *Ping) tcpingHandler(ip net.IPAddr) { + ipCanConnect := false + pingRecv := 0 + var delay time.Duration + for !ipCanConnect { + recv, totalDlay := p.checkConnection(ip) + if recv > 0 { + ipCanConnect = true + pingRecv = recv + delay = totalDlay + } else { + ip.IP[15]++ + if ip.IP[15] == 0 { + break + } + break + } + } + p.bar.Grow(PingTimes) + if !ipCanConnect { + return + } + // for i := 0; i < PingTimes; i++ { + // pingSuccess, pingTimeCurrent := p.tcping(ip) + // progressHandler(utils.NormalPing) + // if pingSuccess { + // pingRecv++ + // pingTime += pingTimeCurrent + // } + // } + data := &utils.PingData{ + IP: ip, + Sended: PingTimes, + Received: pingRecv, + Delay: delay / time.Duration(pingRecv), + } + p.appendIPData(data) + return +} diff --git a/tcping.go b/tcping.go index 02e55ee..1cbacd0 100644 --- a/tcping.go +++ b/tcping.go @@ -91,7 +91,7 @@ func tcpingGoroutine(wg *sync.WaitGroup, mutex *sync.Mutex, ip net.IPAddr, tcpPo *csv = append(*csv, cfdata) mutex.Unlock() } - // <-control + <-control } func GetDialContextByAddr(fakeSourceAddr string) func(ctx context.Context, network, address string) (net.Conn, error) { diff --git a/tcping/ping.go b/tcping/ping.go deleted file mode 100644 index 6f5e4ac..0000000 --- a/tcping/ping.go +++ /dev/null @@ -1,129 +0,0 @@ -package tcp - -import ( - "fmt" - "net" - "sync" - "time" - - "CloudflareSpeedTest/utils" -) - -const tcpConnectTimeout = time.Second * 1 - -type Ping struct { - wg *sync.WaitGroup - m *sync.Mutex - ips []net.IPAddr - isIPv6 bool - tcpPort int - pingCount int - csv []utils.CloudflareIPData - control chan bool - progressHandler func(e utils.ProgressEvent) -} - -func NewPing(ips []net.IPAddr, port, pingTime int, ipv6 bool) *Ping { - return &Ping{ - wg: &sync.WaitGroup{}, - m: &sync.Mutex{}, - ips: ips, - isIPv6: ipv6, - tcpPort: port, - pingCount: pingTime, - csv: make([]utils.CloudflareIPData, 0), - control: make(chan bool), - } -} - -func (p *Ping) Run() { - for _, ip := range p.ips { - p.wg.Add(1) - p.control <- false - go p.start(ip) - } -} - -func (p *Ping) start(ip net.IPAddr) { - defer p.wg.Done() - if ok, data := p.tcpingHandler(ip, nil); ok { - p.appendIPData(data) - } - <-p.control -} - -func (p *Ping) appendIPData(data *utils.PingData) { - p.m.Lock() - defer p.m.Unlock() - p.csv = append(p.csv, utils.CloudflareIPData{ - PingData: data, - }) -} - -//bool connectionSucceed float32 time -func (p *Ping) tcping(ip net.IPAddr) (bool, time.Duration) { - startTime := time.Now() - fullAddress := fmt.Sprintf("%s:%d", ip.String(), p.tcpPort) - //fmt.Println(ip.String()) - if p.isIPv6 { // IPv6 需要加上 [] - fullAddress = fmt.Sprintf("[%s]:%d", ip.String(), p.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 (p *Ping) checkConnection(ip net.IPAddr) (pingRecv int, pingTime time.Duration) { - for i := 0; i < p.pingCount; i++ { - if pingSucceed, pingTimeCurrent := p.tcping(ip); pingSucceed { - pingRecv++ - pingTime += pingTimeCurrent - } - } - return -} - -//return Success packetRecv averagePingTime specificIPAddr -func (p *Ping) tcpingHandler(ip net.IPAddr, progressHandler func(e utils.ProgressEvent)) (bool, *utils.PingData) { - ipCanConnect := false - pingRecv := 0 - var pingTime time.Duration - for !ipCanConnect { - pingRecvCurrent, pingTimeCurrent := p.checkConnection(ip) - if pingRecvCurrent != 0 { - ipCanConnect = true - pingRecv = pingRecvCurrent - pingTime = pingTimeCurrent - } else { - ip.IP[15]++ - if ip.IP[15] == 0 { - break - } - break - } - } - if !ipCanConnect { - progressHandler(utils.NoAvailableIPFound) - return false, nil - } - progressHandler(utils.AvailableIPFound) - for i := 0; i < p.pingCount; i++ { - pingSuccess, pingTimeCurrent := p.tcping(ip) - progressHandler(utils.NormalPing) - if pingSuccess { - pingRecv++ - pingTime += pingTimeCurrent - } - } - return true, &utils.PingData{ - IP: ip, - Count: p.pingCount, - Received: pingRecv, - Delay: pingTime / time.Duration(pingRecv), - } -} diff --git a/utils/csv.go b/utils/csv.go index 242c9ea..f93452d 100644 --- a/utils/csv.go +++ b/utils/csv.go @@ -5,13 +5,22 @@ import ( "log" "net" "os" + "sort" "strconv" "time" ) +var ( + MaxDelay = 9999 * time.Millisecond + MinDelay = time.Duration(0) + + InputMaxDelay = MaxDelay + InputMinDelay = MinDelay +) + type PingData struct { IP net.IPAddr - Count int + Sended int Received int Delay time.Duration } @@ -24,12 +33,23 @@ type CloudflareIPData struct { func (cf *CloudflareIPData) getRecvRate() float32 { if cf.recvRate == 0 { - pingLost := cf.Count - cf.Received - cf.recvRate = float32(pingLost) / float32(cf.Count) + pingLost := cf.Sended - cf.Received + cf.recvRate = float32(pingLost) / float32(cf.Sended) } return cf.recvRate } +func (cf *CloudflareIPData) toString() []string { + result := make([]string, 6) + result[0] = cf.IP.String() + result[1] = strconv.Itoa(cf.Sended) + 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) + return result +} + func ExportCsv(filePath string, data []CloudflareIPData) { fp, err := os.Create(filePath) if err != nil { @@ -43,17 +63,6 @@ func ExportCsv(filePath string, data []CloudflareIPData) { w.Flush() } -func (cf *CloudflareIPData) toString() []string { - result := make([]string, 6) - result[0] = cf.IP.String() - result[1] = strconv.Itoa(cf.Count) - 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) - return result -} - func convertToString(data []CloudflareIPData) [][]string { result := make([][]string, 0) for _, v := range data { @@ -61,3 +70,53 @@ func convertToString(data []CloudflareIPData) [][]string { } return result } + +type PingDelaySet []CloudflareIPData + +func (s PingDelaySet) FilterDelay() (data PingDelaySet) { + sort.Sort(s) + if InputMaxDelay >= MaxDelay || InputMinDelay <= MinDelay { + return s + } + for _, v := range s { + if v.Delay > MaxDelay { // 平均延迟上限 + break + } + if v.Delay <= MinDelay { // 平均延迟下限 + continue + } + data = append(data, v) // 延迟满足条件时,添加到新数组中 + } + return +} + +func (s PingDelaySet) Len() int { + return len(s) +} + +func (s PingDelaySet) Less(i, j int) bool { + iRate, jRate := s[i].getRecvRate(), s[j].getRecvRate() + if iRate != jRate { + return iRate < jRate + } + return s[i].Delay < s[j].Delay +} + +func (s PingDelaySet) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +// 下载速度排序 +type DownloadSpeedSet []CloudflareIPData + +func (s DownloadSpeedSet) Len() int { + return len(s) +} + +func (s DownloadSpeedSet) Less(i, j int) bool { + return s[i].downloadSpeed > s[j].downloadSpeed +} + +func (s DownloadSpeedSet) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} diff --git a/utils/progress.go b/utils/progress.go index cf14e27..5fc3be5 100644 --- a/utils/progress.go +++ b/utils/progress.go @@ -11,13 +11,21 @@ const ( ) type Bar struct { - *pb.ProgressBar + pb *pb.ProgressBar } func NewBar(count int) *Bar { return &Bar{pb.Simple.Start(count)} } +func (b *Bar) Grow(num int) { + b.pb.Add(num) +} + +func (b *Bar) Done() { + b.pb.Finish() +} + func handleProgressGenerator(pb *pb.ProgressBar) func(e ProgressEvent) { return func(e ProgressEvent) { switch e {