获取sntp服务器时间同步本地时间

2019-07-20 12:22发布

本帖最后由 麦田稻草 于 2016-8-11 09:27 编辑

分享一下之前项目用到的同步网络时间的程序给大家,本例需要用到socket,要操作系统的支持,大家可以在 探索者F4 资料盘(A盘)4,程序源码3,扩展例程2,LWIP扩展例程网络实验10 NETCONN_WEBserver实验 的基础下测试。
rtc.h文件增加了如下结构体:
struct rtc_time
{
        u16 year;
        u8 month;
        u8 day;
        u8 week;
        u8 hour;
        u8 min;
        u8 sec;
        u8 ampm;
};

函数 void RTC_Get_Date(u8 *year,u8 *month,u8 *date,u8 *week);
改为 void RTC_Get_Date(u16 *year,u8 *month,u8 *date,u8 *week);
这些可以看着办,我主要是方便使用。

以下为代码,实现获取sntp服务器的时间,并同步更新本地时间(说明一下,我对时间精度要求不高,所以直接用服务器向客户发时间戳的时间来更新本地时间,经测试,误差小于一秒,这主要看网络状态和服务器的位置,我用的是深圳阿里云,我在广州):
[mw_shl_code=c,true]#include <string.h>  
#include <time.h>
#include "lwip/sockets.h"
#include "lwip/inet.h"
#include <netdb.h>  
#include "sntp.h"  
#include "rtc.h"

#define bzero(a, b)             memset(a, 0, b)

#define JAN_1970    2208988676UL+28701        /* 1970 - 1900 in seconds */  
#define NTP_SERVER_NAME "120.24.166.46"    //深圳阿里云
#define NTP_PORT    123  

typedef struct   
{  
    unsigned char LiVnMode;  
    unsigned char Stratum;  
    unsigned char Poll;  
    unsigned char Precision;  
    long int RootDelay;  
    long int RootDispersion;  
    char RefID[4];  
    long int RefTimeInt;  
    long int RefTimeFraction;  
    long int OriTimeInt;  
    long int OriTimeFraction;  
    long int RecvTimeInt;  
    long int RecvTimeFraction;  
    long int TranTimeInt;  
    long int TranTimeFraction;  
}STNP_Header;  
  

//获取sntp服务器的时间
int GetNTPTime(STNP_Header *H_SNTP)  
{  
    int sockfd=0;  
    struct sockaddr_in server;  
                fd_set set;   
    struct timeval timeout;  
     
               
    bzero((void*)H_SNTP, sizeof(STNP_Header));  //清零
    H_SNTP->LiVnMode = 0x1b;  //设置NTP报文格式,LI=0:无警告;  VN=3:NTP的版本号为3;  Mode=3:客户;
               
                //设置要连接的对方的IP地址和端口等属性
                bzero(&server,sizeof(server));//初始化结构体
                server.sin_family = AF_INET;    //设置地址家族
                server.sin_port = htons(NTP_PORT);  //设置端口
                if(inet_aton(NTP_SERVER_NAME,&server.sin_addr) <= 0)//设置地址
      printf("inet_pton error ");
        
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);  //创建一个UDP socket
    if(sockfd<0)  
    {  
        printf("sockfd error! ");  
        return -1;  
    }  
                //发送数据
    if(sendto(sockfd, (void*)H_SNTP, sizeof(STNP_Header), 0, (struct sockaddr*)&server, sizeof(server))<0)  
    {  
        printf("sendto error! ");  
        return -1;  
    }  
      
    FD_ZERO(&set);  //清除描述词组set的全部位
    FD_SET(sockfd, &set);//设置描述词组set中相关fd的位
    timeout.tv_sec = 5;  //等待超时时间
    timeout.tv_usec = 0;  
      
    if( select(sockfd+1, &set, NULL, NULL, &timeout) <= 0 )  
                {
                                printf("select wait timeout! ");  
        return -1;  
                }
    if(recv(sockfd, (void*)H_SNTP, sizeof(STNP_Header), 0)<0)  
                {
                                printf("recv error! ");
        return -1;  
                }
      
    close(sockfd);  
    return 0;  
}  

