通过sntp同步系统时间
小型物联网设备,很少有接口提供给用户进行数据交互,那么我们设备的系统时间只能够通过获取网络时间后,再更新到本地。那么,就少不了使用sntp协议。
ntp协议:NTP(Network Time Protocol)网络时间协议基于UDP,用于网络时间同步的协议,使网络中的计算机时钟同步到UTC(通用协调时,可以理解为0时区的时间,领先东八区8个小时),再配合各个时区的偏移调整就能实现精准同步对时功能。
sntp协议:简单网络时间协议(Simple Network Time Protocol),由 NTP 改编而来,主要用来同步因特网中的计算机时钟。在 RFC2030 中定义。主要运用于小型的设备,占用内存小。
注意:NTP时间戳从1900年开始记秒数,而UNIX时间戳从1970年开始记秒数
一.常用时间函数介绍
首先介绍time.h中的常用api;
1.time();
函数原型:time_t time(time_t *t)
介绍:C 库函数 time_t time(time_t *seconds) 返回自纪元 Epoch(1970-01-01 00:00:00 UTC)起经过的时间,以秒为单位。如果 seconds 不为空,则返回值也存储在变量 seconds 中。
返回值:1970-01-01 00:00:00 起至今经过的时间,单位秒
2.ctime()
函数原型:char *ctime(const time_t *timer)
介绍:C 库函数 char *ctime(const time_t *timer) 返回一个表示当地时间的字符串,当地时间是基于参数 timer。
返回的字符串格式如下: Www(星期) Mmm (月)dd(日) hh:mm:ss(时分秒) yyyy(年)
返回值:是基于参数 timer计算,1970-01-01 00:00:00 经过timer时间后的时间字符串
3.localtime()
函数原型:struct tm *localtime(const time_t *timer)
介绍:C 库函数 struct tm *localtime(const time_t *timer) 使用 timer 的值来填充 tm 结构。timer 的值被分解为 tm 结构,并用本地时区表示。
返回值:填充tm结构体,表示1970-01-01 00:00:00 经过timer时间后的时间结构体
注意:年份输出需要+1900,月份输出需要+1才能得到准确的utc时间
tm结构体:
struct tm {
int tm_sec; /* 秒,范围从 0 到 59 */
int tm_min; /* 分,范围从 0 到 59 */
int tm_hour; /* 小时,范围从 0 到 23 */
int tm_mday; /* 一月中的第几天,范围从 1 到 31 */
int tm_mon; /* 月份,范围从 0 到 11 */
int tm_year; /* 自 1900 起的年数 */
int tm_wday; /* 一周中的第几天,范围从 0 到 6 */
int tm_yday; /* 一年中的第几天,范围从 0 到 365 */
int tm_isdst; /* 夏令时 */
};
4.mktime()
函数原型:time_t mktime(struct tm * timeptr)
介绍:C库函数time_t mktime(struct tm * timeptr)把timeptr所指向的结构转换为一个依据本地时区的time_t值。
参数:tm结构体,同上。
返回值:将参数timeptr所指的tm结构数据转换成从公元1970年1月1日0时0分0秒算起至今的UTC时间所经过的秒数。
二.功能实现
下面分析某项目将网络时间更新到本地的流程,平台可能存在差异,实现流程大致相同的。
源码分析
创建一个sntp客户端,目的从网络中获取时间,更新。
static void sntp_client(void)
{
/** Set this to 1 to allow config of SNTP server(s) by DNS name */
#if (!SNTP_SERVER_DNS)
struct ip4_addr test_addr;
#endif
hal_rtc_time_t r_time = {6,6,6,1,1,6,0};
hal_rtc_status_t ret = HAL_RTC_STATUS_OK;
//Set RTC to a incorrect time.
//设置默认系统时间
ret = hal_rtc_set_time(&r_time);
// LOG_I(sntp_client_main, "[%d]cur_time[%d:%d:%d]", ret, r_time.rtc_hour, r_time.rtc_min, r_time.rtc_sec);
// 设置sntp服务器ip地址或者DNS(目的地址),从什么服务器获取时间
/** Set this to 1 to allow config of SNTP server(s) by DNS name */
#if SNTP_SERVER_DNS
sntp_setservername(0, "1.cn.pool.ntp.org");
sntp_setservername(1, "1.hk.pool.ntp.org");
#else
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));
#endif
//初始化设置
sntp_init();
// LOG_I(sntp_client_main, "SNTP init done");
}
/**
* Initialize this module.
* Send out request instantly or after SNTP_STARTUP_DELAY(_FUNC).
*/
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_RESET_RETRY_TIMEOUT();
//创建udp,用于接收udp包,时间数据
sntp_pcb = udp_new();
LWIP_ASSERT("Failed to allocate udp pcb for sntp client", sntp_pcb != NULL);
if (sntp_pcb != NULL) {
//有数据,处理接收数据,同步到本地,由sntp_recv处理。
udp_recv(sntp_pcb, sntp_recv, NULL);
#if SNTP_STARTUP_DELAY
sys_timeout((u32_t)SNTP_STARTUP_DELAY_FUNC, sntp_request, NULL);
#else
sntp_request(NULL);
#endif
}
}
}
上述程序执行成功,再次获取本地时间,本地时间就会被更新。
uint32_t get_rtc_stamp_time(char * cur_time)
{
hal_rtc_time_t r_time;
hal_rtc_status_t ret = HAL_RTC_STATUS_OK;
time_t timep = 0;
struct tm p;
struct tm *tp;
//获取本地时间,是utc时间
ret = hal_rtc_get_time(&r_time);
if (ret == HAL_RTC_STATUS_OK)
{
// printf("cur_time[%d,%d,%d,%d]\r\n", r_time.rtc_year, r_time.rtc_mon, r_time.rtc_day, r_time.rtc_week);
// printf("[%d]cur_time[%d:%d:%d]\r\n", ret, r_time.rtc_hour, r_time.rtc_min, r_time.rtc_sec);
#if 1
p.tm_year = r_time.rtc_year + 2000 - 1900;//DATA%100 + 2000 -1900;
p.tm_mon = r_time.rtc_mon-1;//(DATA/100) %100 -1;
p.tm_mday = r_time.rtc_day;//DATA/10000;
p.tm_hour= r_time.rtc_hour;//(UTCTime / 10000);
p.tm_min= r_time.rtc_min;//(UTCTime / 100)%100;
p.tm_sec= r_time.rtc_sec;//UTCTime % 100;
//从utc时间转换成东八区时间
timep=mktime(&p);
timep = timep+28800;
tp = localtime(&timep); //0时区与东八区相差8个小时
//printf("%d %d %d %d %d %d\n",(tp->tm_year+1900), (tp->tm_mon+1), tp->tm_mday,tp->tm_hour, tp->tm_min, tp->tm_sec);
sprintf(cur_time,"%02d-%02d-%02d %02d:%02d:%02d",(tp->tm_year+1900), (tp->tm_mon+1), tp->tm_mday,tp->tm_hour, tp->tm_min, tp->tm_sec);
//下图log
printf("%02d-%02d-%02d %02d:%02d:%02d",(tp->tm_year+1900), (tp->tm_mon+1), tp->tm_mday,tp->tm_hour, tp->tm_min, tp->tm_sec);
#endif
//sprintf(cur_time,"%d-%02d-%02d %d %02d:%02d:%02d",r_time.rtc_year, r_time.rtc_mon, r_time.rtc_day, r_time.rtc_week,r_time.rtc_hour, r_time.rtc_min, r_time.rtc_sec);
}else{
printf("get_rtc_stamp_time fail\r\n");
}
return timep;
}
测试log如下图所示