WIFI学习六(SNTP)

WIFI学习六(SNTP)SNPT SimpleNetwor 简单网络时间协议 用于跨广域网或局域网时间同步的协议 具有较高的精确度 几十毫秒 SNTP 是 NTP 协议的简化版 SNTP 协议采用客户端 服务器的工作方式 可以采用单播 点对点 或者广播 一点对多点 模式操作 单播模式下 客户端能够通过定期访问 SNTP 服务器来获取精确的时间信息 用于调整客户端自身的系统时间 广播模式下 SNTP 服务器周期性地发送消息给指定的 IP 广播地址或 IP 多播地址 SNTP 客户

简介

        SNPT(Simple Network Time Protocal简单网络时间协议)用于跨广域网或局域网时间同步的协议,具有较高的精确度(几十毫秒)。SNTP是NTP协议的简化版

SNTP的工作方式

        SNTP协议采用客户端/服务器的工作方式,可以采用单播(点对点)或者广播(一点对多点)模式操作。

        单播模式下,客户端能够通过定期访问SNTP服务器来获取精确的时间信息,用于调整客户端自身的系统时间。

WIFI学习六(SNTP)

        广播模式下。SNTP服务器周期性地发送消息给指定的IP广播地址或IP多播地址。SNTP客户端通过监听这些地址来获取时间信息

WIFI学习六(SNTP)

         网络中一般存在多台SNTP服务器,客户端会通过一定的算法选择最好的几台服务器使用。如果一台SNTP服务器在工作过程中发生异常,则会通知SNTP客户端,那么SNTP客户端就会丢弃发生故障的SNTP服务器发给它的时间信息,然后重新选择其他的SNTP服务器

SNTP校准原理

        SNTP协议主要是通过记录客户端向服务器发送数据包时的时间戳 t1服务器端接收到该数据包时的时间戳 t2服务器向客户端回应的时间戳 t3最后客户端接收到服务器回应时的时间戳 t4来计算客户端时间和服务端时间的偏差,从而进行校准时间操作,如下图所示:

WIFI学习六(SNTP)

         则 t1 和 t2之间的时间差为((t2 – t1) + (t4 – t3)) / 2

        数据包在网络上传输的时间为(t2 – t1) + (t4 – t3)

        获取到时间差后,设备会校准RTC时间,来保证时间的正确性。

源码分析

sntp_setoperatingmode()

        设置操作模式

函数原型:

void sntp_setoperatingmode(u8_t operating_mode);

参数:

operating_mode 操作模式

返回值:

实例:

sntp_setoperatingmode(SNTP_OPMODE_POLL); //设置为单播模式

sntp_setservername()

        设置要连接的服务器主机名字

函数原型:

void sntp_setservername(u8_t idx, const char *server) { LWIP_ASSERT_CORE_LOCKED(); if (idx < SNTP_MAX_SERVERS) { sntp_servers[idx].name = server; } }

参数:

idx 服务器编号。

server 服务器名字。

        可以看到,服务器可以设置多个,而数量的大小取决于SNTP_MAX_SERVERS参数。这个值可以根据需求自行设置。

返回值:

实例:

sntp_setservername(0, "1.cn.pool.ntp.org"); sntp_setservername(1, "1.hk.pool.ntp.org");

sntp_setserver()

        设置要连接的服务器主机IP

函数原型:

void sntp_setserver(u8_t idx, const ip_addr_t *server) { LWIP_ASSERT_CORE_LOCKED(); if (idx < SNTP_MAX_SERVERS) { if (server != NULL) { sntp_servers[idx].addr = (*server); } else { ip_addr_set_zero(&sntp_servers[idx].addr); } #if SNTP_SERVER_DNS sntp_servers[idx].name = NULL; #endif } }

参数:

idx 服务器编号。

server 服务器IP地址。

返回值:

实例:

struct ip4_addr test_addr; IP4_ADDR(&test_addr, 213, 161, 194, 93); sntp_setserver(0, (const ip_addr_t *)(&test_addr)); IP4_ADDR(&test_addr, 129, 6, 15, 29); sntp_setserver(1, (const ip_addr_t *)(&test_addr));

sntp_init()

        初始化SNTP模块,并创建UDP连接。

函数原型:

void sntp_init(void) { #ifdef SNTP_SERVER_ADDRESS #if SNTP_SERVER_DNS sntp_setservername(0, SNTP_SERVER_ADDRESS); #else #error SNTP_SERVER_ADDRESS string not supported SNTP_SERVER_DNS==0 #endif #endif /* SNTP_SERVER_ADDRESS */ if (sntp_pcb == NULL) { sntp_pcb = udp_new_ip_type(IPADDR_TYPE_ANY); //创建一个UDP PCB(协议控制块),IPV4+IPV6 LWIP_ASSERT("Failed to allocate udp pcb for sntp client", sntp_pcb != NULL); if (sntp_pcb != NULL) { udp_recv(sntp_pcb, sntp_recv, NULL); //设置udp接收回调 if (sntp_opmode == SNTP_OPMODE_POLL) { //是单播模式 SNTP_RESET_RETRY_TIMEOUT(); #if SNTP_STARTUP_DELAY sys_timeout((u32_t)SNTP_STARTUP_DELAY_FUNC, sntp_request, NULL); #else sntp_request(NULL); //请求 #endif } else if (sntp_opmode == SNTP_OPMODE_LISTENONLY) { //广播模式 ip_set_option(sntp_pcb, SOF_BROADCAST); udp_bind(sntp_pcb, IP_ANY_TYPE, SNTP_PORT); //绑定 } } } }

参数:

返回值:

udp_new_ip_type()

        创建一个针对IP类型的PCB(Protocol Control Block协议控制块)

函数原型:

struct udp_pcb *udp_new_ip_type(u8_t type) { struct udp_pcb *pcb; LWIP_ASSERT_CORE_LOCKED(); pcb = udp_new(); //创建一个UDP #if LWIP_IPV4 && LWIP_IPV6 if (pcb != NULL) { IP_SET_TYPE_VAL(pcb->local_ip, type); IP_SET_TYPE_VAL(pcb->remote_ip, type); } #else LWIP_UNUSED_ARG(type); #endif /* LWIP_IPV4 && LWIP_IPV6 */ return pcb; }

参数:

type IP地址类型,类型如下

enum lwip_ip_addr_type { / IPv4 */ IPADDR_TYPE_V4 = 0U, / IPv6 */ IPADDR_TYPE_V6 = 6U, / IPv4+IPv6 ("dual-stack") */ IPADDR_TYPE_ANY = 46U };

返回值:

NULL失败

其他值 UDP的协议控制块

实例:

static struct udp_pcb* sntp_pcb; sntp_pcb = udp_new_ip_type(IPADDR_TYPE_ANY); if (sntp_pcb != NULL) { //成功 }

udp_recv()

        设置一个接收回调,从UDP的协议控制块中

函数原型:

void udp_recv(struct udp_pcb *pcb, udp_recv_fn recv, void *recv_arg) { LWIP_ASSERT_CORE_LOCKED(); LWIP_ERROR("udp_recv: invalid pcb", pcb != NULL, return); /* remember recv() callback and user data */ pcb->recv = recv; pcb->recv_arg = recv_arg; }

参数:

pcb  UDP协议控制块,由udp_new_ip_type返回所得。

recv UDP接收的回调函数、

recv_arg UDP接收的回调函数的参数

返回值:

实例:

udp_recv(sntp_pcb, sntp_recv, NULL); 

sntp_request()

        发送SNTP请求

函数原型:

static void sntp_request(void *arg) { ip_addr_t sntp_server_address; err_t err; LWIP_UNUSED_ARG(arg); /* initialize SNTP server address */ #if SNTP_SERVER_DNS if (sntp_servers[sntp_current_server].name) { /* always resolve the name and rely on dns-internal caching & timeout */ ip_addr_set_zero(&sntp_servers[sntp_current_server].addr); err = dns_gethostbyname(sntp_servers[sntp_current_server].name, &sntp_server_address, sntp_dns_found, NULL); if (err == ERR_INPROGRESS) { /* DNS request sent, wait for sntp_dns_found being called */ LWIP_DEBUGF(SNTP_DEBUG_STATE, ("sntp_request: Waiting for server address to be resolved.\n")); return; } else if (err == ERR_OK) { sntp_servers[sntp_current_server].addr = sntp_server_address; } } else #endif /* SNTP_SERVER_DNS */ { sntp_server_address = sntp_servers[sntp_current_server].addr; err = (ip_addr_isany_val(sntp_server_address)) ? ERR_ARG : ERR_OK; } if (err == ERR_OK) { LWIP_DEBUGF(SNTP_DEBUG_TRACE, ("sntp_request: current server address is %s\n", ipaddr_ntoa(&sntp_server_address))); sntp_send_request(&sntp_server_address); } else { /* address conversion failed, try another server */ LWIP_DEBUGF(SNTP_DEBUG_WARN_STATE, ("sntp_request: Invalid server address, trying next server.\n")); sys_timeout((u32_t)SNTP_RETRY_TIMEOUT, sntp_try_next_server, NULL); } }

参数:

可以忽略

返回值:

实例:

sntp_request(NULL); //请求

sntp_recv()

        UDP从SNTP的PCB的接收回调

函数原型:

static void sntp_recv(void *arg, struct udp_pcb* pcb, struct pbuf *p, const ip_addr_t *addr,u16_t port)

参数:

arg:接收的数据传参

pcb:协议控制块

p:接收到的数据

addr:IP地址

Port:端口

返回值:

整个函数中,需要注意的是sntp_process函数,该函数相当于对接收到的数据进行数据处理

WIFI学习六(SNTP)

sntp_process()

        对接收到的时间数据进行处理

函数原型:

static void sntp_process(const struct sntp_timestamps *timestamps) { s32_t sec; u32_t frac; sec = (s32_t)lwip_ntohl(timestamps->xmit.sec); frac = lwip_ntohl(timestamps->xmit.frac); #if SNTP_COMP_ROUNDTRIP # if SNTP_CHECK_RESPONSE >= 2 if (timestamps->recv.sec != 0 || timestamps->recv.frac != 0) # endif { LOGI("Roundtrip compare processing...\n"); s32_t dest_sec; u32_t dest_frac; u32_t step_sec; SNTP_GET_SYSTEM_TIME_NTP(dest_sec, dest_frac); step_sec = (dest_sec < sec) ? ((u32_t)sec - (u32_t)dest_sec) : ((u32_t)dest_sec - (u32_t)sec); /* In order to avoid overflows, skip the compensation if the clock step * is larger than about 34 years. */ if ((step_sec >> 30) == 0) { s64_t t1, t2, t3, t4; /* t4 the time sntp client recv the reply, or the current time. */ t4 = SNTP_SEC_FRAC_TO_S64(dest_sec, dest_frac); /* t3 the time sntp server transmitt the reply. */ t3 = SNTP_SEC_FRAC_TO_S64(sec, frac); /* t1 the time sntp client send a request. */ t1 = SNTP_TIMESTAMP_TO_S64(timestamps->orig); /* t2 the time sntp server recv the request. */ t2 = SNTP_TIMESTAMP_TO_S64(timestamps->recv); /* Clock offset calculation according to RFC 4330 */ t4 += ((t2 - t1) + (t3 - t4)) / 2; sec = (s32_t)((u64_t)t4 >> 32); frac = (u32_t)((u64_t)t4); } } #endif /* SNTP_COMP_ROUNDTRIP */ time_t tim = (u32_t)((sec) + DIFF_SEC_1970_2036); /* change system time and/or the update the RTC clock */ SNTP_SET_SYSTEM_TIME(sec); /* display local time from GMT time */ //LWIP_DEBUGF(SNTP_DEBUG_TRACE, ("sntp_process: %s", ctime(&tim))); LOGI("sntp_process: %s", ctime(&tim)); LWIP_UNUSED_ARG(tim); }

参数:

timestamps 时间戳,结构体类型如下:

struct sntp_timestamps { #if SNTP_COMP_ROUNDTRIP || SNTP_CHECK_RESPONSE >= 2 struct sntp_time orig; //发送前时间戳 struct sntp_time recv; //接收到时间戳 #endif struct sntp_time xmit; //传输 };

sntp_time类型如下:

struct sntp_time { u32_t sec; //秒 u32_t frac; //毫秒 };

返回值:

        从上述代码中可以看到,实际上正如在文章开头所讲的那样SNTP是根据时间差来进行校准时间,计算后得到结果后,需要关注的函数是SNTP_SET_SYSTEM_TIME,后续的数据处理,都在该函数中进行。

#define SNTP_SET_SYSTEM_TIME(sec) sntp_set_system_time((u32_t)((sec)+DIFF_SEC_1970_2036),0)

sntp_set_system_time()

        sntp根据计算得到的结果来设置系统时间。

函数原型:

static void sntp_set_system_time(time_t t, u32_t us) { struct tm *gt = NULL; hal_rtc_time_t r_time; hal_rtc_status_t st = HAL_RTC_STATUS_OK; LOGI("sntp_set_system_time input: %"U32_F" s,%"U32_F" us\n",t,us); gt = gmtime(&t); if (gt == NULL) { gt = localtime(&t); } r_time.rtc_year = (gt->tm_year % 100); r_time.rtc_mon = gt->tm_mon + 1; r_time.rtc_day = gt->tm_mday; r_time.rtc_week = gt->tm_wday; r_time.rtc_hour = gt->tm_hour; r_time.rtc_min = gt->tm_min; r_time.rtc_sec = gt->tm_sec; st = hal_rtc_set_time(&r_time); if (sntp_cb) { LOGI("sntp callback\n"); sntp_cb(r_time); } LOGI("sntp(%d %d %d ",gt->tm_wday,gt->tm_mon,gt->tm_mday); LOGI("%d:%d:%d %d)\n",gt->tm_hour,gt->tm_min,gt->tm_sec,gt->tm_year); LOGI("sntp(atx):(%d:%d)\n",gt->tm_isdst,gt->tm_yday); LOGI("sntp st1(%u)\n",st); st = hal_rtc_get_time(&r_time); (void)st; LOGI("sntp(%u %u %u ",r_time.rtc_week,r_time.rtc_mon,r_time.rtc_day); LOGI("%u:%u:%u %u)\n",r_time.rtc_hour,r_time.rtc_min,r_time.rtc_sec,r_time.rtc_year); LOGI("sntp st2(%u)\n",st); }

参数:

t 时间戳

us  微秒

返回值:

        该函数将数据进行划分计算,得到最终需要的数据结果,然后设置本地的RTC时间。设置完成后,调用sntp_cb函数来进行用户自己的操作。

 sntp_set_callback()

        设置用户自定义的回调结果

函数原型:

void sntp_set_callback(sntp_callback callback) { sntp_cb = callback; }

参数:

callback 用户自定义回调结果

返回值:

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/211204.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月18日 下午10:57
下一篇 2026年3月18日 下午10:58


相关推荐

  • stream的groupingby_handlemapping

    stream的groupingby_handlemappinggroupingBy用于分组,toMap用于list转map测试代码:1.建一个实体类,测试中用packagecom.xhx.java;/***xuhaixing*2018/7/2021:43**/publicclassStudent{privateStringname;privateStringsex;priva…

    2022年8月20日
    8
  • 详解深浅拷贝

    详解深浅拷贝前言说道拷贝大家可能联想到深拷贝和浅拷贝概念 然而在 swift 却很少涉及宝贝问题 即使 swift 下依然有 copy 和 mutableCopy 方法 但终其原因 创建单纯的 swift 类并不需要继承 NSObject 而是使用 swift 类 另外很多牵扯拷贝问题的数组和字典 在 swift 对应于 Array 和 Dictionary 不同于 NSArray 和 NSDictionary swift 的 Array 和 Dictionary 是值类型 赋值后并不需要担心其拷贝问题 所以主要结束 Objective C 中的深拷贝和浅拷贝

    2026年3月17日
    2
  • template模板字符串

    template模板字符串underscore 自定义书写 template 模板字符串

    2026年3月18日
    3
  • RubyMine 4 注册 序列号

    RubyMine 4 注册 序列号RubyMine 注册序列号现在最新版本是 4 0 2 使用这个 key 仍然有效 对于这个没什么好说的 如有必要 请支持购买正版 No 1 name rubymineLice 70414 02VG0BeoZbwm e s830EDlHcWg8

    2026年3月16日
    1
  • 按位取反运算符的运算举例_按位与按位或按位异或运算符

    按位取反运算符的运算举例_按位与按位或按位异或运算符前言:位运算符是用来对二进制位进行操作的c语言中有6种位运算符:&按位与[链接]:https://blog.csdn.net/weixin_42837024/article/details/98736834|按位或[链接]:https://blog.csdn.net/weixin_42837024/article/details/98745019^按位异或[链接]…

    2022年8月15日
    11
  • OpenClaw Windows 安装与排障实录

    OpenClaw Windows 安装与排障实录

    2026年3月15日
    5

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号