< 返回版块

hzqelf 发表于 2026-01-17 18:14

我用GoRust写了一个TCP端口扫描器

Go版本:

package main

import (
        "bufio"
        "flag"
        "fmt"
        "net"
        "os"
        "sort"
        "sync"
        "time"
)

// Config 存储命令行参数
type Config struct {
    Target       string        // 目标 IP 地址
    Concurrency  int           // 并发数
    StartPort    int           // 起始端口
    EndPort      int           // 结束端口
    Timeout      time.Duration // 超时时间
}

func parseFlags() Config {
    var config Config

    // 定义命令行参数
    flag.StringVar(&config.Target, "ip", "127.0.0.1", "目标 IP 地址")
    flag.IntVar(&config.Concurrency, "con", 100, "并发数")
    flag.IntVar(&config.StartPort, "begin", 1, "起始端口")
    flag.IntVar(&config.EndPort, "end", 65535, "结束端口")
    timeout := flag.Int("to", 1, "超时时间(秒)")

    // 参数生效
    flag.Parse()

    // 将超时秒数转换为 Duration
    config.Timeout = time.Duration(*timeout) * time.Second

    return config
}

func measureTime(fn func()) {
    start := time.Now()
    fn()
    elapsed := time.Since(start)
        fmt.Printf("Execution Time: %v\n", elapsed)
}

var config = parseFlags()

func worker(ports <-chan int, results chan<- int) {
        var wg sync.WaitGroup

        // 创建指定数量的 worker
        for range config.Concurrency {
                wg.Go(func() {
                        for port := range ports {
                                addr := net.JoinHostPort(config.Target, fmt.Sprintf("%d", port))
                                conn, err := net.DialTimeout("tcp", addr, config.Timeout)
                                if err != nil {
                                        continue
                                }
                                conn.Close()
                                results <- port
                        }
                })
        }
        wg.Wait()
        close(results)
}

func tcpScan() {
        ports := make(chan int, config.Concurrency)
        results := make(chan int, config.Concurrency)

        // 发送端口到 ports channel
        go func() {
                for i := config.StartPort; i <= config.EndPort; i++ {
                        ports <- i
                }
                close(ports)
        }()

        // 启动 worker
        go worker(ports, results)
        fmt.Printf("Scanning %s\n", config.Target)

        // 收集结果
        opened := []int{}
        for port := range results {
                opened = append(opened, port)
        }

        // 排序
    sort.Ints(opened)

        // 打印结果
        fmt.Printf("Open ports (%d found):\n", len(opened))
        for _, port := range opened {
                fmt.Printf("  %d\n", port)
        }
}

func main() {
    measureTime(tcpScan)
    waitEnter()
}

// 按回车键退出程序
func waitEnter() {
        scanner := bufio.NewScanner(os.Stdin)
        for scanner.Scan() {
                line := scanner.Text()
                if len(line) == 0 {
                        break
                }
        }
}

Rust版本:

use clap::Parser;
use futures::StreamExt;
use std::time::{Duration, Instant};
use tokio::net::TcpStream;

#[derive(Parser, Debug, Clone)]
#[command(author, version, about, long_about = None)]
struct Config {
    /// 目标 IP 地址
    #[arg(short = 'i', long = "ip", default_value = "127.0.0.1")]
    ip: String,

    /// 起始端口
    #[arg(short = 's', long = "start", default_value_t = 1)]
    start_port: u16,

    /// 结束端口
    #[arg(short = 'e', long = "end", default_value_t = 65535)]
    end_port: u16,

    /// 并发数
    #[arg(short = 'c', long = "concurrency", default_value_t = 25000)]
    concurrency: usize,

    /// 超时时间(毫秒)
    #[arg(short = 't', long = "timeout", default_value_t = 200)]
    timeout: u64,
}

