为何bash端口扫描脚本比node-fetch慢300倍?如何优化?
性能差异的核心原因
1. 进程启动的额外开销
Node.js脚本在单进程内完成所有HTTP请求,node-fetch是进程内的模块调用,无需反复创建新进程。而你的Bash脚本每次循环都会启动一个独立的curl进程,进程的fork/exec、资源分配与销毁会带来大量额外开销——这是两者性能差距的主要来源。
2. localhost与0.0.0.0的解析/连接差异
localhost默认优先尝试IPv6解析(对应::1),如果目标服务仅监听IPv4,curl会先尝试IPv6连接失败,再回退到IPv4,每个请求都会多一次失败的连接尝试,导致总耗时暴增。而0.0.0.0直接指定IPv4,跳过了IPv6的尝试流程。
Node.js的fetch对localhost的解析逻辑更高效,可能默认优先使用IPv4,或者解析/失败回退的耗时远低于curl,因此两者差异不明显。
3. 同步执行的串行瓶颈
你的Bash脚本是串行执行:必须等当前curl完成才能进入下一次循环。而Node.js即使使用await串行调用,单进程内的HTTP处理效率也远高于频繁启动外部进程的Bash。
Bash脚本的优化方案
1. 强制使用IPv4消除解析延迟
给curl添加-4参数,强制走IPv4,避免localhost的IPv6尝试:
for ((port=$port_from; port<=$port_to; port++)) do json="$(curl -4 -s http://localhost:$port/config)" echo "$json" done
这能大幅缩小localhost与0.0.0.0的性能差距。
2. 异步并发执行(核心优化)
利用Bash的后台进程(&)让多个curl同时运行,最后用wait等待所有进程完成,彻底打破串行瓶颈:
# 定义处理单个端口的函数 scan_port() { local port=$1 json=$(curl -4 -s http://localhost:$port/config) # 输出时带上端口,避免结果混乱 echo "Port $port: $json" } # 批量启动后台进程 for ((port=$port_from; port<=$port_to; port++)) do scan_port $port & done # 等待所有后台进程完成 wait
这种方式的总耗时会接近单个请求的最长耗时,而非所有请求耗时之和。
3. 复用curl连接(进阶优化)
使用curl的--parallel参数批量处理请求,减少进程启动次数:
# 生成所有请求URL的列表 urls=() for ((port=$port_from; port<=$port_to; port++)) do urls+=("http://localhost:$port/config") done # 并行请求,--parallel-max控制并发数 curl -4 -s --parallel --parallel-max 10 "${urls[@]}"
这种方式只用一个curl进程处理所有请求,彻底消除进程启动开销,性能会更接近Node.js的水平。
4. 用Bash内置/dev/tcp替代curl(极端轻量化)
如果不想依赖curl,可以用Bash内置的/dev/tcp直接建立TCP连接,手动构造HTTP请求。但需要自己处理响应解析,适合追求极致轻量化的场景:
scan_port() { local port=$1 # 构造HTTP GET请求 printf "GET /config HTTP/1.1\r\nHost: localhost:%d\r\nConnection: close\r\n\r\n" $port > /dev/tcp/localhost/$port # 读取响应,过滤出JSON部分(假设响应是纯JSON或在Body中) response=$(cat < /dev/tcp/localhost/$port) json=$(echo "$response" | awk 'BEGIN{flag=0} /^\r$/ {flag=1; next} flag==1 {print}') echo "Port $port: $json" } for ((port=$port_from; port<=$port_to; port++)) do scan_port $port & done wait
注意:这种方式需要处理连接超时、响应格式等问题,复杂度较高。
内容的提问来源于stack exchange,提问作者BoldSheep

