import { useState, useEffect, useRef, type FC } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { toast } from "sonner"; import { Loader2 } from "lucide-react"; interface TCPingResult { seq: number; time: number; success: boolean; error?: string; } interface TCPingStats { sent: number; received: number; lost: number; min: number; max: number; avg: number; } const Tool: FC = () => { const [host, setHost] = useState(""); const [port, setPort] = useState("443"); const [running, setRunning] = useState(false); const [results, setResults] = useState([]); const [stats, setStats] = useState({ sent: 0, received: 0, lost: 0, min: 0, max: 0, avg: 0, }); const intervalRef = useRef(null); const seqRef = useRef(0); const resultsContainerRef = useRef(null); const tcping = async () => { if (!host.trim()) { toast.error("Please enter a hostname or IP"); return; } const seq = ++seqRef.current; const portNum = parseInt(port) || 443; let targetUrl = host.trim(); // 移除协议前缀 targetUrl = targetUrl.replace(/^https?:\/\//, ""); // 构建测试 URL const protocol = portNum === 443 ? "https" : "http"; const url = `${protocol}://${targetUrl}:${portNum}`; const startTime = performance.now(); try { // 使用 fetch 测试连接 await fetch(url, { method: "HEAD", mode: "no-cors", cache: "no-cache", }); const endTime = performance.now(); const time = endTime - startTime; const newResult: TCPingResult = { seq, time, success: true, }; setResults((prev) => [...prev, newResult]); updateStats(newResult); } catch (error: unknown) { const endTime = performance.now(); const time = endTime - startTime; const errorMessage = error instanceof Error ? error.message : "Connection failed"; const newResult: TCPingResult = { seq, time, success: false, error: errorMessage, }; setResults((prev) => [...prev, newResult]); updateStats(newResult); } }; const updateStats = (newResult: TCPingResult) => { setStats((prev) => { const sent = prev.sent + 1; const received = newResult.success ? prev.received + 1 : prev.received; const lost = sent - received; let min = prev.min; let max = prev.max; let avg = prev.avg; if (newResult.success) { if (received === 1) { min = newResult.time; max = newResult.time; avg = newResult.time; } else { min = Math.min(min, newResult.time); max = Math.max(max, newResult.time); avg = (prev.avg * (received - 1) + newResult.time) / received; } } return { sent, received, lost, min, max, avg }; }); }; const startTCPing = () => { if (!host.trim()) { toast.error("Please enter a hostname or IP"); return; } setRunning(true); setResults([]); setStats({ sent: 0, received: 0, lost: 0, min: 0, max: 0, avg: 0, }); seqRef.current = 0; // 立即执行第一次 tcping tcping(); // 然后每秒执行一次 intervalRef.current = window.setInterval(tcping, 1000); }; const stopTCPing = () => { setRunning(false); if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; } }; useEffect(() => { // 自动滚动到底部 if (resultsContainerRef.current) { resultsContainerRef.current.scrollTop = resultsContainerRef.current.scrollHeight; } }, [results]); useEffect(() => { // 清理定时器 return () => { if (intervalRef.current) { clearInterval(intervalRef.current); } }; }, []); const lossRate = stats.sent > 0 ? ((stats.lost / stats.sent) * 100).toFixed(1) : "0.0"; return (
setHost(e.target.value)} disabled={running} />
setPort(e.target.value)} disabled={running} min="1" max="65535" />
{!running ? ( ) : ( )}
{stats.sent > 0 && (
Statistics
Sent:
{stats.sent} times
Success:
{stats.received} times
Failed:
{stats.lost} times ({lossRate}%)
{stats.received > 0 && ( <>
Min Latency:
{stats.min.toFixed(2)} ms
Max Latency:
{stats.max.toFixed(2)} ms
Avg Latency:
{stats.avg.toFixed(2)} ms
Port Status:
Open
)}
)} {results.length > 0 && (
TCPing Results:
{results.map((result) => (
{result.success ? ( <> seq={result.seq} port={port} time={result.time.toFixed(2)}ms ) : ( <> seq={result.seq} port={port} Connection failed {result.error && ` (${result.error})`} )}
))} {running && (
Running...
)}
)}
); }; export default Tool;