#[tokio::main]
async fn main() {
    let args = Config::parse();
    let start = Instant::now();

    // 使用缓冲流控制并发
    let mut result: Vec<_> = futures::stream::iter(args.start_port..=args.end_port)
        .map(|port| {
            let host_port = format!("{}:{}", args.ip, port);
            async move {
                if let Ok(Ok(_)) = tokio::time::timeout(
                    Duration::from_millis(args.timeout),
                    TcpStream::connect(host_port),
                )
                .await
                {
                    Some(port)
                } else {
                    None
                }
            }
        })
        .buffer_unordered(args.concurrency) // 最大并发数
        .filter_map(|port| async move { port })
        .collect()
        .await;

    result.sort();

    let count = result.len();
    print!("Open ports ({count} found):\n");

    result.into_iter().for_each(|port| print!("  {port}\n"));
    println!("Execution Time: {:?}", start.elapsed());
}

我都是用命令行参数的默认值,没有手动额外设定参数值。

我的台式机采用英特尔12代i3处理器,Windows操作系统。

Go版本耗时:小于1秒

Rust版本耗时:大于2秒

我知道测试用例不是完全等价的,比如并发数的设定:因为Rust版本并发设为100速度更慢,约14秒)。我想知道Rust具体慢的原因。

我已经询问了 DeepSeek、GLM、豆包、千问 等大语言模型,但它们都没有给出合理的解释。故来此询问,请大佬们指点迷津。

顺便一提,我想在Rust中尝试将Go代码尽可能1:1迁移过来,但发现tokio似乎只有oneshot和mpsc这两种;而mpsc似乎无法满足代码迁移的需求。(因为Receiver无法被clone)

实在是水平有限,借助AI都没能实现Rust的通道版本,所以没办法做1:1的测试,这里也希望大佬们支支招。

还有一件事情:Rust版本调整并发数到65535,会扫不到较高的端口号,比如4万以上的端口号:

  49664
  49665
  49666
  49667
  49670
  49682

Go语言如果直接给每个tcp链接起一个go程而不使用worker模式,也扫不到这些端口。Rust用tokio来spawn 65535个异步tcp连接,也扫不到这些端口。这又是怎么回事呢?

注:我尝试调整了Rust版本的并发数、超时时间,似乎达到某些阈值会扫不到这些端口。

评论区

写评论
amazinq 2026-01-23 13:18

futures::StreamExt::buffer_unorderedtokio在高并发时都有点性能问题,另外mpmc可以找第三方库来用,这里用的tokio-mpmc,不过貌似这个库也有点性能问题

有那个系统资源限制(异常被代码忽略了)和windows的默认重试策略存在,之前的测试确实可能掺入了虚假的数据。所以以下的测试都尽量排除了这些因素。

测试准备

在 Linux 上用以下指令增大程序开启文件上限(linux默认端口可复用,可以不管),防止资源溢出("Too many open files"):

ulimit -n 65536

在 Winodws 上,避免并发量超过15000来防止端口资源溢出("由于系统缓冲区空间不足或队列已满,不能执行套接字上的操作。"),重试这个就得自己创建RawSocket了,甚至可能需要FFI才能取消重试,太麻烦了

为了监测有响应的TCP连接在异步运行时内的消耗,定义一个时间监测的装饰器:

struct TimeSpan<F: Future> {
    f: F,
    start: Instant,
    port: u16,
}
impl<F: Future> TimeSpan<F> {
    fn new(f: F, port: u16) -> Self {
        let start = Instant::now();
        TimeSpan { f , start, port }
    }
}
impl<F: Future> Future for TimeSpan<F> {
    type Output = F::Output;
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        let this = unsafe { self.get_unchecked_mut() };
        let poll = unsafe { Pin::new_unchecked(&mut this.f) }.poll(cx);
        if poll.is_ready() {
            println!("Time[{}]: {:?}", this.port, this.start.elapsed());
        }
        poll
    }
}

TimeSpan只测试正常完成的任务(取消的不会输出),在调度器内的future本身调度完成的时间。更精细的时间数据,可以让start由外部统一传入一个"原点",可以测量初始偏移和最终偏移。

