7 Commits

Author SHA1 Message Date
e60a1ef082 release: v1.2.0 2025-11-24 17:03:29 +08:00
12b96089e7 update: Makefile 2025-11-24 17:03:21 +08:00
5636fac3e6 update: README.md 2025-11-24 16:59:18 +08:00
d229b22bd8 update: Makefile 2025-11-24 16:49:47 +08:00
5e97736367 update: params 2025-11-24 16:39:33 +08:00
283d6c26e8 feat: min and max 2024-04-22 16:58:48 +08:00
8228932df7 feat: dist 2024-04-22 16:05:16 +08:00
4 changed files with 125 additions and 77 deletions

4
.gitignore vendored
View File

@@ -1,4 +1,6 @@
bin/ bin/
*.exe *.exe
tcping tcping
dist/

View File

@@ -1,46 +1,39 @@
NAME=tcping NAME=tcping
BINDIR=bin BINDIR=bin
GOBUILD=CGO_ENABLED=0 go build -ldflags '-w -s -buildid=' GOBUILD=CGO_ENABLED=0 go build -trimpath -ldflags '-w -s -buildid='
VERSION=1.0.0 VERSION=1.2.0
# The -w and -s flags reduce binary sizes by excluding unnecessary symbols and debug info # The -w and -s flags reduce binary sizes by excluding unnecessary symbols and debug info
# The -buildid= flag makes builds reproducible # The -buildid= flag makes builds reproducible
all: linux darwin-amd64 darwin-arm64 win64 win32 all: releases
linux: linux-amd64:
GOARCH=amd64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ GOARCH=amd64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-arm64:
GOARCH=arm64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
darwin-amd64: darwin-amd64:
GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
darwin-arm64: darwin-arm64:
GOARCH=arm64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ GOARCH=arm64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
win64: windows-amd64:
GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
win32: windows-386:
GOARCH=386 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe GOARCH=386 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
releases: linux darwin-amd64 darwin-arm64 win64 win32 releases: linux-amd64 linux-arm64 darwin-amd64 darwin-arm64 windows-amd64 windows-386
chmod +x $(BINDIR)/$(NAME)-* chmod +x $(BINDIR)/$(NAME)-*
tar zcf $(BINDIR)/$(NAME)-linux-$(VERSION).tar.gz -C $(BINDIR) $(NAME)-linux tar zcf $(BINDIR)/$(NAME)-linux-amd64-$(VERSION).tar.gz -C $(BINDIR) $(NAME)-linux-amd64
tar zcf $(BINDIR)/$(NAME)-linux-arm64-$(VERSION).tar.gz -C $(BINDIR) $(NAME)-linux-arm64
tar zcf $(BINDIR)/$(NAME)-darwin-amd64-$(VERSION).tar.gz -C $(BINDIR) $(NAME)-darwin-amd64 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 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)-windows-386-$(VERSION).zip $(BINDIR)/$(NAME)-windows-386.exe
zip -j $(BINDIR)/$(NAME)-win64-$(VERSION).zip $(BINDIR)/$(NAME)-win64.exe zip -j $(BINDIR)/$(NAME)-windows-amd64-$(VERSION).zip $(BINDIR)/$(NAME)-windows-amd64.exe
rm -f $(BINDIR)/$(NAME)-darwin-amd64 $(BINDIR)/$(NAME)-darwin-arm64 $(BINDIR)/$(NAME)-linux $(BINDIR)/$(NAME)-win32.exe $(BINDIR)/$(NAME)-win64.exe rm -f $(BINDIR)/$(NAME)-darwin-amd64 $(BINDIR)/$(NAME)-darwin-arm64 $(BINDIR)/$(NAME)-linux-amd64 $(BINDIR)/$(NAME)-linux-arm64 $(BINDIR)/$(NAME)-windows-386.exe $(BINDIR)/$(NAME)-windows-amd64.exe
clean: clean:
rm $(BINDIR)/* rm $(BINDIR)/*-$(VERSION).tar.gz $(BINDIR)/*-$(VERSION).zip
# 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"

View File

@@ -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` 来启用快速模式,降低两次成功测试之间的间隔时间

104
main.go
View File

@@ -5,6 +5,7 @@ import (
"net" "net"
"os" "os"
"os/signal" "os/signal"
"strconv"
"syscall" "syscall"
"time" "time"
@@ -21,22 +22,20 @@ func main() {
fast bool fast bool
successCount int successCount int
successDelay time.Duration successDelay []time.Duration
attemptCount int attemptCount int
stopped bool stopped bool
) )
version := "1.0.0" version := "1.2.0"
// 设置命令行参数 showHelp := flag.BoolP("help", "h", false, "Show help")
showHelp := flag.BoolP("help", "h", false, "显示帮助信息") showVersion := flag.BoolP("version", "v", false, "Show version")
showVersion := flag.BoolP("version", "v", false, "显示版本信息") flag.IntVarP(&count, "count", "c", 4, "Number of probes, default 4")
flag.IntVarP(&port, "port", "p", 80, "端口默认为80") flag.DurationVarP(&timeoutDuration, "timeout", "s", 2*time.Second, "Timeout, default 2s")
flag.IntVarP(&count, "count", "c", 4, "测试次数默认为4次") flag.BoolVarP(&infinite, "infinite", "t", false, "Infinite probes")
flag.DurationVarP(&timeoutDuration, "timeout", "s", 2*time.Second, "超时时间默认为2秒") flag.BoolVarP(&ipv6, "ipv6", "6", false, "Use IPv6; requires domain name")
flag.BoolVarP(&infinite, "infinite", "t", false, "无限次测试") flag.BoolVarP(&fast, "fast", "f", false, "Fast mode; reduce delay between successful probes")
flag.BoolVarP(&ipv6, "ipv6", "6", false, "使用 IPv6需搭配域名使用")
flag.BoolVarP(&fast, "fast", "f", false, "快速模式,降低每次成功测试之间的间隔")
flag.Parse() flag.Parse()
@@ -56,13 +55,21 @@ func main() {
os.Exit(1) 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 { if port < 1 || port > 65535 {
fmt.Println("端口号必须在1-65535之间") fmt.Println("Port must be between 1 and 65535")
os.Exit(1) os.Exit(1)
} }
hostname := args[0]
// 设置信号捕获 // 设置信号捕获
stopped = false stopped = false
sigChan := make(chan os.Signal, 1) sigChan := make(chan os.Signal, 1)
@@ -74,7 +81,7 @@ func main() {
// 解析域名 // 解析域名
ips, err := net.LookupIP(hostname) ips, err := net.LookupIP(hostname)
if err != nil { if err != nil {
fmt.Printf("解析 %s 失败: %s\n", hostname, err) fmt.Printf("Failed to resolve %s: %s\n", hostname, err)
os.Exit(1) os.Exit(1)
} }
record := "A" record := "A"
@@ -83,10 +90,10 @@ func main() {
} }
ip, err = filterIP(ips, ipv6) ip, err = filterIP(ips, ipv6)
if err != nil { if err != nil {
fmt.Printf("找不到 %s 的 %s 记录\n", hostname, record) fmt.Printf("No %s record found for %s\n", record, hostname)
os.Exit(1) os.Exit(1)
} }
fmt.Printf("使用 %s %s 记录: %s\n", hostname, record, ip) fmt.Printf("Using %s %s record: %s\n", hostname, record, ip)
} else { } else {
ip = hostname ip = hostname
} }
@@ -107,11 +114,11 @@ func main() {
fmt.Printf("[%d] ", i+1) fmt.Printf("[%d] ", i+1)
if err != nil { if err != nil {
fmt.Printf("测试到 %s 的连接失败: %s\n", address, "连接超时") fmt.Printf("Connection to %s failed: %s\n", address, "timeout")
} else { } else {
successCount++ successCount++
successDelay += duration successDelay = append(successDelay, duration)
fmt.Printf("来自 %s 的响应: 时间=%s\n", address, fmt.Sprintf("%.3fms", float64(duration)/float64(time.Millisecond))) fmt.Printf("Reply from %s: time=%s\n", address, fmt.Sprintf("%.3fms", float64(duration)/float64(time.Millisecond)))
conn.Close() conn.Close()
} }
@@ -127,23 +134,36 @@ func main() {
done <- true done <- true
}() }()
// 等待信号或测试完成
select { select {
case <-sigChan: case <-sigChan:
fmt.Println("\n测试被用户中断") fmt.Println("\nTest interrupted by user")
stopped = true stopped = true
case <-done: case <-done:
} }
// 打印统计结果
if !stopped { if !stopped {
fmt.Println() fmt.Println()
} }
avgDelay := 0.0
if successCount > 0 { successDelayMs := make([]float64, len(successDelay))
avgDelay = float64(successDelay) / float64(time.Duration(successCount * int(time.Millisecond))) for i, delay := range successDelay {
successDelayMs[i] = float64(delay) / float64(time.Millisecond)
} }
fmt.Printf("测试完成,成功次数: %d/%d平均延迟%.3fms\n", successCount, attemptCount, avgDelay)
minDelay := 0.0
maxDelay := 0.0
avgDelay := 0.0
successRate := 0.0
if successCount > 0 {
minDelay = float64_min(successDelayMs)
maxDelay = float64_max(successDelayMs)
avgDelay = float64_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)
} }
func filterIP(ips []net.IP, ipv6 bool) (string, error) { func filterIP(ips []net.IP, ipv6 bool) (string, error) {
@@ -160,5 +180,33 @@ func filterIP(ips []net.IP, ipv6 bool) (string, error) {
} }
} }
} }
return "", fmt.Errorf("找不到合适的IP地址") return "", fmt.Errorf("no suitable IP address found")
}
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))
} }