package handler import ( "net" "strconv" "strings" "time" "github.com/gin-gonic/gin" ) func handleTCPing(c *gin.Context, url string, params map[string]interface{}) { // 获取seq参数 seq := "" if seqVal, ok := params["seq"].(string); ok { seq = seqVal } // 解析host:port格式,如果没有端口则默认80 var host string var portStr string var port int // 检查是否是IPv6格式(如 [::1]:8080) if strings.HasPrefix(url, "[") { // IPv6格式 - 使用 Index 而不是 LastIndex 来找到第一个闭合括号 closeBracket := strings.Index(url, "]") if closeBracket == -1 { c.JSON(200, gin.H{ "seq": seq, "type": "ceTCPing", "url": url, "error": "格式错误,IPv6地址格式应为 [host]:port", }) return } host = url[1:closeBracket] if closeBracket+1 < len(url) && url[closeBracket+1] == ':' { portStr = url[closeBracket+2:] // 如果端口部分为空,使用默认端口80(修复 Bug 1) if portStr == "" { portStr = "80" } } else { portStr = "80" // 默认端口 } } else { // 普通格式 host:port 或 host lastColonIndex := strings.LastIndex(url, ":") if lastColonIndex == -1 { // 没有冒号,使用默认端口80 host = url portStr = "80" } else { host = url[:lastColonIndex] portStr = url[lastColonIndex+1:] // 如果端口部分为空,使用默认端口80 if portStr == "" { portStr = "80" } } } var err error port, err = strconv.Atoi(portStr) if err != nil { c.JSON(200, gin.H{ "seq": seq, "type": "ceTCPing", "url": url, "error": "端口格式错误", }) return } // 解析hostname获取IP var primaryIP string ips, err := net.LookupIP(host) if err == nil && len(ips) > 0 { // 优先使用IPv4 for _, ip := range ips { if ip.To4() != nil { primaryIP = ip.String() break } } if primaryIP == "" && len(ips) > 0 { primaryIP = ips[0].String() } } // 执行多次TCP连接测试(默认10次,和PING一致) const testCount = 10 var latencies []float64 successCount := 0 failureCount := 0 for i := 0; i < testCount; i++ { start := time.Now() conn, err := net.DialTimeout("tcp", net.JoinHostPort(host, portStr), 5*time.Second) latency := time.Since(start).Milliseconds() if err == nil { // 成功:记录延迟 latencies = append(latencies, float64(latency)) successCount++ conn.Close() // 如果之前没有获取到IP,从连接中获取 if primaryIP == "" { if addr, ok := conn.RemoteAddr().(*net.TCPAddr); ok { primaryIP = addr.IP.String() } } } else { // 失败:记录为丢包 failureCount++ } } // 计算统计信息 packetsTotal := testCount packetsRecv := successCount packetsLosrat := float64(failureCount) / float64(testCount) * 100.0 var timeMin, timeMax, timeAvg float64 if len(latencies) > 0 { timeMin = latencies[0] timeMax = latencies[0] sum := 0.0 for _, lat := range latencies { if lat < timeMin { timeMin = lat } if lat > timeMax { timeMax = lat } sum += lat } timeAvg = sum / float64(len(latencies)) } else { // 全部失败 timeMin = -1 timeMax = -1 timeAvg = -1 } // 如果之前没有获取到IP,尝试从host解析 if primaryIP == "" { ips, err := net.LookupIP(host) if err == nil && len(ips) > 0 { for _, ip := range ips { if ip.To4() != nil { primaryIP = ip.String() break } } if primaryIP == "" && len(ips) > 0 { primaryIP = ips[0].String() } } } // 返回格式和PING一致 result := gin.H{ "seq": seq, "type": "ceTCPing", "url": url, "ip": primaryIP, "host": host, "port": port, "packets_total": strconv.Itoa(packetsTotal), "packets_recv": strconv.Itoa(packetsRecv), "packets_losrat": packetsLosrat, // float64类型,百分比值(如10.5表示10.5%) } // 时间字段:如果是-1(全部失败),返回字符串"-",否则返回float64 if timeMin < 0 { result["time_min"] = "-" result["time_max"] = "-" result["time_avg"] = "-" } else { result["time_min"] = timeMin result["time_max"] = timeMax result["time_avg"] = timeAvg } // 如果全部失败,添加error字段 if successCount == 0 { result["error"] = "所有TCP连接测试均失败" } c.JSON(200, result) }