網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
利用Rust實(shí)現(xiàn)一個(gè)簡(jiǎn)單的Ping應(yīng)用_Rust語(yǔ)言
作者:qingwave ? 更新時(shí)間: 2023-01-03 編程語(yǔ)言這兩年Rust火的一塌糊涂,甚至都燒到了前端,再不學(xué)習(xí)怕是要落伍了。最近翻了翻文檔,寫了個(gè)簡(jiǎn)單的Ping應(yīng)用練練手,被所有權(quán)折騰的夠嗆,相比起Golang上手難度大很多,現(xiàn)將開(kāi)發(fā)中的一些問(wèn)題總結(jié)如下,所有源碼見(jiàn)ring。
目標(biāo)
實(shí)現(xiàn)一個(gè)Ping,功能包含:
命令行解析
實(shí)現(xiàn)ICMP協(xié)議,pnet包中已經(jīng)包含了ICMP包定義,可以使用socket2庫(kù)發(fā)送
周期性發(fā)送Ping,通過(guò)多線程發(fā)送,再匯總結(jié)果
監(jiān)聽(tīng)退出信號(hào)
命令行解析
系統(tǒng)庫(kù)std::env::args可以解析命令行參數(shù),但對(duì)于一些復(fù)雜的參數(shù)使用起來(lái)比較繁瑣,更推薦clap。利用clap的注解,通過(guò)結(jié)構(gòu)體定義命令行參數(shù)
/// ping but with rust, rust + ping -> ring
#[derive(Parser, Debug, Clone)] // Parser生成clap命令行解析方法
#[command(author, version, about, long_about = None)]
pub struct Args {
/// Count of ping times
#[arg(short, default_value_t = 4)] // short表示開(kāi)啟短命名,默認(rèn)為第一個(gè)字母,可以指定;default_value_t設(shè)置默認(rèn)值
count: u16,
/// Ping packet size
#[arg(short = 's', default_value_t = 64)]
packet_size: usize,
/// Ping ttl
#[arg(short = 't', default_value_t = 64)]
ttl: u32,
/// Ping timeout seconds
#[arg(short = 'w', default_value_t = 1)]
timeout: u64,
/// Ping interval duration milliseconds
#[arg(short = 'i', default_value_t = 1000)]
interval: u64,
/// Ping destination, ip or domain
#[arg(value_parser=Address::parse)] // 自定義解析
destination: Address,
}
clap可以方便的指定參數(shù)命名、默認(rèn)值、解析方法等,運(yùn)行結(jié)果如下
? ?ring git:(main) cargo run -- -h
? ?Compiling ring v0.1.0 (/home/i551329/work/ring)
? ? Finished dev [unoptimized + debuginfo] target(s) in 1.72s
? ? ?Running `target/debug/ring -h`
ping but with rust, rust + ping -> ring
Usage: ring [OPTIONS] <DESTINATION>
Arguments:
? <DESTINATION> ?Ping destination, ip or domain
Options:
? -c <COUNT> ? ? ? ? ? ?Count of ping times [default: 4]
? -s <PACKET_SIZE> ? ? ?Ping packet size [default: 64]
? -t <TTL> ? ? ? ? ? ? ?Ping ttl [default: 64]
? -w <TIMEOUT> ? ? ? ? ?Ping timeout seconds [default: 1]
? -i <INTERVAL> ? ? ? ? Ping interval duration milliseconds [default: 1000]
? -h, --help ? ? ? ? ? ?Print help information
? -V, --version ? ? ? ? Print version information
實(shí)現(xiàn)Ping
pnet中提供了ICMP包的定義,socket2可以將定義好的ICMP包發(fā)送給目標(biāo)IP,另一種實(shí)現(xiàn)是通過(guò)pnet_transport::transport_channel發(fā)送原始數(shù)據(jù)包,但需要過(guò)濾結(jié)果而且權(quán)限要求較高。
首先定義ICMP包
let mut buf = vec![0; self.config.packet_size];
let mut icmp = MutableEchoRequestPacket::new(&mut buf[..]).ok_or(RingError::InvalidBufferSize)?;
icmp.set_icmp_type(IcmpTypes::EchoRequest); // 設(shè)置為EchoRequest類型
icmp.set_icmp_code(IcmpCodes::NoCode);
icmp.set_sequence_number(self.config.sequence + seq_offset); // 序列號(hào)
icmp.set_identifier(self.config.id);
icmp.set_checksum(util::checksum(icmp.packet(), 1)); // 校驗(yàn)函數(shù)
通過(guò)socket2發(fā)送請(qǐng)求
let socket = Socket::new(Domain::IPV4, Type::DGRAM, Some(Protocol::ICMPV4))?;
let src = SocketAddr::new(net::IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0);
socket.bind(&src.into())?; // 綁定源地址
socket.set_ttl(config.ttl)?;
socket.set_read_timeout(Some(Duration::from_secs(config.timeout)))?; // 超時(shí)配置
socket.set_write_timeout(Some(Duration::from_secs(config.timeout)))?;
???????// 發(fā)送
socket.send_to(icmp.packet_mut(), &self.dest.into())?;
最后處理相應(yīng),轉(zhuǎn)換成pnet中的EchoReplyPacket
let mut mem_buf = unsafe { &mut *(buf.as_mut_slice() as *mut [u8] as *mut [std::mem::MaybeUninit<u8>]) };
let (size, _) = self.socket.recv_from(&mut mem_buf)?;
???????// 轉(zhuǎn)換成EchoReply
let reply = EchoReplyPacket::new(&buf).ok_or(RingError::InvalidPacket)?;
至此,一次Ping請(qǐng)求完成。
周期性發(fā)送
Ping需要周期性的發(fā)送請(qǐng)求,比如秒秒請(qǐng)求一次,如果直接通過(guò)循環(huán)實(shí)現(xiàn),一次請(qǐng)求卡住將影響主流程,必須通過(guò)多線程來(lái)保證固定周期的發(fā)送。
發(fā)送請(qǐng)求
let send = Arc::new(AtomicU64::new(0)); // 統(tǒng)計(jì)發(fā)送次數(shù)
let _send = send.clone();
let this = Arc::new(self.clone());
let (sx, rx) = bounded(this.config.count as usize); // channel接受線程handler
thread::spawn(move || {
for i in 0..this.config.count {
let _this = this.clone();
sx.send(thread::spawn(move || _this.ping(i))).unwrap(); // 線程中運(yùn)行ping,并將handler發(fā)送到channel中
_send.fetch_add(1, Ordering::SeqCst); // 發(fā)送一次,send加1
if i < this.config.count - 1 {
thread::sleep(Duration::from_millis(this.config.interval));
}
}
drop(sx); // 發(fā)送完成關(guān)閉channel
});
- thread::spawn可以快速創(chuàng)建線程,但需要注意所有權(quán)的轉(zhuǎn)移,如果在線程內(nèi)部調(diào)用方法獲取變量,需要通過(guò)Arc原子引用計(jì)數(shù)
- send變量用來(lái)統(tǒng)計(jì)發(fā)送數(shù),原子類型,并且用Arc包裹;this是當(dāng)前類的Arc克隆,會(huì)轉(zhuǎn)移到線程中
- 第一個(gè)線程內(nèi)周期性調(diào)用ping(),并且其在單獨(dú)線程中運(yùn)行
- 通過(guò)bounded來(lái)定義channel(類似Golang中的chan),用來(lái)處理結(jié)果,發(fā)送完成關(guān)閉
處理結(jié)果
let success = Arc::new(AtomicU64::new(0)); // 定義請(qǐng)求成功的請(qǐng)求
let _success = success.clone();
let (summary_s, summary_r) = bounded(1); // channel來(lái)判斷是否處理完成
thread::spawn(move || {
for handle in rx.iter() {
if let Some(res) = handle.join().ok() {
if res.is_ok() {
_success.fetch_add(1, Ordering::SeqCst); // 如果handler結(jié)果正常,success加1
}
}
}
summary_s.send(()).unwrap(); // 處理完成
});
第二個(gè)線程用來(lái)統(tǒng)計(jì)結(jié)果,channel通道取出ping線程的handler,如果返回正常則加1
處理信號(hào)
let stop = signal_notify()?; // 監(jiān)聽(tīng)退出信號(hào)
select!(
recv(stop) -> sig => {
if let Some(s) = sig.ok() { // 收到退出信號(hào)
println!("Receive signal {:?}", s);
}
},
recv(summary_r) -> summary => { // 任務(wù)完成
if let Some(e) = summary.err() {
println!("Error on summary: {}", e);
}
},
);
通過(guò)select來(lái)處理信號(hào)(類似Golang中的select),到收到退出信號(hào)或者任務(wù)完成時(shí)繼續(xù)往下執(zhí)行。
信號(hào)處理
Golang中可以很方便的處理信號(hào),但在Rust中官方庫(kù)沒(méi)有提供類似功能,可以通過(guò)signal_hook與crossbeam_channel實(shí)現(xiàn)監(jiān)聽(tīng)退出信號(hào)
fn signal_notify() -> std::io::Result<Receiver<i32>> { let (s, r) = bounded(1); // 定義channel,用來(lái)異步接受退出信號(hào) let mut signals = signal_hook::iterator::Signals::new(&[SIGINT, SIGTERM])?; // 創(chuàng)建信號(hào) thread::spawn(move || { for signal in signals.forever() { // 如果結(jié)果到信號(hào)發(fā)送到channel中 s.send(signal).unwrap(); break; } }); Ok(r) // 返回接受channel }
其他
很多吐槽人Golang的錯(cuò)誤處理,Rust也不遑多讓,不過(guò)提供了?語(yǔ)法糖,也可以配合anyhow與thiserror來(lái)簡(jiǎn)化錯(cuò)誤處理。
驗(yàn)證
Ping域名/IP
ring git:(main) ?cargo run -- www.baidu.com?
PING www.baidu.com(103.235.46.40)
64 bytes from 103.235.46.40: icmp_seq=1 ttl=64 time=255.85ms
64 bytes from 103.235.46.40: icmp_seq=2 ttl=64 time=254.17ms
64 bytes from 103.235.46.40: icmp_seq=3 ttl=64 time=255.41ms
64 bytes from 103.235.46.40: icmp_seq=4 ttl=64 time=256.50ms
--- www.baidu.com ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3257.921ms
測(cè)試退出信息,運(yùn)行中通過(guò)Ctrl+C中止
cargo run 8.8.8.8 -c 10
PING 8.8.8.8(8.8.8.8)
64 bytes from 8.8.8.8: icmp_seq=1 ttl=64 time=4.32ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=64 time=3.02ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=64 time=3.24ms
^CReceive signal 2
???????--- 8.8.8.8 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2365.104ms
總結(jié)
Rust為了安全高效,通過(guò)引入所有權(quán)來(lái)解決GC問(wèn)題,也帶來(lái)了許多不便,編程時(shí)必須要考慮到變量的聲明周期、借用等問(wèn)題,所有語(yǔ)言都是在方便、性能、安全之間做權(quán)衡,要么程序員不方便,要么編譯器多做點(diǎn)功。換一個(gè)角度來(lái)說(shuō)Bug總是不可避免的,在編譯階段出現(xiàn)總好過(guò)運(yùn)行階段。
原文鏈接:https://blog.csdn.net/u012986012/article/details/128101040
相關(guān)推薦
- 2022-08-17 C++詳解如何通過(guò)模板實(shí)現(xiàn)元素的反序_C 語(yǔ)言
- 2022-04-14 Python實(shí)現(xiàn)簡(jiǎn)單購(gòu)物車小程序_python
- 2023-06-16 Visual?Studio?如何創(chuàng)建C/C++項(xiàng)目問(wèn)題_C 語(yǔ)言
- 2022-05-23 高效的數(shù)據(jù)同步工具DataX的使用及實(shí)現(xiàn)示例_數(shù)據(jù)庫(kù)其它
- 2022-06-13 docker從安裝入門到應(yīng)用部署及私有倉(cāng)庫(kù)搭建基礎(chǔ)命令_docker
- 2023-05-30 基于CUDA?out?of?memory的一種神奇解決方式_python
- 2022-08-15 linux交叉編譯依賴包
- 2022-06-15 go語(yǔ)言方法集為類型添加方法示例解析_Golang
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過(guò)濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支