端口扫描工具
工具如其名,主要功能是扫描服务器端口是否开放。常用的方式是扫描服务器(S)在某一段端口(P)范围内,有哪些端口正在被监听。
如:扫描服务器 127.0.0.1,端口 [80, 2000)段内,查看被监听的端口。
实现方案
- 和对端服务器端口尝试建立 socket 链接,如果链接成功建立,则表明该端口正在被监听。
- 使用多线程方案,加快端口扫描速度
- 使用协程方案,降低资源使用,加快扫描速度
以上就是大致的是实现步骤。本次实现了
- Java 单线程扫描
- Java 多线程扫描
- Java 协程扫描
- Rust 协程扫描
没错,>..< 实际上这是一次学习 Java Quasar Fiber 和 Rust 过程中的小练习。
实现代码
实现已放在 github 仓库,感兴趣的可以下载自行运行。
遇到的问题
Java Fiber 和 Rust 的实现过程中都遇到了关于协程使用上的问题,在此记录一下。
Java Fiber
首先需要在函数上标明 @Suspendable
,协程内的阻塞函数需要标记此注解才能让协程正常服务。
其次协程中使用的阻塞函数,需要有自己的封装实现。线程方式中使用 java.net.Socket#connect
函数是调用的 JDK 原生实现 java.net.PlainSocketImpl#socketConnect
。
这个实现是阻塞的,此阻塞的实现,会影响阻塞住协程,使得协程的性能无法发挥。
Quasar 为此单独提供了 FiberSocketChannel
实现,此实现调用了 co.paralleluniverse.fibers.io.AsyncFiberSocketChannel#connect
,
底层是异步 IO 实现,使用 Fiber.park
接替了原有的阻塞实现。
使用过程中可以感受到,使用 Fiber 接替现有的代码,还是有需要改造的部分,无法零成本接入。(此处如果我有使用不合理的地方,请邮件我改进,谢谢)
Rust
Rust 版本实现中,使用了三方依赖 may 作为 coroutine 的实现依赖。出现了同 Java 版本类似的问题。
原生的 std::net::TcpStream
依然会阻塞住协程,还是需要使用 may 提供的异步 IO 实现 may::net::TcpStream
才能匹配协程提升性能。
另外就是 Rust 的 TcpStream::connect_timeout
使用方式不能按照官方提供的文档使用。
use std::net::TcpStream;
if let Ok(stream) = TcpStream::connect("127.0.0.1:8080") {
println!("Connected to the server!");
} else {
println!("Couldn't connect to server...");
}
按照此方法使用 TcpStream::connect_timeout
,会导致 if 判断提前进入 else 逻辑,造成逻辑异常。
必须将链接建立的语句提前,才能逻辑正常。
// 正确实现
let stream = TcpStream::connect_timeout(&addrs[0], timeout);
if stream.is_ok() {
stream.unwrap().shutdown(Shutdown::Both).expect("shutdown tcp stream fail");
return true;
} else {
return false;
}
诡异的问题
实现 Rust 版本的测试结果中,出现了诡异的问题。问题描述如下:
使用 rdial --start-port 80 --end-port 9000 --hostname 127.0.0.1 --timeout 200 执行时,
结果返回了 [1080, 4198] 两个接口,实际上 6394 接口也是在被监听,但没有被扫描出来。
修改使用 rdial --start-port 6000 --end-port 9000 --hostname 127.0.0.1 --timeout 200 执行时,
此时结果返回了 6394 接口。
经过 debug 确认了所有端口都有的的确确被扫描到,并没有漏掉 6394 端口。
继续尝试增加 timeout 超时时间,发现还是无法扫描到 6394 端口,但是有个特殊现象——6394 端口在建立链接语句执行后,立马返回了“close”日志,并没有经过设置好的等待时间。
最后发现问题是在操作系统限制的 fd 上,使用 ulimit -n
发现此时设置为 4864,可以看到我们的扫描程序第一次执行的确扫描到了 4198 这个接口监听,而忽略了 6394 这个接口。
答案呼之欲出,执行 ulimit -n 10000
将 fd 限制提升到 10000 之后再次执行,此时返回结果 [1080, 4198, 6394]
,问题解决。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 [email protected]