测试示例

这里只展示在最低配的阿里云1核主机上测试比较(Ubuntu x86_64 2.5GHz),以下的数据都来自这台主机:

注意

  1. 这里测试不会展示本地ip的扫描结果,因为本地回环网络都能在正常时间内返回连接成功/失败,速度很快,且会产生大量噪声数据,所以这里扫描的都是另一个低中高端口都开放防火墙的外网主机。
  2. Rust主要使用cargo r -r -- [options]运行,Go使用go build test.go + ./test [options]

测试结果表明,tokio调度器性能存在一个任务数量界限,在同时处理任务数量达到数千个时(可能与机子性能相关),调度器的额外消耗开始明显增加,并且随着数量继续扩大,开销增加趋势也在迅速变大,到最后可能还不如低任务数量的效率。

  1. 首先第一个,用futures::StreamExt::buffer_unordered接口做并发是单线程的,在低任务时开销不明显,但是并发量高起来后,副作用就相对明显了
    1. 在这台机子上,使用tokio-mpmc + tokio::spawn改造后,满载情况下耗时大概能从3.x(原始代码) 降到2.x
  2. 第二就是运行时的调度切换成本,在瞬间创建超量的新任务时,底层依然还是要挨个遍历轮询,这样靠后的任务延迟就很高
    1. 即使设置-c 65535一次性全扫描,Rust代码的耗时也在 2.x 左右波动,而设置-c 10240,Rust代码耗时也是在 1.6~2.x 左右波动
    2. 并发量降低6倍,效率反而有所提升
    3. CPU 90+%

类似的,Go版(go version go1.25.5 linux/amd64)代码应该也是有一个性能衰减边界,不过没有tokio这么明显:

  1. 在设置-con 65535时,耗时大概 5.x~6.x 秒(CPU 90+%)
  2. 在设置-con 10240时,耗时大概 7.x 秒(此时发现CPU大概只有60+%)
  3. 并发量降低6倍,效率仅有轻微降低

上面测试结果已经发现了,在排除其他非语言因素后,Go版扫描比Rust版明显更慢,debug模式亦如此,而在设置-c 5120-con 5120后对比更明显:

  1. Rust -c 5120 CPU 90+%

    Time[80]: 3.499961ms -> 13.564715ms
    Time[443]: 16.167227ms -> 22.691732ms
    Time[9003]: 296.067335ms -> 316.985104ms
    Time[9002]: 296.040792ms -> 317.88182ms
    Time[9005]: 296.097447ms -> 318.192882ms
    Time[9004]: 296.071246ms -> 318.311288ms
    Time[9001]: 296.040304ms -> 318.334899ms
    Time[23333]: 894.970727ms -> 903.375697ms
    Open ports (5 found):
      80
      443
      9001
      9002
      9003
    Execution Time: 2.770325709s
    
  2. Golang -con 5120 CPU 25+%

    Scanning ...
    Open ports (5 found):
      80
      443
      9001
      9002
      9003
    Execution Time: 13.315283952s
    
  3. 测试发现,Go大概需要在15k并发度的时候,才能CPU 90+%,而Rust在4k并发度就能CPU 90+%

  4. 进一步对比,一次性全扫描本机127.0.0.1,Rust依然维持 1.x2.x,而Go提升到 2.x3.x

对于两者的耗时差异,由于我上次接触Go已经是快两年前了,现在对Go底层机制不甚了解,也不清楚为什么Go在并发度低的时候CPU都跑不满。

所以只能盲猜可能是因为垃圾回收+有栈协程,垃圾回收会导致频繁停顿,而有栈协程在切换任务时,比无栈协程需要保存和恢复更多的临时变量。

当然,也有可能是因为这台测试机的性能太差了

xiaoyaou 2026-01-21 16:24

因为之前发现了一些比较奇怪的现象,后面好奇研究了一下:

