题目重点:探测旧123与新456端口,输出迁移状态并限制并发,区分端口事实与版本证明。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 训练每host两个TCP connect probes,端口按约定对应服务版本,但连接成功只证明端口接受连接,真正version需协议健康响应。输入host唯一,代码不扫描未授权目标。
- 明确open/refused/unknown;超时、DNS失败和路由问题均unknown,不称host必down。示例无自动重试,总结要保留未知项。
解题路线
- 旧新都open标both,旧open新refused为old-only,反之new-only,都refused为neither。任一unknown都保留两端详情并归unknown,不能与refused合并。
- 每连接with context关闭socket,防数千host句柄泄漏;timeout限制connect等待,但OS DNS解析不受同一connect timeout严格保证,别承诺完整总deadline。
- I/O等待适合ThreadPoolExecutor,有界in-flight workers,不一次submit整个巨大文件。完成一个host再提交下一个,流式yield结果,输出顺序按完成不保证文件顺序。
- 每host两probe独立记录,巡检间状态可能变,因此不是全网原子快照;记录观测时间并对迁移异常做有预算复查,勿自动重启或关旧版。
- 进程适合CPU密集解析/隔离而非简单socket等待,额外启动/通信成本大。线程也有DNS、fd和目标负载上限,不能无限开线程缩短总时长。
手动走一遍
old connect成功、new超时应输出old=open,new=unknown,status=unknown,不能说only-old;两端拒绝仅neither-port-open,主机可能健康但服务未监听。两open需进一步核对进程/版本,可能双实例过渡。
Python 参考实现
下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。
代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。
import socket,errno
from concurrent.futures import ThreadPoolExecutor,wait,FIRST_COMPLETED
def probe_port(host,port,timeout):
"""Connect timeout does not guarantee a deadline for OS DNS resolution."""
try:
with socket.create_connection((host,port),timeout=timeout):return 'open'
except OSError as error:
return 'refused' if error.errno==errno.ECONNREFUSED else 'unknown'
def migration_status(host,timeout=2,probe=probe_port):
old=probe(host,123,timeout);new=probe(host,456,timeout)
if 'unknown' in (old,new):status='unknown'
elif old=='open' and new=='open':status='both_ports_open'
elif old=='open':status='old_port_only'
elif new=='open':status='new_port_only'
else:status='neither_port_open'
return host,old,new,status
def iter_migration(hosts,workers=32,timeout=2,probe=probe_port):
"""At most workers hosts in flight; unordered completion; unique input hosts.
Yields facts, not proof of service version or host liveness. No network retries.
"""
if workers<=0 or timeout<=0:raise ValueError('positive workers/timeout')
iterator=iter(hosts)
with ThreadPoolExecutor(max_workers=workers) as pool:
pending=set()
for _ in range(workers):
try:host=next(iterator)
except StopIteration:break
pending.add(pool.submit(migration_status,host,timeout,probe))
while pending:
completed,pending=wait(pending,return_when=FIRST_COMPLETED)
for future in completed:
yield future.result()
try:host=next(iterator)
except StopIteration:continue
pending.add(pool.submit(migration_status,host,timeout,probe))
复杂度
H host两探测、W并发,理想网络等待总约O(H×平均探测等待/W),但DNS与目标限速影响;in-flight状态O(W)、结果流式,调用方收集全结果才O(H)。超时×2不是OS DNS全局硬截止。
易错点与追问
容易失分的边界
- 超时直接判断down,隐藏防火墙/网络不确定性。
- 无界submit和线程数让巡检本身资源耗尽。
- TCP open当作服务确实新版,无版本响应验证。
继续深挖
- 若强制每host总deadline,怎样隔离/限制DNS并取消未完成I/O?
- 重试需指数退避与总预算,怎样避免迁移故障时巡检加剧负载?
