Rust SNTP客户端库sntpc的使用:轻量级、高精度的网络时间协议(SNTP)实现
Rust SNTP客户端库sntpc的使用:轻量级、高精度的网络时间协议(SNTP)实现
简介
这个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");
}
这个示例展示了:
- 从多个NTP服务器尝试获取时间
- 处理网络和解析错误
- 将NTP时间转换为系统时间
- 比较本地时间和NTP时间
- 详细的错误处理和日志输出
你可以根据需要调整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服务器查询均失败");
}
}
注意事项
- 默认使用
pool.ntp.org
作为NTP服务器,但你可以指定任何可用的NTP服务器 - 对于高精度需求,建议使用多个NTP服务器并取平均值
- 网络延迟会影响时间同步的精度
- 在嵌入式系统中使用时,可能需要自定义时间戳生成器
sntpc
库提供了简单而有效的方式来同步网络时间,适合需要轻量级NTP/SNTP客户端的应用场景。