< 返回版块

hzqd 发表于 2021-02-23 00:29

Tags:并发,并行

我想写一个 TCP 端口扫描器,用来判断端口打开和关闭的工具。

以 Rust 官网为例,代码如下:

use rayon::prelude::*;
use std::net::TcpStream;

fn main() {
    let result = (0..=65535).into_par_iter().map(|i|
        match TcpStream::connect(format!("rust-lang.org:{}", i)) {
            Ok(_) => (Some(i), None),
            Err(_) => (None, Some(i))
        }
    ).collect::<Vec<_>>();

    let open = result.par_iter().filter_map(|i| i.0).collect::<Vec<_>>();
    let close = result.into_par_iter().filter_map(|i| i.1).collect::<Vec<_>>();

    println!("打开的端口号: {:?}", open);
    println!("关闭的端口号: {:?}", close);
}

其中,第 5 行的 map,以及 12 和 13 行的 filter_map,它们一共循环了 3 次。

我的问题如下:

  1. 有没有办法只循环一次?
  2. rayon 在此场景下,速度仍不够快。有没有更快/更适用的并行方式?
  3. 对于此需求,并发和并行,哪种更合适?

评论区

写评论
whfuyn 2021-02-25 00:44
E834159672 2021-02-24 09:39

有一个crate,名字好像是:itertools,提供了类似groupby的操作

--
👇
ltoddy: Rust的迭代器没有Java Stream中的类似GroupBy的方法,所以没法合成一个循环。

zhylmzr 2021-02-23 14:51

试了下,timeout还是放在外层比较好,dns查询也需要时间,或者使用tokio的lookup_host。 https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=99447eaf55ab96957908ac804bae048a

👇
zhylmzr: 对rayon不是很了解,不过需要异步io的时候可以用tokio,用tokio写了一版

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=df8035d5969fbca756e138cef066c680

zhylmzr 2021-02-23 14:17

对rayon不是很了解,不过需要异步io的时候可以用tokio,用tokio写了一版

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=df8035d5969fbca756e138cef066c680

Ryan-Git 2021-02-23 11:39

这里瓶颈肯定是在 connect。建议用 tokio 之类的非阻塞 connect,就不用开线程了(即不用 rayon)。

ltoddy 2021-02-23 07:21

Rust的迭代器没有Java Stream中的类似GroupBy的方法,所以没法合成一个循环。

johnmave126 2021-02-23 06:20

PS: tcp超时的具体秒数是印象里的,不一定准确,总之协议里的默认超时超过了120s

johnmave126 2021-02-23 06:17
use rayon::prelude::*;
use std::net::{ToSocketAddrs, TcpStream};

fn main() {
    let mut addrs = "rust-lang.org:0".to_socket_addrs().unwrap();
    let base_addr = addrs.next().unwrap();
    let (open, close): (Vec<_>, Vec<_>) = (0..=65535_u16).into_par_iter().partition(|i| {
        let mut addr = base_addr.clone();
        addr.set_port(*i);
        TcpStream::connect(addr).is_ok()
    });

    println!("打开的端口号: {:?}", open);
    println!("关闭的端口号: {:?}", close);
}

做了两个优化

  1. 地址只解析一次
  2. 用一个partition代替filter

我觉得主要慢在tcp超时,tcp协议里握手超时是186秒,建议用connect_timeout去代替connect

1 共 8 条评论, 1 页