溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么用Rust實現一個簡單的Ping應用

發(fā)布時間:2022-12-07 09:58:30 來源:億速云 閱讀:172 作者:iii 欄目:開發(fā)技術

這篇文章主要介紹了怎么用Rust實現一個簡單的Ping應用的相關知識,內容詳細易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇怎么用Rust實現一個簡單的Ping應用文章都會有所收獲,下面我們一起來看看吧。

目標

實現一個Ping,功能包含:

命令行解析

實現ICMP協議,pnet包中已經包含了ICMP包定義,可以使用socket2庫發(fā)送

周期性發(fā)送Ping,通過多線程發(fā)送,再匯總結果

監(jiān)聽退出信號

命令行解析

系統(tǒng)庫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

實現Ping

pnet中提供了ICMP包的定義,socket2可以將定義好的ICMP包發(fā)送給目標IP,另一種實現是通過pnet_transport::transport_channel發(fā)送原始數據包,但需要過濾結果而且權限要求較高。

首先定義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發(fā)送請求

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)))?;

// 發(fā)送
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請求完成。

周期性發(fā)送

Ping需要周期性的發(fā)送請求,比如秒秒請求一次,如果直接通過循環(huán)實現,一次請求卡住將影響主流程,必須通過多線程來保證固定周期的發(fā)送。

發(fā)送請求

let send = Arc::new(AtomicU64::new(0)); // 統(tǒng)計發(fā)送次數
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發(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ā)送完成關閉channel
});
  • thread::spawn可以快速創(chuàng)建線程,但需要注意所有權的轉移,如果在線程內部調用方法獲取變量,需要通過Arc原子引用計數

  • send變量用來統(tǒng)計發(fā)送數,原子類型,并且用Arc包裹;this是當前類的Arc克隆,會轉移到線程中

  • 第一個線程內周期性調用ping(),并且其在單獨線程中運行

  • 通過bounded來定義channel(類似Golang中的chan),用來處理結果,發(fā)送完成關閉

處理結果

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(); // 處理完成
});

第二個線程用來統(tǒng)計結果,channel通道取出ping線程的handler,如果返回正常則加1

處理信號

let stop = signal_notify()?; // 監(jiān)聽退出信號
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),到收到退出信號或者任務完成時繼續(xù)往下執(zhí)行。

信號處理

Golang中可以很方便的處理信號,但在Rust中官方庫沒有提供類似功能,可以通過signal_hook與crossbeam_channel實現監(jiān)聽退出信號

fn signal_notify() -> std::io::Result<Receiver<i32>> {
    let (s, r) = bounded(1); // 定義channel,用來異步接受退出信號

    let mut signals = signal_hook::iterator::Signals::new(&[SIGINT, SIGTERM])?; // 創(chuàng)建信號

    thread::spawn(move || {
        for signal in signals.forever() { // 如果結果到信號發(fā)送到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實現一個簡單的Ping應用”這篇文章的內容就介紹到這里,感謝各位的閱讀!相信大家對“怎么用Rust實現一個簡單的Ping應用”知識都有一定的了解,大家如果還想學習更多知識,歡迎關注億速云行業(yè)資訊頻道。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI