全自动跑(推荐):

1# 扫描 → TCP压测 → 自动找HTTP端口上传200GB
2python3 autostress.py 192.168.1.100 --http-size 200

只做 HTTP 大包上传:

1# 直接指定端口,跳过扫描
2python3 autostress.py 192.168.1.100 --http-only --http-port 80 --http-size 200

调大并发和包体:

1python3 autostress.py 192.168.1.100 --http-only --http-port 80 \
2  --http-size 200 --http-concurrency 20 --chunk-mb 64

三个阶段说明:

阶段内容需要对方配合?
① 端口扫描发现开放端口❌ 不需要
② TCP压测QPS / 延迟 / 成功率❌ 不需要
③ HTTP大包上传吞吐量 / 断连 / 稳定性❌ 不需要(对方有HTTP服务即可)

HTTP 大包上传的原理:向目标的 HTTP 服务 POST 大块数据,服务器返回 4xx/5xx 也没关系——数据已经从你这边发出去了,吞吐量照常统计。

1nano /root/autostress.py
  1#!/usr/bin/env python3
  2"""
  3自动扫描 + 压测工具 v2.0
  41. 扫描目标 IP 的开放端口
  52. TCP 连接压测(测 QPS / 延迟 / 稳定性)
  63. HTTP 大包上传压测(测吞吐量,无需目标配合接收端)
  7"""
  8 
  9import socket
 10import time
 11import threading
 12import argparse
 13import sys
 14import os
 15import statistics
 16import ssl
 17import urllib.request
 18import urllib.error
 19from collections import Counter
 20from concurrent.futures import ThreadPoolExecutor, as_completed
 21from dataclasses import dataclass, field
 22from typing import List, Tuple, Optional
 23 
 24# ──────────────────────────────────────────────
 25# 颜色
 26# ──────────────────────────────────────────────
 27R      = "\033[0m"
 28BOLD   = "\033[1m"
 29GREEN  = "\033[92m"
 30RED    = "\033[91m"
 31YELLOW = "\033[93m"
 32CYAN   = "\033[96m"
 33GRAY   = "\033[90m"
 34BLUE   = "\033[94m"
 35 
 36def c(text, *codes): return "".join(codes) + str(text) + R
 37 
 38# ──────────────────────────────────────────────
 39# 常量
 40# ──────────────────────────────────────────────
 41COMMON_PORTS = {
 42    21: "FTP", 22: "SSH", 23: "Telnet", 25: "SMTP",
 43    53: "DNS", 80: "HTTP", 110: "POP3", 143: "IMAP",
 44    443: "HTTPS", 445: "SMB", 3306: "MySQL", 3389: "RDP",
 45    5432: "PostgreSQL", 5672: "RabbitMQ", 6379: "Redis",
 46    8080: "HTTP-Alt", 8443: "HTTPS-Alt", 9200: "Elasticsearch",
 47    27017: "MongoDB", 11211: "Memcached",
 48}
 49HTTP_PORTS = {80, 443, 8080, 8443, 8000, 8008, 8081, 8088, 8888, 9000, 9090}
 50GB = 1024 ** 3
 51 
 52# ──────────────────────────────────────────────
 53# 工具函数
 54# ──────────────────────────────────────────────
 55def make_payload(size: int) -> bytes:
 56    """动态生成随机数据块,不占磁盘"""
 57    base = os.urandom(min(size, 65536))
 58    repeat = size // len(base)
 59    remainder = size % len(base)
 60    return base * repeat + base[:remainder]
 61 
 62def fmt_size(b: float) -> str:
 63    if b >= GB:        return f"{b/GB:.2f} GB"
 64    if b >= 1024**2:   return f"{b/1024**2:.2f} MB"
 65    if b >= 1024:      return f"{b/1024:.2f} KB"
 66    return f"{b:.0f} B"
 67 
 68def fmt_speed(bps: float) -> str:
 69    mbps = bps / 1024 / 1024
 70    if mbps >= 1000: return f"{mbps/1024:.2f} GB/s"
 71    return f"{mbps:.2f} MB/s"
 72 
 73# ──────────────────────────────────────────────
 74# 阶段 1:端口扫描
 75# ──────────────────────────────────────────────
 76def scan_port(host: str, port: int, timeout: float) -> Tuple[int, bool, float]:
 77    start = time.perf_counter()
 78    try:
 79        with socket.create_connection((host, port), timeout=timeout):
 80            return port, True, (time.perf_counter() - start) * 1000
 81    except Exception:
 82        return port, False, 0.0
 83 
 84 
 85def scan_ports(host: str, ports: List[int], timeout: float = 1.0, workers: int = 800) -> List[Tuple[int, float]]:
 86    open_ports = []
 87    total = len(ports)
 88    done  = 0
 89    lock  = threading.Lock()
 90 
 91    print(c(f"\n  🔍 正在扫描 {host}{total} 个端口...", CYAN))
 92 
 93    with ThreadPoolExecutor(max_workers=workers) as ex:
 94        futures = {ex.submit(scan_port, host, p, timeout): p for p in ports}
 95        for future in as_completed(futures):
 96            port, is_open, lat = future.result()
 97            with lock:
 98                done += 1
 99                if is_open:
100                    open_ports.append((port, lat))
101                bar_len = 40
102                filled  = int(bar_len * done / total)
103                bar = c("█" * filled, GREEN) + c("░" * (bar_len - filled), GRAY)
104                print(f"\r  [{bar}] {done}/{total}", end="", flush=True)
105 
106    print()
107    return sorted(open_ports)
108 
109 
110def print_scan_result(host: str, open_ports: List[Tuple[int, float]]):
111    sep = c("─" * 60, GRAY)
112    print()
113    print(sep)
114    print(c(f"  🗺  端口扫描结果  →  {host}", BOLD, CYAN))
115    print(sep)
116    if not open_ports:
117        print(c("  未发现任何开放端口", RED))
118    else:
119        print(f"  {'端口':<8} {'服务':<16} {'连接延迟':<12} {'类型'}")
120        print(c("  " + "·" * 46, GRAY))
121        for port, lat in open_ports:
122            svc       = COMMON_PORTS.get(port, "Unknown")
123            lat_color = GREEN if lat < 10 else YELLOW if lat < 50 else RED
124            ptype     = c("HTTP ✓", CYAN) if port in HTTP_PORTS else c("TCP", GRAY)
125            print(f"  {c(str(port), CYAN):<18} {svc:<16} {c(f'{lat:.1f}ms', lat_color):<20} {ptype}")
126    print(sep)
127 
128 
129# ──────────────────────────────────────────────
130# 阶段 2:TCP 连接压测
131# ──────────────────────────────────────────────
132@dataclass
133class BenchResult:
134    port: int
135    service: str
136    total: int
137    success: int = 0
138    latencies: List[float] = field(default_factory=list)
139    errors: List[str] = field(default_factory=list)
140    duration: float = 0.0
141 
142    @property
143    def fail(self):         return self.total - self.success
144    @property
145    def success_rate(self): return self.success / self.total * 100 if self.total else 0
146    @property
147    def avg_lat(self):      return statistics.mean(self.latencies) if self.latencies else 0
148    @property
149    def min_lat(self):      return min(self.latencies) if self.latencies else 0
150    @property
151    def max_lat(self):      return max(self.latencies) if self.latencies else 0
152    @property
153    def p95_lat(self):
154        if not self.latencies: return 0
155        s = sorted(self.latencies)
156        return s[int(len(s) * 0.95)]
157    @property
158    def qps(self):          return self.success / self.duration if self.duration else 0
159 
160 
161def _tcp_task(host, port, timeout):
162    start = time.perf_counter()
163    try:
164        with socket.create_connection((host, port), timeout=timeout):
165            return True, (time.perf_counter() - start) * 1000, None
166    except Exception as e:
167        return False, (time.perf_counter() - start) * 1000, type(e).__name__
168 
169 
170def bench_port(host, port, requests, concurrency, timeout) -> BenchResult:
171    service = COMMON_PORTS.get(port, "Unknown")
172    result  = BenchResult(port=port, service=service, total=requests)
173    lock    = threading.Lock()
174    done    = [0]
175 
176    print(c(f"\n  ⚡ TCP压测  端口 {port} ({service})  {requests}次 / {concurrency}并发", BOLD))
177 
178    t0 = time.perf_counter()
179    with ThreadPoolExecutor(max_workers=concurrency) as ex:
180        futures = [ex.submit(_tcp_task, host, port, timeout) for _ in range(requests)]
181        for future in as_completed(futures):
182            ok, lat, err = future.result()
183            with lock:
184                done[0] += 1
185                if ok:
186                    result.success += 1
187                    result.latencies.append(lat)
188                elif err:
189                    result.errors.append(err)
190                bar_len = 35
191                filled  = int(bar_len * done[0] / requests)
192                bar = c("█" * filled, CYAN) + c("░" * (bar_len - filled), GRAY)
193                pct = done[0] / requests * 100
194                print(f"\r    [{bar}] {pct:5.1f}%  ✓{result.success}{result.fail}", end="", flush=True)
195 
196    result.duration = time.perf_counter() - t0
197    print()
198    return result
199 
200 
201def print_tcp_bench_report(results: List[BenchResult], host: str):
202    sep = c("─" * 60, GRAY)
203    print()
204    print(sep)
205    print(c(f"  📊 TCP 压测报告  →  {host}", BOLD, CYAN))
206    print(sep)
207 
208    for r in results:
209        sr_color = GREEN if r.success_rate >= 95 else YELLOW if r.success_rate >= 70 else RED
210        print()
211        print(c(f"  端口 {r.port}  ({r.service})", BOLD))
212        print(f"  {'成功率':<14} {c(f'{r.success_rate:.1f}%', sr_color)}  ({r.success}/{r.total})")
213        print(f"  {'QPS':<14} {c(f'{r.qps:.1f}', YELLOW)}")
214        print(f"  {'总耗时':<14} {r.duration:.2f}s")
215        if r.latencies:
216            print(f"  {'延迟(ms)':<14} min={r.min_lat:.1f}  avg={c(f'{r.avg_lat:.1f}', CYAN)}  p95={r.p95_lat:.1f}  max={r.max_lat:.1f}")
217        if r.errors:
218            top  = Counter(r.errors).most_common(3)
219            errs = "  ".join(f"{n}x {e}" for e, n in top)
220            print(c(f"  错误: {errs}", RED))
221        grade = (c("✅ 优秀", GREEN) if r.success_rate >= 99 else
222                 c("🟡 良好", YELLOW) if r.success_rate >= 90 else
223                 c("⚠️  较差", YELLOW) if r.success_rate >= 50 else
224                 c("❌ 不可用", RED))
225        print(f"  {'评级':<14} {grade}")
226 
227    print()
228    print(sep)
229 
230 
231# ──────────────────────────────────────────────
232# 阶段 3:HTTP 大包上传压测
233# ──────────────────────────────────────────────
234@dataclass
235class HttpBulkResult:
236    port: int
237    url: str
238    target_bytes: int
239    sent_bytes: int = 0
240    success_reqs: int = 0
241    fail_reqs: int = 0
242    total_reqs: int = 0
243    latencies: List[float] = field(default_factory=list)
244    throughputs: List[float] = field(default_factory=list)
245    errors: List[str] = field(default_factory=list)
246    duration: float = 0.0
247    disconnects: int = 0
248 
249    @property
250    def avg_throughput(self): return statistics.mean(self.throughputs) if self.throughputs else 0
251    @property
252    def peak_throughput(self): return max(self.throughputs) if self.throughputs else 0
253    @property
254    def success_rate(self): return self.success_reqs / self.total_reqs * 100 if self.total_reqs else 0
255    @property
256    def overall_mbps(self): return (self.sent_bytes / self.duration / 1024 / 1024) if self.duration else 0
257 
258 
259def _http_upload_task(url: str, payload: bytes, timeout: int, ssl_ctx) -> Tuple[bool, float, float, str]:
260    """
261    单次 HTTP POST 上传。
262    服务器返回 4xx/5xx 也算"数据已发出",统计吞吐。
263    """
264    start = time.perf_counter()
265    try:
266        req = urllib.request.Request(url, data=payload, method="POST")
267        req.add_header("Content-Type", "application/octet-stream")
268        req.add_header("User-Agent", "BulkStressTest/2.0")
269        req.add_header("Content-Length", str(len(payload)))
270        try:
271            with urllib.request.urlopen(req, timeout=timeout, context=ssl_ctx) as resp:
272                resp.read(1024)
273        except urllib.error.HTTPError:
274            pass  # 4xx/5xx:数据已发出,继续统计
275 
276        elapsed = time.perf_counter() - start
277        mbps = len(payload) / elapsed / 1024 / 1024
278        return True, elapsed * 1000, mbps, ""
279    except Exception as e:
280        elapsed_ms = (time.perf_counter() - start) * 1000
281        return False, elapsed_ms, 0.0, str(e)[:100]
282 
283 
284def http_bulk_stress(host, port, total_gb, chunk_mb, concurrency, timeout) -> HttpBulkResult:
285    scheme = "https" if port in (443, 8443) else "http"
286    url    = f"{scheme}://{host}:{port}/"
287 
288    ssl_ctx = ssl.create_default_context()
289    ssl_ctx.check_hostname = False
290    ssl_ctx.verify_mode    = ssl.CERT_NONE
291 
292    target_bytes = int(total_gb * GB)
293    chunk_bytes  = chunk_mb * 1024 * 1024
294    total_reqs   = max(1, int(target_bytes / chunk_bytes))
295 
296    result = HttpBulkResult(port=port, url=url, target_bytes=target_bytes, total_reqs=total_reqs)
297    lock   = threading.Lock()
298    done   = [0]
299 
300    print(c(f"\n  📤 HTTP 大包上传压测", BOLD))
301    print(c(f"     URL:    {url}", GRAY))
302    print(c(f"     目标:   {fmt_size(target_bytes)}  ({total_reqs} 个请求 × {chunk_mb}MB)", GRAY))
303    print(c(f"     并发:   {concurrency}  超时: {timeout}s", GRAY))
304    print()
305 
306    # 预生成 payload(所有线程共享同一块,不重复分配)
307    print(c("  ⏳ 生成数据包...", GRAY), end=" ", flush=True)
308    payload = make_payload(chunk_bytes)
309    print(c(f"完成 ({fmt_size(len(payload))})", GREEN))
310    print()
311 
312    def reporter():
313        while done[0] < total_reqs and result.duration == 0:
314            time.sleep(1.5)
315            with lock:
316                sent = result.sent_bytes
317                cur  = done[0]
318                fail = result.fail_reqs
319            elapsed = time.perf_counter() - t0
320            if elapsed <= 0 or sent <= 0:
321                continue
322            spd     = sent / elapsed / 1024 / 1024
323            pct     = sent / target_bytes * 100
324            eta_s   = int((target_bytes - sent) / (sent / elapsed))
325            eta_str = f"{eta_s//3600}h{(eta_s%3600)//60}m" if eta_s > 3600 else \
326                      f"{eta_s//60}m{eta_s%60:02d}s" if eta_s > 60 else f"{eta_s}s"
327            bar_len = 32
328            filled  = int(bar_len * min(pct, 100) / 100)
329            bar = c("█" * filled, GREEN) + c("░" * (bar_len - filled), GRAY)
330            print(
331                f"\r  [{bar}] {pct:5.1f}%  "
332                f"{c(fmt_size(sent), CYAN)}/{fmt_size(target_bytes)}  "
333                f"速度:{c(f'{spd:.1f}MB/s', YELLOW)}  "
334                f"ETA:{eta_str}  "
335                f"失败:{c(str(fail), RED if fail else GRAY)}",
336                end="", flush=True
337            )
338 
339    t0 = time.perf_counter()
340    rep = threading.Thread(target=reporter, daemon=True)
341    rep.start()
342 
343    with ThreadPoolExecutor(max_workers=concurrency) as ex:
344        futures = [ex.submit(_http_upload_task, url, payload, timeout, ssl_ctx) for _ in range(total_reqs)]
345        for future in as_completed(futures):
346            ok, lat_ms, mbps, err = future.result()
347            with lock:
348                done[0] += 1
349                if ok:
350                    result.success_reqs += 1
351                    result.sent_bytes   += chunk_bytes
352                    result.latencies.append(lat_ms)
353                    result.throughputs.append(mbps)
354                else:
355                    result.fail_reqs += 1
356                    result.errors.append(err)
357                    err_l = err.lower()
358                    if any(k in err_l for k in ("reset", "broken", "connect", "refused", "timed")):
359                        result.disconnects += 1
360 
361    result.duration = time.perf_counter() - t0
362    print()
363    return result
364 
365 
366def print_http_bulk_report(r: HttpBulkResult):
367    sep = c("─" * 60, GRAY)
368    print()
369    print(sep)
370    print(c(f"  📦 HTTP 大包上传报告  →  {r.url}", BOLD, CYAN))
371    print(sep)
372    print()
373 
374    sent_pct = r.sent_bytes / r.target_bytes * 100 if r.target_bytes else 0
375    print(f"  {'发送总量':<16} {c(fmt_size(r.sent_bytes), GREEN)}  ({sent_pct:.1f}% of {fmt_size(r.target_bytes)})")
376    print(f"  {'总耗时':<16} {r.duration:.2f}s")
377    print(f"  {'整体吞吐量':<16} {c(fmt_speed(r.sent_bytes / r.duration) if r.duration else '0', YELLOW)}")
378    print(f"  {'请求成功率':<16} {c(f'{r.success_rate:.1f}%', GREEN if r.success_rate >= 95 else YELLOW)}  ({r.success_reqs}/{r.total_reqs})")
379    print()
380 
381    if r.throughputs:
382        avg_tp = statistics.mean(r.throughputs)
383        peak   = max(r.throughputs)
384        med    = statistics.median(r.throughputs)
385        low    = min(r.throughputs)
386        print(f"  {'单请求吞吐(MB/s)':<16}")
387        print(f"    {'平均':<12} {c(f'{avg_tp:.2f}', CYAN)}")
388        print(f"    {'中位数':<12} {med:.2f}")
389        print(f"    {'峰值':<12} {c(f'{peak:.2f}', GREEN)}")
390        print(f"    {'最低':<12} {low:.2f}")
391        print()
392 
393    if r.latencies:
394        sl = sorted(r.latencies)
395        print(f"  {'请求延迟(ms)':<16}")
396        print(f"    {'平均':<12} {statistics.mean(r.latencies):.0f}")
397        print(f"    {'P95':<12} {sl[int(len(sl)*0.95)]:.0f}")
398        print(f"    {'P99':<12} {sl[int(len(sl)*0.99)]:.0f}")
399        print(f"    {'最大':<12} {max(r.latencies):.0f}")
400        print()
401 
402    print(f"  {'断连/重置':<16} {c(str(r.disconnects), RED if r.disconnects else GREEN)}")
403 
404    if r.errors:
405        print()
406        print(c("  错误摘要 (TOP 5):", YELLOW))
407        for err, cnt in Counter(r.errors).most_common(5):
408            print(f"    [{cnt}x] {err}")
409 
410    print()
411    print(c("  稳定性评估:", BOLD))
412    if r.success_rate >= 99 and r.disconnects == 0:
413        print(c("  ✅ 优秀 — 全程无断连,吞吐稳定", GREEN))
414    elif r.success_rate >= 90:
415        print(c(f"  🟡 良好 — 成功率 {r.success_rate:.1f}%,轻微波动", YELLOW))
416    elif r.success_rate >= 60:
417        print(c(f"  ⚠️  较差 — 成功率 {r.success_rate:.1f}%,服务器可能限流或过载", YELLOW))
418    else:
419        print(c(f"  ❌ 不可用 — 大量请求失败({r.fail_reqs}/{r.total_reqs}),检查目标服务", RED))
420 
421    print()
422    print(sep)
423    print()
424 
425 
426# ──────────────────────────────────────────────
427# CLI
428# ──────────────────────────────────────────────
429def parse_args():
430    parser = argparse.ArgumentParser(
431        description="自动扫描 + TCP压测 + HTTP大包上传压测(无需目标配合)",
432        formatter_class=argparse.RawDescriptionHelpFormatter,
433        epilog="""
434示例:
435  # 全自动:扫描 → TCP压测 → HTTP大包上传(默认1GB)
436  python3 autostress.py 192.168.1.100
437 
438  # 发送 200GB HTTP 大包(自动找HTTP端口)
439  python3 autostress.py 192.168.1.100 --http-size 200
440 
441  # 只做 HTTP 大包上传,指定端口
442  python3 autostress.py 192.168.1.100 --http-only --http-port 8080 --http-size 10
443 
444  # 大并发大包:20并发 × 64MB 单包 × 200GB
445  python3 autostress.py 192.168.1.100 --http-only --http-port 80 \\
446      --http-size 200 --http-concurrency 20 --chunk-mb 64
447 
448  # 只扫描端口,不压测
449  python3 autostress.py 192.168.1.100 --scan-only
450 
451  # 全端口扫描
452  python3 autostress.py 192.168.1.100 --full-scan
453        """
454    )
455    parser.add_argument("host", help="目标 IP 或域名")
456 
457    scan = parser.add_argument_group("扫描参数")
458    scan.add_argument("--full-scan", action="store_true", help="扫描全部 65535 端口")
459    scan.add_argument("--ports", type=str, default=None, help="手动指定端口,如 80,443 或 1-1024")
460    scan.add_argument("--scan-only", action="store_true", help="只扫描端口,不压测")
461    scan.add_argument("--scan-timeout", type=float, default=0.8, help="扫描超时(默认0.8s)")
462    scan.add_argument("--scan-workers", type=int, default=800, help="扫描并发数(默认800)")
463 
464    tcp = parser.add_argument_group("TCP压测参数")
465    tcp.add_argument("-n", "--requests", type=int, default=200, help="每端口TCP请求数(默认200)")
466    tcp.add_argument("-c", "--concurrency", type=int, default=20, help="TCP并发数(默认20)")
467    tcp.add_argument("--bench-timeout", type=int, default=5, help="TCP连接超时(默认5s)")
468    tcp.add_argument("--max-bench-ports", type=int, default=3, help="最多TCP压测端口数(默认3)")
469 
470    http = parser.add_argument_group("HTTP大包上传参数")
471    http.add_argument("--http-only", action="store_true",
472                      help="跳过扫描和TCP压测,直接做HTTP大包上传")
473    http.add_argument("--http-port", type=int, default=None,
474                      help="HTTP上传目标端口(默认自动检测)")
475    http.add_argument("--http-size", type=float, default=1.0,
476                      help="上传总量 GB(默认1GB,大测试用200)")
477    http.add_argument("--chunk-mb", type=int, default=32,
478                      help="单次上传包大小 MB(默认32MB)")
479    http.add_argument("--http-concurrency", type=int, default=10,
480                      help="HTTP上传并发连接数(默认10)")
481    http.add_argument("--http-timeout", type=int, default=60,
482                      help="HTTP请求超时(默认60s)")
483    http.add_argument("--no-http", action="store_true",
484                      help="跳过HTTP大包上传测试")
485 
486    return parser.parse_args()
487 
488 
489def resolve_ports(args) -> List[int]:
490    if args.ports:
491        ports = []
492        for seg in args.ports.split(","):
493            seg = seg.strip()
494            if "-" in seg:
495                a, b = seg.split("-")
496                ports.extend(range(int(a), int(b) + 1))
497            else:
498                ports.append(int(seg))
499        return ports
500    elif args.full_scan:
501        return list(range(1, 65536))
502    else:
503        return sorted(set(list(COMMON_PORTS.keys()) + [
504            8000, 8008, 8081, 8082, 8083, 8084, 8085, 8086, 8087, 8088, 8089,
505            8090, 8888, 9000, 9090, 9091, 9092, 9100, 9300, 9999,
506            4000, 4001, 4040, 4200, 4443, 4500,
507            2181, 2375, 2376, 2379, 2380,
508            5000, 5001, 5601, 5900,
509            6000, 6001, 6443, 6800,
510            7000, 7001, 7002, 7070, 7077, 7474,
511            10000, 10001, 10250, 10255,
512            15672, 16379, 50070,
513        ]))
514 
515 
516def main():
517    args = parse_args()
518    host = args.host
519 
520    print()
521    print(c("  ╔══════════════════════════════════════════════╗", CYAN))
522    print(c("  ║   🛰  自动扫描 + TCP + HTTP大包压测  v2.0    ║", CYAN, BOLD))
523    print(c("  ╚══════════════════════════════════════════════╝", CYAN))
524    print()
525    print(c(f"  目标: {host}", BOLD))
526 
527    try:
528        ip = socket.gethostbyname(host)
529        if ip != host:
530            print(c(f"  解析: {ip}", GRAY))
531    except Exception as e:
532        print(c(f"  ❌ DNS 解析失败: {e}", RED))
533        sys.exit(1)
534 
535    # ── HTTP-only 模式 ──
536    if args.http_only:
537        port = args.http_port or 80
538        print(c(f"\n  [HTTP-only 模式]  端口 {port}  上传 {args.http_size}GB", YELLOW))
539        r = http_bulk_stress(
540            host=host, port=port,
541            total_gb=args.http_size,
542            chunk_mb=args.chunk_mb,
543            concurrency=args.http_concurrency,
544            timeout=args.http_timeout,
545        )
546        print_http_bulk_report(r)
547        return
548 
549    # ── 全自动模式 ──
550    ports = resolve_ports(args)
551    print(c(f"  扫描端口数: {len(ports)}", GRAY))
552 
553    # 阶段1:扫描
554    open_ports = scan_ports(host, ports, timeout=args.scan_timeout, workers=args.scan_workers)
555    print_scan_result(host, open_ports)
556 
557    if not open_ports:
558        print(c("  没有找到开放端口,退出。", RED))
559        print(c("  提示:防火墙可能拦截了扫描,试试 --scan-timeout 2", GRAY))
560        sys.exit(0)
561 
562    if args.scan_only:
563        sys.exit(0)
564 
565    # 阶段2:TCP 压测
566    bench_targets = [p for p, _ in open_ports[:args.max_bench_ports]]
567    skipped = [str(p) for p, _ in open_ports[args.max_bench_ports:]]
568    if skipped:
569        print(c(f"\n  ℹ️  TCP只压测前 {args.max_bench_ports} 个端口,其余跳过: {', '.join(skipped)}", GRAY))
570 
571    print(c(f"\n  ── 阶段2:TCP 连接压测 ──", BOLD))
572    tcp_results = []
573    for port in bench_targets:
574        r = bench_port(host, port, args.requests, args.concurrency, args.bench_timeout)
575        tcp_results.append(r)
576    print_tcp_bench_report(tcp_results, host)
577 
578    # 阶段3:HTTP 大包上传
579    if args.no_http:
580        return
581 
582    http_port = args.http_port
583    if not http_port:
584        for p, _ in open_ports:
585            if p in HTTP_PORTS:
586                http_port = p
587                break
588 
589    if not http_port:
590        print(c("\n  ℹ️  未发现 HTTP/HTTPS 端口,跳过大包上传测试。", YELLOW))
591        print(c("     如需强制测试,使用 --http-port <端口>", GRAY))
592        return
593 
594    print(c(f"\n  ── 阶段3:HTTP 大包上传压测 ──", BOLD))
595    r = http_bulk_stress(
596        host=host, port=http_port,
597        total_gb=args.http_size,
598        chunk_mb=args.chunk_mb,
599        concurrency=args.http_concurrency,
600        timeout=args.http_timeout,
601    )
602    print_http_bulk_report(r)
603 
604 
605if __name__ == "__main__":
606    main()
607