<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
這兩年Rust火的一塌糊塗,甚至都燒到了前端,再不學習怕是要落伍了。最近翻了翻檔案,寫了個簡單的Ping應用練練手,被所有權折騰的夠嗆,相比起Golang上手難度大很多,現將開發中的一些問題總結如下,所有原始碼見ring。
實現一個Ping,功能包含:
命令列解析
實現ICMP協定,pnet包中已經包含了ICMP包定義,可以使用socket2庫傳送
週期性傳送Ping,通過多執行緒傳送,再彙總結果
監聽退出訊號
系統庫std::env::args可以解析命令列引數,但對於一些複雜的引數使用起來比較繁瑣,更推薦clap。利用clap的註解,通過結構體定義命令列引數
/// 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表示開啟短命名,預設為第一個字母,可以指定;default_value_t設定預設值 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可以方便的指定引數命名、預設值、解析方法等,執行結果如下
➜ 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
pnet中提供了ICMP包的定義,socket2可以將定義好的ICMP包傳送給目標IP,另一種實現是通過pnet_transport::transport_channel傳送原始封包,但需要過濾結果而且許可權要求較高。
首先定義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); // 設定為EchoRequest型別 icmp.set_icmp_code(IcmpCodes::NoCode); icmp.set_sequence_number(self.config.sequence + seq_offset); // 序列號 icmp.set_identifier(self.config.id); icmp.set_checksum(util::checksum(icmp.packet(), 1)); // 校驗函數
通過socket2傳送請求
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)))?; // 超時設定 socket.set_write_timeout(Some(Duration::from_secs(config.timeout)))?; // 傳送 socket.send_to(icmp.packet_mut(), &self.dest.into())?;
最後處理相應,轉換成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)?; // 轉換成EchoReply let reply = EchoReplyPacket::new(&buf).ok_or(RingError::InvalidPacket)?;
至此,一次Ping請求完成。
Ping需要週期性的傳送請求,比如秒秒請求一次,如果直接通過迴圈實現,一次請求卡住將影響主流程,必須通過多執行緒來保證固定週期的傳送。
傳送請求
let send = Arc::new(AtomicU64::new(0)); // 統計傳送次數 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(); // 執行緒中執行ping,並將handler傳送到channel中 _send.fetch_add(1, Ordering::SeqCst); // 傳送一次,send加1 if i < this.config.count - 1 { thread::sleep(Duration::from_millis(this.config.interval)); } } drop(sx); // 傳送完成關閉channel });
處理結果
let success = Arc::new(AtomicU64::new(0)); // 定義請求成功的請求 let _success = success.clone(); let (summary_s, summary_r) = bounded(1); // channel來判斷是否處理完成 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結果正常,success加1 } } } summary_s.send(()).unwrap(); // 處理完成 });
第二個執行緒用來統計結果,channel通道取出ping執行緒的handler,如果返回正常則加1
處理訊號
let stop = signal_notify()?; // 監聽退出訊號 select!( recv(stop) -> sig => { if let Some(s) = sig.ok() { // 收到退出訊號 println!("Receive signal {:?}", s); } }, recv(summary_r) -> summary => { // 任務完成 if let Some(e) = summary.err() { println!("Error on summary: {}", e); } }, );
通過select來處理訊號(類似Golang中的select),到收到退出訊號或者任務完成時繼續往下執行。
訊號處理
Golang中可以很方便的處理訊號,但在Rust中官方庫沒有提供類似功能,可以通過signal_hook與crossbeam_channel實現監聽退出訊號
fn signal_notify() -> std::io::Result<Receiver<i32>> { let (s, r) = bounded(1); // 定義channel,用來非同步接受退出訊號 let mut signals = signal_hook::iterator::Signals::new(&[SIGINT, SIGTERM])?; // 建立訊號 thread::spawn(move || { for signal in signals.forever() { // 如果結果到訊號傳送到channel中 s.send(signal).unwrap(); break; } }); Ok(r) // 返回接受channel }
很多吐槽人Golang的錯誤處理,Rust也不遑多讓,不過提供了?語法糖,也可以配合anyhow與thiserror來簡化錯誤處理。
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
測試退出資訊,執行中通過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
Rust為了安全高效,通過引入所有權來解決GC問題,也帶來了許多不便,程式設計時必須要考慮到變數的宣告週期、借用等問題,所有語言都是在方便、效能、安全之間做權衡,要麼程式設計師不方便,要麼編譯器多做點功。換一個角度來說Bug總是不可避免的,在編譯階段出現總好過執行階段。
到此這篇關於利用Rust實現一個簡單的Ping應用的文章就介紹到這了,更多相關Rust實現Ping內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45