//同步时间
//返回0:成功,-1:失败
int SYNC_Time(void)  
{  
    STNP_Header HeaderSNTP;  
    time_t t1;  
                struct timeval tv;  
                struct rtc_time st;
                struct tm *time;
        
                //获取sntp服务器数据
    printf("sync time from %s ",NTP_SERVER_NAME);  
    if(GetNTPTime(&HeaderSNTP)<0)  
        return -1;         
                //从1900年1月1号0时0分0秒到服务器向客户发时间戳的时间,单位:秒
    t1 = ntohl(HeaderSNTP.TranTimeInt);  
                printf("t1:%u ",t1);
                //减去1900年至1970年的时间
    tv.tv_sec=t1-JAN_1970;  
    printf("UTC:%s ", ctime((const unsigned int *)&tv.tv_sec));  
               
                //把从1970-1-1零点零分到当前时间系统所偏移的秒数时间转换为日历时间
                //获得的tm结构体的时间,是已经进行过时区转化为本地时间,年份加上1900,月份加上1
                time=localtime((const unsigned int *)&tv.tv_sec);
                printf("sntp:%u-%u-%u,%u:%u:%u,星期%d ",(time->tm_year)+1900,(time->tm_mon)+1,time->tm_mday,time->tm_hour,time->tm_min,time->tm_sec,time->tm_wday);
               
                //获取rtc时间
                RTC_Get_Time(&st.hour,&st.min,&st.sec,&st.ampm);
                RTC_Get_Date(&st.year,&st.month,&st.day,&st.week);
                st.year=st.year+2000;
                printf("rtc1:%d-%d-%d,%d:%d:%d ",st.year,st.month,st.day,st.hour,st.min,st.sec);
                //时间相差超过1分钟就更新时间
                if(st.year!=(time->tm_year)+1900 | st.month!=(time->tm_mon)+1 | st.day!=time->tm_mday)
                {
                        RTC_Set_Date((time->tm_year)+1900-2000,(time->tm_mon)+1,time->tm_mday,time->tm_wday);
                        printf("RTC set date! ");
                }
                if(st.hour!=time->tm_hour | st.min!=time->tm_min)
                {
                        if(time->tm_hour<12) st.ampm=0;
                        else st.ampm=1;
                        RTC_Set_Time(time->tm_hour,time->tm_min,time->tm_sec,st.ampm);
                        printf("RTC set time! ");
                }
                RTC_Get_Time(&st.hour,&st.min,&st.sec,&st.ampm);
                RTC_Get_Date(&st.year,&st.month,&st.day,&st.week);
                st.year=st.year+2000;
                printf("rtc2:%d-%d-%d,%d:%d:%d ",st.year,st.month,st.day,st.hour,st.min,st.sec);
               
    return 0;  
}  
[/mw_shl_code]

友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
19条回答
麦田稻草
2019-07-22 13:56
a295055641 发表于 2016-8-12 13:00
用的这个例程发现断电后上电的时候没接网线,然后在接网线的话就不能进行对时了,if( select(sockfd+1, &se ...

断电后上电的时候没接网线,你是怎么能跳到sntp的函数里去的?原子哥的例程main函数里有初始化网卡的函数:
while(lwip_comm_init())         //lwip初始化
{
        LCD_ShowString(30,110,200,20,16,"Lwip Init failed!");         //lwip初始化失败
        delay_ms(500);
        LCD_Fill(30,110,230,130,WHITE);
        delay_ms(500);
}
网线没接不可能通过这里,将一直死循环在这里,你是直接用f407跑那个例程吗?

等待时间超时而退出去导致没有执行close(sockfd);,这条语句在我上传的例程里已经修改过来了,贴出来的那些代码是测试的时候的代码。
原子哥的例程里有个问题就是:上电的时候没有接网线,过一段时间再接上去也会一直卡死在初始化网卡那里
while(lwip_comm_init())         //lwip初始化
{
        LCD_ShowString(30,110,200,20,16,"Lwip Init failed!");         //lwip初始化失败
        delay_ms(500);
        LCD_Fill(30,110,230,130,WHITE);
        delay_ms(500);
}
原因是u8 lwip_comm_init(void)函数里的以下3句:
if(ETH_Mem_Malloc())return 1;                //内存申请失败
if(lwip_comm_mem_malloc())return 1;        //内存申请失败
if(LAN8720_Init())return 2;                        //初始化LAN8720失败
初始化LAN8720失败时没有释放前面2个函数申请的内存导致不断申请内存而内存不足失败。

一周热门 更多>