Rust SNTP客户端库sntpc的使用:轻量级、高精度的网络时间协议(SNTP)实现

Rust SNTP客户端库sntpc的使用:轻量级、高精度的网络时间协议(SNTP)实现

sntpc test

简介

这个crate提供了向NTP服务器发送请求并处理响应的方法,提取接收到的时间戳。

支持的SNTP协议版本:

  • SNTPv4

文档

更多关于这个crate的信息可以在文档中找到

使用示例

依赖项

[dependencies]
sntpc = { version = "0.6", features = ["sync"] }

应用代码

use sntpc::{sync::get_time, NtpContext, StdTimestampGen};

use std::net::{SocketAddr, ToSocketAddrs, UdpSocket};
use std::thread;
use std::time::Duration;

#[allow(dead_code)]
const POOL_NTP_ADDR: &str = "pool.ntp.org:123";
#[allow(dead_code)]
const GOOGLE_NTP_ADDR: &str = "time.google.com:123";

fn main() {
    #[cfg(feature = "log")]
    if cfg!(debug_assertions) {
        simple_logger::init_with_level(log::Level::Trace).unwrap();
    } else {
        simple_logger::init_with_level(log::Level::Info).unwrap();
    }

    let socket =
        UdpSocket::bind("0.0.0.0:0").expect("Unable to crate UDP socket");
    socket
        .set_read_timeout(Some(Duration::from_secs(2)))
        .expect("Unable to set UDP socket read timeout");

    for addr in POOL_NTP_ADDR.to_socket_addrs().unwrap() {
        let ntp_context = NtpContext::new(StdTimestampGen::default());
        let result = get_time(addr, &socket, ntp_context);

        match result {
            Ok(time) => {
                assert_ne!(time.sec(), 0);
                let seconds = time.sec();
                let microseconds = u64::from(time.sec_fraction()) * 1_000_000
                    / u64::from(u32::MAX);
                println!("Got time from [{POOL_NTP_ADDR}] {addr}: {seconds}.{microseconds}");

                break;
            }
            Err(err) => println!("Err: {err:?}"),
        }

        thread::sleep(Duration::new(2, 0));
    }
}

no_std支持

有一个关于如何使用smoltcp堆栈的示例可用,这应该提供了关于如何引导no_std网络和时间戳工具以使用sntpc库的一般想法。

async支持

从版本0.5开始,默认接口是async的。如果你想使用同步接口,请阅读下面关于sync特性的内容。

还有no_std支持与async特性,但需要Rust >= 1.75-nightly版本。

sync支持

sntpc crate默认是async的,因为大多数嵌入式系统框架使用异步方法,例如:

  • RTIC
  • embassy

如果你需要完全同步的接口,可以在sntpc::sync子模块和相应的sync特性中使用。如果有人需要同步套接字支持,当前async的NtpUdpSocket trait可以完全同步地实现。

贡献

贡献总是受欢迎的!如果你有想法,最好先和我讨论一下,以确保没有浪费精力。如果已经有相关的开放问题,请随意解决。

许可证

这个项目遵循3-Clause BSD License

完整示例代码

基于上面的示例,这里是一个更完整的Rust SNTP客户端实现:

use sntpc::{sync::get_time, NtpContext, StdTimestampGen};
use std::net::{SocketAddr, ToSocketAddrs, UdpSocket};
use std::time::{SystemTime, UNIX_EPOCH, Duration};

const NTP_SERVERS: &[&str] = &[
    "pool.ntp.org:123",
    "time.google.com:123",
    "time.windows.com:123",
];

fn main() {
    // 创建UDP套接字
    let socket = UdpSocket::bind("0.0.0.0:0").expect("Failed to bind UDP socket");
    socket.set_read_timeout(Some(Duration::from_secs(2)))
        .expect("Failed to set socket read timeout");

    // 创建NTP上下文
    let ntp_context = NtpContext::new(StdTimestampGen::default());
    
    // 尝试从多个NTP服务器获取时间
    for server in NTP_SERVERS {
        match server.to_socket_addrs() {
            Ok(mut addresses) => {
                if let Some(addr) = addresses.next() {
                    println!("Querying NTP server: {}", server);
                    
                    match get_time(addr, &socket, ntp_context) {
                        Ok(time) => {
                            let seconds = time.sec();
                            let microseconds = u64::from(time.sec_fraction()) * 1_000_000 / u64::from(u32::MAX);
                            
                            // 转换为系统时间
                            let ntp_time = UNIX_EPOCH + Duration::new(seconds, (microseconds * 1000) as u32);
                            
                            println!("Successfully retrieved time from {}:", server);
                            println!("  NTP timestamp: {}.{}", seconds, microseconds);
                            println!("  Local time:    {:?}", SystemTime::now());
                            println!("  NTP time:      {:?}", ntp_time);
                            
                            // 计算本地时间与NTP时间的差异
                            if let Ok(diff) = SystemTime::now().duration_since(ntp_time) {
                                println!("  Local clock is ahead by: {:?}", diff);
                            } else if let Ok(diff) = ntp_time.duration_since(SystemTime::now()) {
                                println!("  Local clock is behind by: {:?}", diff);
                            }
                            
                            return;
                        }
                        Err(e) => {
                            println!("Error querying {}: {:?}", server, e);
                        }
                    }
                }
            }
            Err(e) => {
                println!("Failed to resolve {}: {:?}", server, e);
            }
        }
    }
    
    println!("Failed to retrieve time from any NTP server");
}

这个示例展示了:

  1. 从多个NTP服务器尝试获取时间
  2. 处理网络和解析错误
  3. 将NTP时间转换为系统时间
  4. 比较本地时间和NTP时间
  5. 详细的错误处理和日志输出