其中比较重要的是,不同操作系统上,对进程的资源数量,或多或少都会有限制。比如:

  1. Linux: 这里socket也是文件,默认受以下限制
    $ ulimit -a
    
    ...
    open files  (-n)  1024
    ...
    
  2. Windows: 这里socket算句柄,虽然对句柄数量没什么限制,但是网络端口依然有相关的限制
    > netsh int ipv4 show dynamicport tcp
    
    协议 tcp 动态端口范围
    ---------------------------------
    启动端口        : 49152
    端口数          : 16384
    

所以,在并发量较高的时候,某时刻资源耗尽,因为旧资源未能及时释放,会导致后续新的socket创建失败,直接提前返回err,导致快速的虚假连接失败。

xiaoyaou 2026-01-20 18:01

关于golang标准实现里用的api优化,在微软官方学习资料里SIO_TCP_INITIAL_RTO控制代码TCP_INITIAL_RTO_PARAMETERS 结构有,里面说明了如何配置重传次数

xiaoyaou 2026-01-20 15:47

我觉得这个主要还是系统环境导致的差异,加上Rust只使用了默认的系统调用,而Go对windows的行为进行了优化,所以Go版能在网络协议完整的情况更快返回,而Rust完全取决于系统实现。对于这一点,我也使用Python进行了测试,发现表现上跟Rust是一致的,这里就不再展示。

下面是简单的抓包分析

windows 示例

在网络协议层面上,TCP的连接速度应该是与语言无关的,所以我针对前面出现的情况,作为示例在windows上抓取了网络包来分析,为了简化数据这里只放关键数据。

  1. 本机端口扫描(127.0.0.1或者本机内网Ip),这通常会进入本地回环(Loopback),网络包不会真的发送到系统外部,在内核中就直接转发了,所以默认防火墙不会拦截。如果是使用开放了网络防火墙的外网Ip,也是一样的。

    这里每次都是连接请求等待2秒后才提示目标计算机积极拒绝,无法连接

    No Time Ptl Info 简单分析
    1 0.000000 TCP 52642 → 20000 [SYN] Seq=0 Win=65535 Len=0 SYN请求
    2 0.000015 TCP 20000 → 52642 [RST, ACK] Seq=1 Ack=1 Win=0 Len=0 连接重置
    3 0.505515 TCP [TCP Port numbers reused] 52642 → 20000 [SYN] Seq=0 1次重试
    4 0.505535 TCP 20000 → 52642 [RST, ACK] Seq=1 Ack=1 Win=0 Len=0 连接重置
    5 1.016914 TCP [TCP Port numbers reused] 52642 → 20000 [SYN] Seq=0 2次重试
    6 1.016963 TCP 20000 → 52642 [RST, ACK] Seq=1 Ack=1 Win=0 Len=0 连接重置
    7 1.528423 TCP [TCP Port numbers reused] 52642 → 20000 [SYN] Seq=0 3次重试
    8 1.528447 TCP 20000 → 52642 [RST, ACK] Seq=1 Ack=1 Win=0 Len=0 连接重置
    9 2.032603 TCP [TCP Port numbers reused] 52642 → 20000 [SYN] Seq=0 4次重试
    10 2.032654 TCP 20000 → 52642 [RST, ACK] Seq=1 Ack=1 Win=0 Len=0 连接重置
  2. 外网端口扫描(外网Ip,数据包经过外部网络传递),这通常会经过网关、防火墙等设备,可能被拦截。

    这里每次都是连接请求等待21秒后才提示连接的主机没有响应,连接失败

    No Time Ptl Info 简单分析
    1 0.000000 TCP 52652 → 20000 [SYN] Seq=0 Win=64240 Len=0 SYN请求
    2 1.009387 TCP [TCP Retransmission] 52652 → 20000 [SYN] Seq=0 TCP重传
    3 3.009990 TCP [TCP Retransmission] 52652 → 20000 [SYN] Seq=0 TCP重传
    4 7.021336 TCP [TCP Retransmission] 52652 → 20000 [SYN] Seq=0 TCP重传
    5 15.029888 TCP [TCP Retransmission] 52652 → 20000 [SYN] Seq=0 TCP重传

2秒和21秒也算是windows上很经典的连接等待时间了,从上面抓包记录分析,实际分为两类:

  1. SYN数据包是能正确响应RST网络协议的,系统是能立即知道目标主机拒绝连接,但是在windows上依然会重复尝试4次,每次间隔0.5秒,然后再返回失败。
  2. SYN数据包被拦截丢弃的(无响应,因为中途可能经过网关和防火墙),就会触发TCP层的丢包重传机制(1->2->4->8->..),但是在windows上,第21秒才被系统暂停,共4次重传,然后返回失败。

在 Linux 系统上的差异

在 Linux 系统(我用的是ubuntu)上,基础的网络层过程跟 Windows 基本是一样,但是操作系统默认处理的行为不一样:

  1. 对于能正确响应RST的(没有被防火墙拦截),Linux 系统不会重试,而是会立即返回失败。
  2. 对于无响应的外网数据包,由于始终收不到确认响应(因为这个端口故意被网络安全组拦截的,使用系统防火墙拦截也是一样效果),依然触发TCP的重传机制,但是需要两分多钟才会暂停,共7次重传,然后返回失败

总结

总结就是,端口扫描效率受到目标主机的网关、防火墙等拦截规则影响很大,并且对于连接请求的响应与否,不同系统实现的默认处理方式也有所不同。对于明确拒绝连接的端口,windows依然会尝试重连4次,而linux会直接返回失败;对于无响应的连接,linux则是比windows默认等待的时间更久。

上面已经有亮出Go对Windows连接速度的优化处理,而rust我在crate.io简单搜了一下,也不清楚应该用什么关键字,貌似还没有相关优化过windows连接速度的库。

kwsc98 2026-01-20 14:59

怎么感觉像window的问题,我在mac(arm)上跑go和rust差不多都是1.8

Nayaka 2026-01-19 18:01

Go版本我在我的服务器上跑平均1,8秒

Rust的标准库+OS线程,平均600毫秒

阿里云服务规格ecs.e-c1m2.xlarge, 4C8G

省流就是:windows和linux逻辑不一样

大概方向:

大量线程+阻塞 TcpStream::connect_timeout 在 Windows 上比 Linux 更昂贵,Windows 对大量并发短连接的上下文切换/套接字管理开销更高

Linux 对本地回环连接(尤其返回 ECONNREFUSED)通常更快,Windows 在某些情况下会有更高的系统调用/防火墙处理延迟。

线程数太多导致调度/资源争用,200 线程在 Windows 上可能比在 Linux 上代价更高

--
👇
hzqelf: --
👇
artiga033:

至少在建立连接这块,异步一般没有同步快,所以你这还不如直接用标准库OS线程,

首先就是光是把你那个tokio timeout直接换成标准库,就能快不少:

    if let Ok(Ok(_)) = tokio::task::spawn_blocking(move || {
                    std::net::TcpStream::connect_timeout(
                        &host_port.parse().expect("socket addr"),
                        Duration::from_millis(args.timeout),
                    )
                })
                .await

这个版本是 700ms,正好比tokio快一倍,比go慢一倍。这里你就已经可以把配置改成和golang一样是100并发1s超时了,我测试没明显差别。

我复制粘贴了这段代码覆盖上去,耗时27秒。另外:

所以直接用标准库+OS线程其实更快... 这个case甚至在playground上都能跑进一秒,在我本地是72ms。

这份代码在playground确实很快,但在我的电脑上跑了一分钟也没有出结果。这真的很奇怪!

我的Windows11家庭版版本号为:24H2,版本是:26100.7623

rustup show
Default host: x86_64-pc-windows-msvc
rustup home:  C:\Users\xxx\.rustup

