简介:TCP&UDP测试工具是一款面向网络工程师、开发人员及网络编程学习者的传输层协议测试软件,可用于网络编程调试、故障排查与性能评估。工具免安装,解压即可运行,支持设定源地址、目标地址、端口、数据包大小与传输速率等参数,模拟真实网络环境,并覆盖压力测试、吞吐量测试、延迟测试与丢包测试等场景,帮助理解TCP可靠传输与UDP低延迟特性在应用中的差异。资源包共13个文件,约1.5MB,以exe可执行程序为主,辅以dll动态库、ini配置文件、htm与css界面文件及jpg截图等,结构紧凑、开箱即用。目前已有390人学习下载,适合需要快速验证网络设备性能、排查协议配置问题或开展网络安全测试的读者参考使用。
1. 一个端口通不通,为什么值得单独写个测试工具
做后端联调、网络排障、IoT 设备接入的人,几乎都遇到过这种场景:服务端日志显示监听正常,客户端却连不上;防火墙规则看着没问题,握手就是超时;换一台机器同样的代码又能跑通。这时候靠 ping 只能证明 IP 可达,靠 curl 只能测 HTTP,真正卡住你的往往是 TCP 连接能不能建立、UDP 包能不能收到回包。TCP&UDP 测试工具就是干这件事的:它绕开应用层协议,直接在传输层发连接请求或数据报,把「网络通不通、端口开不开、延迟多少、丢包多少」这几个问题一次性回答清楚。适合谁?后端开发、运维、嵌入式调试、做设备上云的工程师,以及任何需要判断「到底是我的代码问题还是网络问题」的人。下面这套方案,我用 Python 标准库实现,不依赖第三方包,单文件就能跑,既能当命令行工具,也能当库嵌进自己的诊断脚本。
2. 先想清楚测什么:TCP 与 UDP 的测试目标差异
2.1 TCP 测的是「连接生命周期」,UDP 测的是「包能不能到」
很多人把 TCP 和 UDP 测试混为一谈,写一个函数传个协议参数就完事,结果测出来的数据没法解释。TCP 是有连接的,一次测试至少包含三个阶段:三次握手建立连接、可选的数据收发、四次挥手断开。你真正关心的是握手耗时(connect 时间)和连接是否被拒绝或超时。连接被拒绝通常意味着端口没监听或防火墙 REJECT;连接超时通常意味着包被丢弃或路由不可达。这两个结论的排查方向完全不同。
UDP 是无连接的,sendto 成功只代表数据交给了本机协议栈,不代表对方收到。所以 UDP 测试必须设计回包机制:客户端发一个带序号的小包,服务端原样回发,客户端统计往返时间和丢包率。没有回包的 UDP 测试,测了等于没测。常见做法是在测试包里放一个递增的 sequence number 和时间戳,服务端只做 echo,客户端根据收到的序号判断丢了哪些包。
2.2 端口选择与权限边界
测试目标端口时,有几个硬约束要先记住。1024 以下的端口在 Linux/macOS 上需要 root 权限才能绑定,如果你只是测连通性,客户端不需要绑定特权端口,但如果你要起一个临时服务端做回环测试,就得注意。另一个坑是 ICMP 端口不可达:向一个没有监听的 UDP 端口发包,对方主机可能回一个 ICMP port unreachable,但很多防火墙会拦掉这个 ICMP,导致客户端一直等到超时。所以 UDP 测试的超时时间要设得比 TCP 短,通常 1 到 2 秒足够,否则一个不通的端口会拖慢整个测试流程。
2.3 超时参数怎么定:别用默认值
Python socket 的默认超时是 None,也就是阻塞到天荒地老。做测试工具必须显式设置。我的经验值:局域网内 TCP connect 超时设 1 秒,跨机房设 3 秒,公网设 5 秒。UDP 单包等待回包超时设 0.5 到 1 秒,重试 2 到 3 次。为什么 TCP 可以比 UDP 长?因为 TCP 握手有内核重传机制,SYN 重传间隔通常 1s、2s、4s,你设 5 秒能覆盖两次重传,拿到更准确的「超时」结论。UDP 没有重传,等太久只是浪费自己的时间。
import socket import time def tcp_connect_test(host, port, timeout=3.0): """ TCP 连接测试:返回 (是否成功, 耗时毫秒, 错误信息) timeout 单位秒,建议局域网 1s,公网 5s """ start = time.perf_counter() sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(timeout) # 必须设置,否则阻塞 try: sock.connect((host, port)) elapsed = (time.perf_counter() - start) * 1000 return True, round(elapsed, 2), None except socket.timeout: elapsed = (time.perf_counter() - start) * 1000 return False, round(elapsed, 2), "timeout" except ConnectionRefusedError: elapsed = (time.perf_counter() - start) * 1000 return False, round(elapsed, 2), "refused" except OSError as e: elapsed = (time.perf_counter() - start) * 1000 return False, round(elapsed, 2), str(e) finally: sock.close()这段代码的关键点:用perf_counter而不是time.time,因为前者是单调时钟,不受系统时间调整影响;finally里关闭 socket,避免文件描述符泄漏;区分 timeout 和 refused,因为这两个错误的排查方向完全不同。参数上,timeout不要用默认值,显式传入。如果你要批量测多个端口,建议把 socket 创建放在循环内,不要复用,避免状态污染。
3. 把 TCP 和 UDP 测试写成一个能复用的工具
3.1 UDP echo 测试:没有回包就等于没测
UDP 测试的核心是构造一个可识别的探测包,并等待回包。下面这个实现发送一个包含序号和时间戳的 JSON 包,服务端原样返回,客户端计算 RTT 和丢包。
import socket import time import json def udp_echo_test(host, port, count=5, timeout=1.0, interval=0.2): """ UDP 回包测试:发送 count 个包,统计 RTT 和丢包 timeout: 单包等待回包时间,建议 0.5~1s interval: 包间隔,避免瞬时拥塞 """ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.settimeout(timeout) results = [] for seq in range(count): payload = json.dumps({ "seq": seq, "ts": time.time() }).encode() send_time = time.perf_counter() try: sock.sendto(payload, (host, port)) data, _ = sock.recvfrom(1024) rtt = (time.perf_counter() - send_time) * 1000 results.append({"seq": seq, "rtt": round(rtt, 2), "ok": True}) except socket.timeout: results.append({"seq": seq, "rtt": None, "ok": False}) time.sleep(interval) sock.close() lost = sum(1 for r in results if not r["ok"]) rtts = [r["rtt"] for r in results if r["ok"]] avg_rtt = round(sum(rtts) / len(rtts), 2) if rtts else None return { "sent": count, "lost": lost, "loss_rate": round(lost / count * 100, 1), "avg_rtt_ms": avg_rtt, "detail": results }逻辑说明:每个包带 seq,服务端 echo 回来时客户端能确认是哪个包的回包,避免乱序导致误判。interval参数很重要,如果你连续发 100 个包不间隔,中间网络设备可能因为瞬时队列溢出丢包,测出来的丢包率不能反映真实链路质量。一般设 0.1 到 0.5 秒。timeout设 1 秒是折中,设太长测试慢,设太短会把网络抖动误判为丢包。
3.2 服务端 echo 实现:临时起一个用于回环验证
很多时候你需要一个对端来配合测试,比如验证防火墙规则、验证客户端代码。下面这个 UDP echo 服务端可以临时跑在目标机器上。
import socket def udp_echo_server(bind_host="0.0.0.0", bind_port=9999, buffer_size=1024): """ 极简 UDP echo 服务端,收到什么回什么 仅用于测试,不要暴露在公网 """ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind((bind_host, bind_port)) print(f"UDP echo server listening on {bind_host}:{bind_port}") while True: data, addr = sock.recvfrom(buffer_size) sock.sendto(data, addr) # 原样回发参数说明:bind_host设0.0.0.0表示监听所有网卡,测试时如果只想本机验证可以设127.0.0.1。buffer_size要大于客户端发送的最大包,否则截断。这个服务端没有做任何鉴权,只适合内网临时测试,测完就关。TCP 版本同理,把 socket 类型换成SOCK_STREAM,加listen和accept即可,但 TCP 测试连通性其实不需要服务端回显,connect 成功本身就说明问题。
3.3 批量测试与结果输出:把数据变成可读报告
单次测试只能回答「通不通」,批量测试才能看出规律。比如你怀疑某个网段有间歇性丢包,就需要连续测 100 次,看丢包是否集中在特定时间点。
def batch_tcp_test(host, ports, timeout=2.0): """ 批量 TCP 端口测试,返回每个端口的连通状态 ports: 端口列表,如 [22, 80, 443, 3306] """ report = [] for port in ports: ok, elapsed, err = tcp_connect_test(host, port, timeout) report.append({ "port": port, "open": ok, "connect_ms": elapsed, "error": err }) return report # 输出示例 if __name__ == "__main__": result = batch_tcp_test("192.168.1.10", [22, 80, 443, 9999]) for item in result: status = "OPEN" if item["open"] else f"CLOSED({item['error']})" print(f"port {item['port']:>5} {status:<20} {item['connect_ms']}ms")输出里把 connect 耗时也打出来,因为有时候端口是通的,但握手耗时 800ms,说明网络质量有问题或者对方 accept 队列快满了。这个数据比单纯的 open/closed 有价值得多。批量测试时建议加一个总超时控制,避免某个端口卡住导致整个脚本挂起。
4. 避坑与排查:那些让你怀疑人生的假象
4.1 坑一:本机测试通,跨机测试不通
现象:在目标机器上telnet 127.0.0.1 3306能通,从另一台机器连就超时。原因:服务只监听了127.0.0.1而不是0.0.0.0,或者防火墙只放行了 lo 接口。解决:检查服务配置里的 bind address,用ss -tlnp看监听地址是127.0.0.1:3306还是0.0.0.0:3306。如果是前者,改服务配置;如果是后者,查防火墙规则链。
4.2 坑二:UDP 测试显示 100% 丢包,但服务其实正常
现象:UDP echo 测试丢包率 100%,但业务日志显示服务收到了请求。原因:服务端收到包后没有回发,或者回发包的源地址和客户端期望的不一致(多网卡场景)。解决:先用 tcpdump 在服务端抓包,确认包到了;再在客户端抓包,确认回包有没有回来。如果回包回来了但客户端没收到,检查是不是绑定了错误的网卡。UDP 测试必须确认对端有 echo 逻辑,否则测的是单向可达性,不是往返质量。
4.3 坑三:TCP connect 超时时间设太短,把慢连接误判为不通
现象:跨机房测试,connect 耗时 2.8 秒,超时设 3 秒,偶尔成功偶尔失败。原因:网络抖动导致握手时间波动,超时阈值卡在边界上。解决:超时时间至少设为实测 P99 耗时的 2 倍。先跑 20 次不限超时的测试,看最大耗时,再定阈值。不要拍脑袋设 1 秒。
4.4 坑四:批量测试把文件描述符耗尽
现象:测到第 900 多个端口时脚本报Too many open files。原因:socket 没有及时关闭,或者用了with但异常路径没走到。解决:确保每个 socket 在finally里 close,或者用上下文管理器。批量测试时加time.sleep(0.01)给内核一点回收时间。Linux 默认单进程 fd 上限 1024,测大量端口时要么分批,要么调 ulimit。
4.5 坑五:UDP 包大小超过 MTU 被分片丢弃
现象:小包能通,发 2KB 的包就丢。原因:UDP 包超过路径 MTU 会被分片,中间设备可能丢弃分片包。解决:测试包控制在 1400 字节以内,避开分片。如果必须发大包,先测路径 MTU,或者改用 TCP。这个坑在跨公网测试时特别常见,因为中间链路 MTU 可能只有 1400 甚至更小。
5. 进阶:把测试工具变成持续诊断能力
5.1 用统计代替单次判断
单次测试的结论很脆弱,网络抖动、瞬时拥塞都会影响结果。我习惯对关键链路做持续采样:每 30 秒测一次 TCP connect 耗时和 UDP 丢包率,存到本地 SQLite,跑一天后看趋势。如果 connect 耗时在特定时间段规律性升高,基本能定位到网络高峰或对端负载问题。下面是一个极简的采样记录结构。
import sqlite3 import time def init_db(path="net_probe.db"): conn = sqlite3.connect(path) conn.execute(""" CREATE TABLE IF NOT EXISTS probe ( ts REAL, host TEXT, port INTEGER, proto TEXT, ok INTEGER, rtt_ms REAL, err TEXT ) """) conn.commit() return conn def record(conn, host, port, proto, ok, rtt_ms, err=None): conn.execute( "INSERT INTO probe VALUES (?, ?, ?, ?, ?, ?, ?)", (time.time(), host, port, proto, int(ok), rtt_ms, err) ) conn.commit()这个表结构够用:时间戳、目标、协议、结果、耗时、错误类型。查询时按小时聚合,算 P50/P95/P99,比看单次结果靠谱得多。SQLite 单文件,不需要额外服务,适合在跳板机或本地长期跑。
5.2 用并发提升批量测试效率
测 100 个 IP 的同一个端口,串行跑太慢。用concurrent.futures的线程池,把超时设短一点,并发数控制在 50 以内,避免把本机 fd 打满。
from concurrent.futures import ThreadPoolExecutor, as_completed def parallel_tcp_scan(hosts, port, timeout=1.0, workers=50): """ 并发测试多个主机的同一个端口 workers 不要超过 100,否则本机 fd 和内核连接跟踪表压力大 """ results = {} with ThreadPoolExecutor(max_workers=workers) as pool: future_map = { pool.submit(tcp_connect_test, h, port, timeout): h for h in hosts } for fut in as_completed(future_map): host = future_map[fut] ok, elapsed, err = fut.result() results[host] = {"open": ok, "ms": elapsed, "err": err} return results参数上,workers设 50 是保守值,Linux 默认的net.ipv4.ip_local_port_range是 32768 到 60999,大约 28000 个可用端口,50 并发远不会耗尽。但如果你同时测多个端口,要乘一下。timeout设 1 秒,因为并发场景下慢连接会拖累整体进度,宁可误判几个,也不要让整个扫描卡住。
5.3 一个我常犯的错误:把工具当答案
早期我做排障,跑完测试看到「端口 open」就下结论说网络没问题,结果业务还是不通。后来才明白,TCP 通只代表传输层通,应用层协议不兼容、TLS 握手失败、认证超时,这些测试工具都测不出来。所以现在的习惯是:测试工具只用来排除传输层问题,一旦确认 TCP/UDP 层正常,立刻把排查方向转到应用层日志和抓包。这个边界感,比工具本身更重要。希望帮到你。
本文还有配套的精品资源,点击获取