你可以根据需要调整NTP服务器列表或添加额外的错误处理逻辑。


1 回复

Rust SNTP客户端库sntpc使用指南

sntpc是一个轻量级、高精度的Rust SNTP(Simple Network Time Protocol)客户端库实现,用于获取网络时间。

特性

  • 轻量级实现,无复杂依赖
  • 高精度时间同步
  • 支持IPv4和IPv6
  • 简单的API接口
  • 超时配置支持

使用方法

添加依赖

Cargo.toml中添加:

[dependencies]
sntpc = "0.6"

基本示例

use sntpc::{SntpClient, NtpContext, NtpTimestampGenerator};

fn main() {
    // 创建SNTP客户端
    let client = SntpClient::new();
    
    // 配置上下文(可选)
    let ctx = NtpContext::default();
    
    // 获取时间(使用默认的NTP服务器)
    let result = client.synchronize("pool.ntp.org", ctx).unwrap();
    
    println!("UTC时间: {:?}", result.datetime());
    println!("本地时间: {:?}", result.datetime().to_local());
    println!("时间戳: {}", result.timestamp());
}

自定义NTP服务器

use sntpc::SntpClient;

fn main() {
    let client = SntpClient::new();
    let result = client.synchronize("time.google.com", Default::default()).unwrap();
    
    println!("从Google NTP服务器获取的时间: {:?}", result.datetime());
}

带超时配置

use std::time::Duration;
use sntpc::{SntpClient, NtpContext};

fn main() {
    let client = SntpClient::new();
    let mut ctx = NtpContext::default();
    ctx.timeout = Some(Duration::from_secs(3)); // 3秒超时
    
    match client.synchronize("pool.ntp.org", ctx) {
        Ok(result) => println!("时间: {:?}", result.datetime()),
        Err(e) => eprintln!("获取时间失败: {}", e),
    }
}

自定义时间戳生成器

use sntpc::{SntpClient, NtpContext, NtpTimestampGenerator};
use std::time::{SystemTime, UNIX_EPOCH};

struct CustomTimestampGenerator;

impl NtpTimestampGenerator for CustomTimestampGenerator {
    fn init(&mut self) {}
    
    fn timestamp_sec(&self) -> u64 {
        SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs()
    }
    
    fn timestamp_subsec_micros(&self) -> u32 {
        SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .subsec_micros()
    }
}

fn main() {
    let client = SntpClient::new();
    let mut ctx = NtpContext::new(CustomTimestampGenerator);
    
    let result = client.synchronize("pool.ntp.org", ctx).unwrap();
    println!("自定义时间戳生成器获取的时间: {:?}", result.datetime());
}

高级用法

获取原始NTP数据

use sntpc::{SntpClient, NtpContext};

fn main() {
    let client = SntpClient::new();
    let result = client.synchronize("pool.ntp.org", Default::default()).unwrap();
    
    println!("原始NTP数据:");
    println!("Leap Indicator: {:?}", result.leap_indicator());
    println!("Version: {}", result.version());
    println!("Mode: {:?}", result.mode());
    println!("Stratum: {}", result.stratum());
    println!("Poll Interval: {}", result.poll_interval());
    println!("Precision: {}", result.precision());
    println!("Root Delay: {}", result.root_delay());
    println!("Root Dispersion: {}", result.root_dispersion());
    println!("Reference ID: {:?}", result.reference_id());
    println!("Reference Timestamp: {:?}", result.reference_timestamp());
    println!("Origin Timestamp: {:?}", result.origin_timestamp());
    println!("Receive Timestamp: {:?}", result.receive_timestamp());
    println!("Transmit Timestamp: {:?}", result.transmit_timestamp());
}

完整示例代码

// 完整示例:使用sntpc库从多个NTP服务器获取时间并计算平均值
use sntpc::{SntpClient, NtpContext};
use std::time::{Duration, SystemTime};

fn main() {
    // 定义要查询的NTP服务器列表
    let ntp_servers = [
        "pool.ntp.org",
        "time.google.com",
        "time.windows.com",
        "ntp.aliyun.com"
    ];
    
    // 创建SNTP客户端和上下文
    let client = SntpClient::new();
    let mut ctx = NtpContext::default();
    ctx.timeout = Some(Duration::from_secs(2)); // 设置2秒超时
    
    // 收集所有成功的时间结果
    let mut successful_results = Vec::new();
    
    for server in ntp_servers {
        match client.synchronize(server, ctx) {
            Ok(result) => {
                println!("从 {} 获取时间成功: {:?}", server, result.datetime());
                successful_results.push(result);
            }
            Err(e) => {
                eprintln!("从 {} 获取时间失败: {}", server, e);
            }
        }
    }
    
    // 如果有成功结果,计算平均时间
    if !successful_results.is_empty() {
        let total_secs: i64 = successful_results.iter()
            .map(|r| r.datetime().timestamp())
            .sum();
        
        let avg_secs = total_secs / successful_results.len() as i64;
        
        // 将平均秒数转换为SystemTime
        let avg_time = SystemTime::UNIX_EPOCH + Duration::from_secs(avg_secs as u64);
        
        println!("\n平均网络时间: {:?}", avg_time);
        println!("使用的服务器数量: {}", successful_results.len());
    } else {
        eprintln!("所有NTP服务器查询均失败");
    }
}

注意事项

  1. 默认使用pool.ntp.org作为NTP服务器,但你可以指定任何可用的NTP服务器
  2. 对于高精度需求,建议使用多个NTP服务器并取平均值
  3. 网络延迟会影响时间同步的精度
  4. 在嵌入式系统中使用时,可能需要自定义时间戳生成器

sntpc库提供了简单而有效的方式来同步网络时间,适合需要轻量级NTP/SNTP客户端的应用场景。

回到顶部