installed toolchains
--------------------
stable-x86_64-pc-windows-msvc (active, default)

rustc -V
rustc 1.92.0 (ded5c06cf 2025-12-08)
作者 hzqelf 2026-01-19 09:52

--
👇
artiga033:

至少在建立连接这块,异步一般没有同步快,所以你这还不如直接用标准库OS线程,

首先就是光是把你那个tokio timeout直接换成标准库,就能快不少:

    if let Ok(Ok(_)) = tokio::task::spawn_blocking(move || {
                    std::net::TcpStream::connect_timeout(
                        &host_port.parse().expect("socket addr"),
                        Duration::from_millis(args.timeout),
                    )
                })
                .await

这个版本是 700ms,正好比tokio快一倍,比go慢一倍。这里你就已经可以把配置改成和golang一样是100并发1s超时了,我测试没明显差别。

我复制粘贴了这段代码覆盖上去,耗时27秒。另外:

所以直接用标准库+OS线程其实更快... 这个case甚至在playground上都能跑进一秒,在我本地是72ms。

这份代码在playground确实很快,但在我的电脑上跑了一分钟也没有出结果。这真的很奇怪!

我的Windows11家庭版版本号为:24H2,版本是:26100.7623

rustup show
Default host: x86_64-pc-windows-msvc
rustup home:  C:\Users\xxx\.rustup

installed toolchains
--------------------
stable-x86_64-pc-windows-msvc (active, default)

rustc -V
rustc 1.92.0 (ded5c06cf 2025-12-08)
xsbsy-hzbyp 2026-01-18 11:56

在Windows上,go版本只要几百微秒,rust版本要两秒,我猜是tokio用的系统调用不一样,像是等了一次重传。

好像和这里有关:

	if isloopback {
		// This makes ConnectEx() fails faster if the target port on the localhost
		// is not reachable, instead of waiting for 2s.
		params := windows.TCP_INITIAL_RTO_PARAMETERS{
			Rtt:                   windows.TCP_INITIAL_RTO_UNSPECIFIED_RTT, // use the default or overridden by the Administrator
			MaxSynRetransmissions: 1,                                       // minimum possible value before Windows 10.0.16299
		}
		if windows.SupportTCPInitialRTONoSYNRetransmissions() {
			// In Windows 10.0.16299 TCP_INITIAL_RTO_NO_SYN_RETRANSMISSIONS makes ConnectEx() fails instantly.
			params.MaxSynRetransmissions = windows.TCP_INITIAL_RTO_NO_SYN_RETRANSMISSIONS
		}
		var out uint32
		// Don't abort the connection if WSAIoctl fails, as it is only an optimization.
		// If it fails reliably, we expect TestDialClosedPortFailFast to detect it.
		_ = fd.pfd.WSAIoctl(windows.SIO_TCP_INITIAL_RTO, (*byte)(unsafe.Pointer(&params)), uint32(unsafe.Sizeof(params)), nil, 0, &out, nil, 0)
	}
xsbsy-hzbyp 2026-01-18 10:55

你这样用一个大 future 只做并发、不做并行的话就相当于在一个线程上叫 6 万多次的 connect 系统调用,好比你 Go 那边设置了 GOMAXPROCS=1,即使 socket 设置了非阻塞,肯定也要比 Go runtime 帮你把任务调度到不同系统线程上要慢。如果只是传一直递增的端口号的话可以不需要通道,直接用一个 AtomicU32,然后 worker 那边一直用 fetch_add 拿一个,直到扫描范围末尾就好。

artiga033 2026-01-18 00:22

首先你的这个版本在我的Windows上,golang要2s,Rust要14s,如果rust改成100并发那我不知道要多久,因为等了一分钟还没跑完。

Linux下比较正常 go版本300ms,Rust1400ms。

我不知道为什么你那里没问题,可能你的Windows版本和配置和我不一样,不过我试了我的两个Windows设备都有这个问题。

先说为什么,对于随便一个没有开放的端口,以下代码:

