Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 57937127ee | |||
| f2936ac6ad | |||
| e60a1ef082 | |||
| 12b96089e7 | |||
| 5636fac3e6 | |||
| d229b22bd8 | |||
| 5e97736367 |
31
.github/workflows/release.yml
vendored
Normal file
31
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*"
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: "1.22"
|
||||||
|
|
||||||
|
- name: Run GoReleaser
|
||||||
|
uses: goreleaser/goreleaser-action@v6
|
||||||
|
with:
|
||||||
|
version: "~> v2"
|
||||||
|
args: release --clean
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
36
.goreleaser.yml
Normal file
36
.goreleaser.yml
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
version: 2
|
||||||
|
|
||||||
|
builds:
|
||||||
|
- binary: tcping
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=0
|
||||||
|
goos:
|
||||||
|
- linux
|
||||||
|
- darwin
|
||||||
|
- windows
|
||||||
|
goarch:
|
||||||
|
- amd64
|
||||||
|
- arm64
|
||||||
|
- "386"
|
||||||
|
ignore:
|
||||||
|
- goos: darwin
|
||||||
|
goarch: "386"
|
||||||
|
ldflags:
|
||||||
|
- -w -s -buildid=
|
||||||
|
|
||||||
|
archives:
|
||||||
|
- format: tar.gz
|
||||||
|
name_template: "tcping-{{ .Os }}-{{ .Arch }}"
|
||||||
|
format_overrides:
|
||||||
|
- goos: windows
|
||||||
|
format: zip
|
||||||
|
|
||||||
|
checksum:
|
||||||
|
name_template: checksums.txt
|
||||||
|
|
||||||
|
changelog:
|
||||||
|
sort: asc
|
||||||
|
filters:
|
||||||
|
exclude:
|
||||||
|
- "^docs:"
|
||||||
|
- "^test:"
|
||||||
46
Makefile
46
Makefile
@@ -1,46 +0,0 @@
|
|||||||
NAME=tcping
|
|
||||||
BINDIR=bin
|
|
||||||
GOBUILD=CGO_ENABLED=0 go build -ldflags '-w -s -buildid='
|
|
||||||
VERSION=1.0.0
|
|
||||||
# The -w and -s flags reduce binary sizes by excluding unnecessary symbols and debug info
|
|
||||||
# The -buildid= flag makes builds reproducible
|
|
||||||
|
|
||||||
all: linux darwin-amd64 darwin-arm64 win64 win32
|
|
||||||
|
|
||||||
linux:
|
|
||||||
GOARCH=amd64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
darwin-amd64:
|
|
||||||
GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
darwin-arm64:
|
|
||||||
GOARCH=arm64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
win64:
|
|
||||||
GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
|
||||||
|
|
||||||
win32:
|
|
||||||
GOARCH=386 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
|
||||||
|
|
||||||
releases: linux darwin-amd64 darwin-arm64 win64 win32
|
|
||||||
chmod +x $(BINDIR)/$(NAME)-*
|
|
||||||
tar zcf $(BINDIR)/$(NAME)-linux-$(VERSION).tar.gz -C $(BINDIR) $(NAME)-linux
|
|
||||||
tar zcf $(BINDIR)/$(NAME)-darwin-amd64-$(VERSION).tar.gz -C $(BINDIR) $(NAME)-darwin-amd64
|
|
||||||
tar zcf $(BINDIR)/$(NAME)-darwin-arm64-$(VERSION).tar.gz -C $(BINDIR) $(NAME)-darwin-arm64
|
|
||||||
zip -j $(BINDIR)/$(NAME)-win32-$(VERSION).zip $(BINDIR)/$(NAME)-win32.exe
|
|
||||||
zip -j $(BINDIR)/$(NAME)-win64-$(VERSION).zip $(BINDIR)/$(NAME)-win64.exe
|
|
||||||
rm -f $(BINDIR)/$(NAME)-darwin-amd64 $(BINDIR)/$(NAME)-darwin-arm64 $(BINDIR)/$(NAME)-linux $(BINDIR)/$(NAME)-win32.exe $(BINDIR)/$(NAME)-win64.exe
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm $(BINDIR)/*
|
|
||||||
|
|
||||||
# Remove trailing {} from the release upload url
|
|
||||||
GITHUB_UPLOAD_URL=$(shell echo $${GITHUB_RELEASE_UPLOAD_URL%\{*})
|
|
||||||
|
|
||||||
upload: releases
|
|
||||||
curl -H "Authorization: token $(GITHUB_TOKEN)" -H "Content-Type: application/gzip" --data-binary @$(BINDIR)/$(NAME)-linux.tgz "$(GITHUB_UPLOAD_URL)?name=$(NAME)-linux.tgz"
|
|
||||||
curl -H "Authorization: token $(GITHUB_TOKEN)" -H "Content-Type: application/gzip" --data-binary @$(BINDIR)/$(NAME)-linux.gz "$(GITHUB_UPLOAD_URL)?name=$(NAME)-linux.gz"
|
|
||||||
curl -H "Authorization: token $(GITHUB_TOKEN)" -H "Content-Type: application/gzip" --data-binary @$(BINDIR)/$(NAME)-macos-amd64.gz "$(GITHUB_UPLOAD_URL)?name=$(NAME)-macos-amd64.gz"
|
|
||||||
curl -H "Authorization: token $(GITHUB_TOKEN)" -H "Content-Type: application/gzip" --data-binary @$(BINDIR)/$(NAME)-macos-arm64.gz "$(GITHUB_UPLOAD_URL)?name=$(NAME)-macos-arm64.gz"
|
|
||||||
curl -H "Authorization: token $(GITHUB_TOKEN)" -H "Content-Type: application/zip" --data-binary @$(BINDIR)/$(NAME)-win64.zip "$(GITHUB_UPLOAD_URL)?name=$(NAME)-win64.zip"
|
|
||||||
curl -H "Authorization: token $(GITHUB_TOKEN)" -H "Content-Type: application/zip" --data-binary @$(BINDIR)/$(NAME)-win32.zip "$(GITHUB_UPLOAD_URL)?name=$(NAME)-win32.zip"
|
|
||||||
55
README.md
55
README.md
@@ -1,50 +1,55 @@
|
|||||||
# Tcping GO
|
# Tcping GO
|
||||||
|
|
||||||
一个使用go写的小工具,用于检测TCP端口是否开放
|
一个使用 Golang 写的小工具,用于检测TCP端口是否开放
|
||||||
|
|
||||||
## 使用方法
|
## 使用方法
|
||||||
|
|
||||||
你可以简单的测试一个地址的某个端口是否开放,可以使用域名或者IP地址,默认测试该地址的80端口
|
你可以简单的测试一个域名(或 IP 地址)的某个端口是否开放,默认测试 80 端口
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
tcping www.baidu.com
|
$ tcping.exe www.qq.com
|
||||||
使用 www.qq.com 的 A 记录: 175.27.8.138
|
Using www.qq.com A record: 101.91.42.232
|
||||||
[1] 来自 175.27.8.138:80 的响应: 时间=29.256ms
|
[1] Reply from 101.91.42.232:80: time=14.400ms
|
||||||
[2] 来自 175.27.8.138:80 的响应: 时间=29.086ms
|
[2] Reply from 101.91.42.232:80: time=15.086ms
|
||||||
[3] 来自 175.27.8.138:80 的响应: 时间=37.761ms
|
[3] Reply from 101.91.42.232:80: time=14.557ms
|
||||||
[4] 来自 175.27.8.138:80 的响应: 时间=30.259ms
|
[4] Reply from 101.91.42.232:80: time=11.862ms
|
||||||
|
|
||||||
测试完成,成功次数: 4/4
|
Test finished, success 4/4 (100.00%)
|
||||||
|
min = 11.862ms, max = 15.086ms, avg = 13.976ms
|
||||||
```
|
```
|
||||||
|
|
||||||
使用 `-p` 指定端口号,如下
|
也可以指定测试端口号,跟随在域名或 IP 地址后面
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
tcping www.qq.com -p 443
|
$ tcping.exe www.qq.com 443
|
||||||
使用 www.qq.com 的 A 记录: 175.27.8.138
|
Using www.qq.com A record: 101.91.42.232
|
||||||
[1] 来自 175.27.8.138:443 的响应: 时间=28.835ms
|
[1] Reply from 101.91.42.232:443: time=12.348ms
|
||||||
[2] 来自 175.27.8.138:443 的响应: 时间=27.419ms
|
[2] Reply from 101.91.42.232:443: time=10.089ms
|
||||||
[3] 来自 175.27.8.138:443 的响应: 时间=44.382ms
|
[3] Reply from 101.91.42.232:443: time=10.370ms
|
||||||
[4] 来自 175.27.8.138:443 的响应: 时间=28.356ms
|
[4] Reply from 101.91.42.232:443: time=9.131ms
|
||||||
|
|
||||||
测试完成,成功次数: 4/4
|
Test finished, success 4/4 (100.00%)
|
||||||
|
min = 9.131ms, max = 12.348ms, avg = 10.485ms
|
||||||
```
|
```
|
||||||
|
|
||||||
使用 `-6` 指定使用IPv6(仅对域名有效)
|
使用 `-6` 指定使用IPv6(仅对域名有效)
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
tcping www.qq.com -6
|
$ tcping.exe www.qq.com -6
|
||||||
使用 www.qq.com 的 AAAA 记录: 240e:97c:2f:1::5c
|
Using www.qq.com AAAA record: 240e:e1:a800:120::36
|
||||||
[1] 来自 [240e:97c:2f:1::5c]:80 的响应: 时间=30.795ms
|
[1] Reply from [240e:e1:a800:120::36]:80: time=17.576ms
|
||||||
[2] 来自 [240e:97c:2f:1::5c]:80 的响应: 时间=30.247ms
|
[2] Reply from [240e:e1:a800:120::36]:80: time=14.510ms
|
||||||
[3] 来自 [240e:97c:2f:1::5c]:80 的响应: 时间=30.856ms
|
[3] Reply from [240e:e1:a800:120::36]:80: time=14.667ms
|
||||||
[4] 来自 [240e:97c:2f:1::5c]:80 的响应: 时间=30.275ms
|
[4] Reply from [240e:e1:a800:120::36]:80: time=15.449ms
|
||||||
|
|
||||||
测试完成,成功次数: 4/4
|
Test finished, success 4/4 (100.00%)
|
||||||
|
min = 14.510ms, max = 17.576ms, avg = 15.551ms
|
||||||
```
|
```
|
||||||
|
|
||||||
使用 `-c` 指定测试次数,默认为4次
|
使用 `-c` 指定测试次数,默认为4次
|
||||||
|
|
||||||
使用 `-t` 指定超时时间,默认为2秒
|
使用 `-s` 指定超时时间,默认为2秒
|
||||||
|
|
||||||
|
使用 `-t` 来启用无限次测试模式
|
||||||
|
|
||||||
使用 `-f` 来启用快速模式,降低两次成功测试之间的间隔时间
|
使用 `-f` 来启用快速模式,降低两次成功测试之间的间隔时间
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -1,4 +1,4 @@
|
|||||||
module tcping
|
module github.com/taurusxin/tcping-go
|
||||||
|
|
||||||
go 1.22.1
|
go 1.22.1
|
||||||
|
|
||||||
|
|||||||
177
internal/cli/cli.go
Normal file
177
internal/cli/cli.go
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"strconv"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
flag "github.com/spf13/pflag"
|
||||||
|
"github.com/taurusxin/tcping-go/internal/filter"
|
||||||
|
"github.com/taurusxin/tcping-go/internal/stats"
|
||||||
|
)
|
||||||
|
|
||||||
|
const version = "1.3.0"
|
||||||
|
|
||||||
|
// Run executes the tcping CLI.
|
||||||
|
func Run() {
|
||||||
|
var (
|
||||||
|
port int
|
||||||
|
count int
|
||||||
|
timeoutDuration time.Duration
|
||||||
|
infinite bool
|
||||||
|
ipv6 bool
|
||||||
|
fast bool
|
||||||
|
|
||||||
|
successCount int
|
||||||
|
successDelay []time.Duration
|
||||||
|
attemptCount int
|
||||||
|
stopped bool
|
||||||
|
)
|
||||||
|
|
||||||
|
showHelp := flag.BoolP("help", "h", false, "Show help")
|
||||||
|
showVersion := flag.BoolP("version", "v", false, "Show version")
|
||||||
|
flag.IntVarP(&count, "count", "c", 4, "Number of probes")
|
||||||
|
flag.DurationVarP(&timeoutDuration, "timeout", "s", 2*time.Second, "Timeout")
|
||||||
|
flag.BoolVarP(&infinite, "infinite", "t", false, "Infinite probes")
|
||||||
|
flag.BoolVarP(&ipv6, "ipv6", "6", false, "Use IPv6; requires domain name")
|
||||||
|
flag.BoolVarP(&fast, "fast", "f", false, "Fast mode; reduce delay between successful probes")
|
||||||
|
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if *showHelp {
|
||||||
|
flag.Usage()
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *showVersion {
|
||||||
|
fmt.Printf("tcping v%s\n", version)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
args := flag.Args()
|
||||||
|
if len(args) < 1 {
|
||||||
|
flag.Usage()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
hostname := args[0]
|
||||||
|
port = 80
|
||||||
|
if len(args) >= 2 {
|
||||||
|
p, err := strconv.Atoi(args[1])
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Port must be an integer")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
port = p
|
||||||
|
}
|
||||||
|
if port < 1 || port > 65535 {
|
||||||
|
fmt.Println("Port must be between 1 and 65535")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
stopped = false
|
||||||
|
sigChan := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
|
||||||
|
ip := ""
|
||||||
|
|
||||||
|
if net.ParseIP(hostname) == nil {
|
||||||
|
ips, err := net.LookupIP(hostname)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to resolve %s: %s\n", hostname, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
record := "A"
|
||||||
|
if ipv6 {
|
||||||
|
record = "AAAA"
|
||||||
|
}
|
||||||
|
ip, err = filter.IP(ips, ipv6)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("No %s record found for %s\n", record, hostname)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Printf("Using %s %s record: %s\n", hostname, record, ip)
|
||||||
|
} else {
|
||||||
|
ip = hostname
|
||||||
|
}
|
||||||
|
|
||||||
|
address := net.JoinHostPort(ip, fmt.Sprintf("%d", port))
|
||||||
|
|
||||||
|
if infinite {
|
||||||
|
count = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
done := make(chan bool, 1)
|
||||||
|
go func() {
|
||||||
|
probe := 0
|
||||||
|
for i := 0; infinite || i < count; i++ {
|
||||||
|
start := time.Now()
|
||||||
|
conn, err := net.DialTimeout("tcp", address, timeoutDuration)
|
||||||
|
duration := time.Since(start)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
attemptCount++
|
||||||
|
probe++
|
||||||
|
fmt.Printf("[%d] Connection to %s failed: %s\n", probe, address, "timeout")
|
||||||
|
} else if duration < 500*time.Microsecond {
|
||||||
|
conn.Close()
|
||||||
|
if !infinite {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
attemptCount++
|
||||||
|
probe++
|
||||||
|
successCount++
|
||||||
|
successDelay = append(successDelay, duration)
|
||||||
|
fmt.Printf("[%d] Reply from %s: time=%.3fms\n", probe, address, float64(duration)/float64(time.Millisecond))
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !infinite && attemptCount >= count {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if fast {
|
||||||
|
time.Sleep(150 * time.Millisecond)
|
||||||
|
} else {
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
done <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-sigChan:
|
||||||
|
fmt.Println("\nTest interrupted by user")
|
||||||
|
stopped = true
|
||||||
|
case <-done:
|
||||||
|
}
|
||||||
|
|
||||||
|
if !stopped {
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
successDelayMs := make([]float64, len(successDelay))
|
||||||
|
for i, delay := range successDelay {
|
||||||
|
successDelayMs[i] = float64(delay) / float64(time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
minDelay := 0.0
|
||||||
|
maxDelay := 0.0
|
||||||
|
avgDelay := 0.0
|
||||||
|
successRate := 0.0
|
||||||
|
|
||||||
|
if successCount > 0 {
|
||||||
|
minDelay = stats.Min(successDelayMs)
|
||||||
|
maxDelay = stats.Max(successDelayMs)
|
||||||
|
avgDelay = stats.Avg(successDelayMs)
|
||||||
|
}
|
||||||
|
if attemptCount > 0 {
|
||||||
|
successRate = float64(successCount) / float64(attemptCount) * 100
|
||||||
|
}
|
||||||
|
fmt.Printf("Test finished, success %d/%d (%.2f%%)\nmin = %.3fms, max = %.3fms, avg = %.3fms\n", successCount, attemptCount, successRate, minDelay, maxDelay, avgDelay)
|
||||||
|
}
|
||||||
25
internal/filter/filter.go
Normal file
25
internal/filter/filter.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package filter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IP filters a list of resolved IPs and returns the first match
|
||||||
|
// for the requested address family (IPv4 or IPv6).
|
||||||
|
func IP(ips []net.IP, ipv6 bool) (string, error) {
|
||||||
|
if ipv6 {
|
||||||
|
for _, ip := range ips {
|
||||||
|
if ip.To16() != nil && ip.To4() == nil {
|
||||||
|
return ip.String(), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, ip := range ips {
|
||||||
|
if ip.To4() != nil && ip.To16() != nil {
|
||||||
|
return ip.String(), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("no suitable IP address found")
|
||||||
|
}
|
||||||
35
internal/stats/stats.go
Normal file
35
internal/stats/stats.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package stats
|
||||||
|
|
||||||
|
// Min returns the minimum value in a float64 slice.
|
||||||
|
// The slice must not be empty.
|
||||||
|
func Min(array []float64) float64 {
|
||||||
|
min := array[0]
|
||||||
|
for _, value := range array {
|
||||||
|
if value < min {
|
||||||
|
min = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return min
|
||||||
|
}
|
||||||
|
|
||||||
|
// Max returns the maximum value in a float64 slice.
|
||||||
|
// The slice must not be empty.
|
||||||
|
func Max(array []float64) float64 {
|
||||||
|
max := array[0]
|
||||||
|
for _, value := range array {
|
||||||
|
if value > max {
|
||||||
|
max = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return max
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avg returns the arithmetic mean of a float64 slice.
|
||||||
|
// The slice must not be empty.
|
||||||
|
func Avg(array []float64) float64 {
|
||||||
|
sum := 0.0
|
||||||
|
for _, value := range array {
|
||||||
|
sum += value
|
||||||
|
}
|
||||||
|
return sum / float64(len(array))
|
||||||
|
}
|
||||||
201
main.go
201
main.go
@@ -1,204 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import "github.com/taurusxin/tcping-go/internal/cli"
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
flag "github.com/spf13/pflag"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var (
|
cli.Run()
|
||||||
port int
|
|
||||||
count int
|
|
||||||
timeoutDuration time.Duration
|
|
||||||
infinite bool
|
|
||||||
ipv6 bool
|
|
||||||
fast bool
|
|
||||||
|
|
||||||
successCount int
|
|
||||||
successDelay []time.Duration
|
|
||||||
attemptCount int
|
|
||||||
stopped bool
|
|
||||||
)
|
|
||||||
|
|
||||||
version := "1.0.0"
|
|
||||||
|
|
||||||
// 设置命令行参数
|
|
||||||
showHelp := flag.BoolP("help", "h", false, "显示帮助信息")
|
|
||||||
showVersion := flag.BoolP("version", "v", false, "显示版本信息")
|
|
||||||
flag.IntVarP(&port, "port", "p", 80, "端口,默认为80")
|
|
||||||
flag.IntVarP(&count, "count", "c", 4, "测试次数,默认为4次")
|
|
||||||
flag.DurationVarP(&timeoutDuration, "timeout", "s", 2*time.Second, "超时时间,默认为2秒")
|
|
||||||
flag.BoolVarP(&infinite, "infinite", "t", false, "无限次测试")
|
|
||||||
flag.BoolVarP(&ipv6, "ipv6", "6", false, "使用 IPv6,需搭配域名使用")
|
|
||||||
flag.BoolVarP(&fast, "fast", "f", false, "快速模式,降低每次成功测试之间的间隔")
|
|
||||||
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
if *showHelp {
|
|
||||||
flag.Usage()
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *showVersion {
|
|
||||||
fmt.Printf("tcping v%s\n", version)
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
args := flag.Args()
|
|
||||||
if len(args) < 1 {
|
|
||||||
flag.Usage()
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if port < 1 || port > 65535 {
|
|
||||||
fmt.Println("端口号必须在1-65535之间")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
hostname := args[0]
|
|
||||||
|
|
||||||
// 设置信号捕获
|
|
||||||
stopped = false
|
|
||||||
sigChan := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
|
||||||
|
|
||||||
ip := ""
|
|
||||||
|
|
||||||
if net.ParseIP(hostname) == nil {
|
|
||||||
// 解析域名
|
|
||||||
ips, err := net.LookupIP(hostname)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("解析 %s 失败: %s\n", hostname, err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
record := "A"
|
|
||||||
if ipv6 {
|
|
||||||
record = "AAAA"
|
|
||||||
}
|
|
||||||
ip, err = filterIP(ips, ipv6)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("找不到 %s 的 %s 记录\n", hostname, record)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
fmt.Printf("使用 %s 的 %s 记录: %s\n", hostname, record, ip)
|
|
||||||
} else {
|
|
||||||
ip = hostname
|
|
||||||
}
|
|
||||||
|
|
||||||
address := net.JoinHostPort(ip, fmt.Sprintf("%d", port))
|
|
||||||
|
|
||||||
if infinite {
|
|
||||||
count = -1
|
|
||||||
}
|
|
||||||
|
|
||||||
done := make(chan bool, 1)
|
|
||||||
go func() {
|
|
||||||
for i := 0; infinite || i < count; i++ {
|
|
||||||
start := time.Now()
|
|
||||||
conn, err := net.DialTimeout("tcp", address, timeoutDuration)
|
|
||||||
duration := time.Since(start)
|
|
||||||
attemptCount++
|
|
||||||
|
|
||||||
fmt.Printf("[%d] ", i+1)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("测试到 %s 的连接失败: %s\n", address, "连接超时")
|
|
||||||
} else {
|
|
||||||
successCount++
|
|
||||||
successDelay = append(successDelay, duration)
|
|
||||||
fmt.Printf("来自 %s 的响应: 时间=%s\n", address, fmt.Sprintf("%.3fms", float64(duration)/float64(time.Millisecond)))
|
|
||||||
conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
if !infinite && attemptCount >= count {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if fast {
|
|
||||||
time.Sleep(150 * time.Millisecond)
|
|
||||||
} else {
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
done <- true
|
|
||||||
}()
|
|
||||||
|
|
||||||
// 等待信号或测试完成
|
|
||||||
select {
|
|
||||||
case <-sigChan:
|
|
||||||
fmt.Println("\n测试被用户中断")
|
|
||||||
stopped = true
|
|
||||||
case <-done:
|
|
||||||
}
|
|
||||||
|
|
||||||
// 打印统计结果
|
|
||||||
if !stopped {
|
|
||||||
fmt.Println()
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert duration to milliseconds
|
|
||||||
successDelayMs := make([]float64, len(successDelay))
|
|
||||||
for i, delay := range successDelay {
|
|
||||||
successDelayMs[i] = float64(delay) / float64(time.Millisecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
minDelay := 0.0
|
|
||||||
maxDelay := 0.0
|
|
||||||
avgDelay := 0.0
|
|
||||||
|
|
||||||
if successCount > 0 {
|
|
||||||
minDelay = float64_min(successDelayMs)
|
|
||||||
maxDelay = float64_max(successDelayMs)
|
|
||||||
avgDelay = float64_avg(successDelayMs)
|
|
||||||
}
|
|
||||||
fmt.Printf("测试完成,成功次数: %d/%d,最小=%.3fms,最大=%.3fms,平均=%.3fms\n", successCount, attemptCount, minDelay, maxDelay, avgDelay)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func filterIP(ips []net.IP, ipv6 bool) (string, error) {
|
|
||||||
if ipv6 {
|
|
||||||
for _, ip := range ips {
|
|
||||||
if ip.To16() != nil && ip.To4() == nil {
|
|
||||||
return ip.String(), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for _, ip := range ips {
|
|
||||||
if ip.To4() != nil && ip.To16() != nil {
|
|
||||||
return ip.String(), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", fmt.Errorf("找不到合适的IP地址")
|
|
||||||
}
|
|
||||||
|
|
||||||
func float64_min(array []float64) float64 {
|
|
||||||
min := array[0]
|
|
||||||
for _, value := range array {
|
|
||||||
if value < min {
|
|
||||||
min = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return min
|
|
||||||
}
|
|
||||||
|
|
||||||
func float64_max(array []float64) float64 {
|
|
||||||
max := array[0]
|
|
||||||
for _, value := range array {
|
|
||||||
if value > max {
|
|
||||||
max = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return max
|
|
||||||
}
|
|
||||||
|
|
||||||
func float64_avg(array []float64) float64 {
|
|
||||||
sum := 0.0
|
|
||||||
for _, value := range array {
|
|
||||||
sum += value
|
|
||||||
}
|
|
||||||
return sum / float64(len(array))
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user