package main

import (
	"fmt"
	"net"
	"time"
)

func main() {
	ts := time.Now()
	conn, err := net.Dial("tcp", "127.0.0.1:65521")
	if err != nil {
		fmt.Println("error:", err)
	} else {
		conn.Close()
	}
	te := time.Since(ts)
	println("Elapsed", te.Microseconds(), "µs")
}
#[tokio::main]
async fn main() {
    let ts = std::time::Instant::now();
    let addr: std::net::SocketAddr = "127.0.0.1:65521".parse().expect("socket addr");
    let conn = tokio::net::TcpStream::connect(addr).await;
    match conn {
        Ok(_) => println!("Connected"),
        Err(e) => println!("Failed to connect: {}", e),
    }
    println!("Elapsed: {:?}", ts.elapsed());
}

在Windows上,go版本只要几百微秒,rust版本要两秒,我猜是tokio用的系统调用不一样,像是等了一次重传。所以造成的结果就是,Rust版本实际上不管对于是真关闭的端口,还有开放但是不回包的端口,都会一直等到min(2s,timeout_duration)。找了一圈没找到怎么通过改windows配置解决,要么只能直接手搓socket。所以我下面的内容都是基于linux测的。

至少在建立连接这块,异步一般没有同步快,所以你这还不如直接用标准库OS线程,

首先就是光是把你那个tokio timeout直接换成标准库,就能快不少:

    if let Ok(Ok(_)) = tokio::task::spawn_blocking(move || {
                    std::net::TcpStream::connect_timeout(
                        &host_port.parse().expect("socket addr"),
                        Duration::from_millis(args.timeout),
                    )
                })
                .await

这个版本是 700ms,正好比tokio快一倍,比go慢一倍。这里你就已经可以把配置改成和golang一样是100并发1s超时了,我测试没明显差别。

如果把 spawn_blocking 去掉,会快非常多,到400ms左右(不过这是因为我这里是wsl,开放的端口一共就两个),但是非常不建议,至于为什么看过tokio文档应该就知道了。

所以直接用标准库+OS线程其实更快... 这个case甚至在playground上都能跑进一秒,在我本地是72ms。

至于mpmc channel,可以用crossbeam或者flume,应该能优化到更高。

当然我的结果其实没什么参考价值,如前所述,我这里开放的端口极少,而你的程序逻辑就非常依赖超时判断,端口数量不同差距应该会很大。

最后,

Rust版本调整并发数到65535,会扫不到较高的端口号

计算机网络基本问题,唯一标识一个TCP连接的四元组是(源IP,源端口,目标IP,目标端口),那你这里源IP和目标IP是一样的(127.0.0.1),又是同一台机器端口也是共用的,你把全部端口都要完了那新的任务再向OS申请端口的时候直接就失败了,你把错误打印出来而不要忽略就知道了,这个和语言没关系...Go的话可以用net.Dialer里的LocalAddr字段手动选一个别的IP,比如127.0.0.2,但其实这样你也不能要65535,因为1024以下的端口你也是申请不到的,除非是root,而且作为dialer的话可能可用的端口更少,总之建议补一下计网基础。Rust标准库和tokio都没有设置源IP的选项,只能自己创建raw socket。

relufi 2026-01-17 23:37

你贴出来的rust就没用tokio::spawn啊, 贴你最新的代码

Linyuqiz 2026-01-17 22:53
排名 版本 最佳成绩 平均成绩 相比Go 优化技巧
🥇 Rust Rayon 优化版 700.73ms 861ms 快 15.7% 避免字符串分配
🥈 Rust Rayon 原版 703.01ms 834ms 快 15.4% 数据并行
🥉 Rust ThreadPool 790.05ms 885ms 快 5.0% 固定线程池
4️⃣ Go 原版 831.30ms 996ms 基准 goroutine
5️⃣ Rust Tokio 1358.48ms 1424ms 慢 63% 异步开销
1 共 12 条评论, 1 页