专栏从0设计NTP时间服务客户端-NTP协议介绍和UDP编程实战

NTP入门介绍

这篇文章除了代码部分,其他均为我从其他人文章处搬运过来的,只阐述我个人的阅读思路,读者如果看到写的不好的地方敬请谅解,可以从文章底部原文跳转查看!

概述

网络时间协议,英文名称:Network Time Protocol(NTP) 是用来使计算机时间同步化的一种协议,它可以使计算机对其服务器或时钟源(如石英钟,GPS等等)做同步化,

它可以提供高精准度的时间校正(LAN上与标准间差小于1毫秒,WAN上几十毫秒), 且可介由加密确认的方式来防止恶毒的协议攻击。NTP的目的是在无序的Internet环境中提供精确和健壮的时间服务。

NTP 基于 UDP 报文进行传输, 使用的UDP端口号为 123.

使用 NTP 的目的是对网络内所有具有时钟的设备进行时钟同步, 使网络内所有设备的时钟保持一致, 从而使设备能够提供基于统一时间的多种应用.

对于运行 NTP 的本地系统, 既可以接收来自其他时钟源的同步, 又可以作为时钟源同步其他的时钟, 并且可以和其他设备相互同步.

工作原理

实现方式

无线时钟: 服务器系统可以通过串口连接一个无线时钟. 无线时钟接收 GPS 的卫星发射的信号来决定当前时间. 无线时钟是一个非常精确的时间源, 但是需要花一定的费用.

时间服务器: 还可以使用网络中 NTP 时间服务器, 通过这个服务器来同步网络中的系统的时钟.

域网内的同步: 如果只是需要在本局域网内进行系统间的时钟同步, 那么就可以使用局域网中任何一个系统的时钟. 你需要选择局域网中的一个节点的时钟作”权威的”的时间源, 然后其它的节点就只需要与这个时间源进行时间同步即可. 使用这种方式,

所有的节点都会使用一个公共的系统时钟, 但是不需要和局域网外的系统进行时钟同步. 如果一个系统在一个局域网的内部, 同时又不能使用无线时钟, 这种方式是最好的选择.

工作流程

这里目前我只列举【NTP服务端与客户端的交互过程】这一工作模式,还有许多其他的工作模式, 请参考这边文章,东西写的是真的详细,我的基本都是从这里搬运过来的.

NTP 协议简单分析

NTP服务端与客户端的交互过程

客户端和服务端都有一个时间轴,分别代表着各自系统的时间,当客户端想要同步服务端的时间时,客户端会构造一个NTP协议包发送到NTP服务端,客户端会记下此时发送的时间t0,经过一段网络延时传输后,服务器在t1时刻收到数据包,经过一段时间处理后在t2时刻向客户端返回数据包,

再经过一段网络延时传输后客户端在t3时刻收到NTP服务器数据包。

t0和t3是客户端时间系统的时间、t1和t2是NTP服务端时间系统的时间,它们是有区别的。 t0、t1、t2分别对应着server->cient NTP报文中的三个参数:

t0:origin timestamp(客户端发送数据包的时间)

t1: receive timestamp(服务器接收到数据包的时间)

t2: transmit timestamp(服务器返回数据包时间)

t3: client receive timestamp(客户端收到回复报文时本地的时间)。

延时和时间偏差计算

假设:客户端与服务端的时间系统的偏差定义为θ、网络的往/返延迟(单程延时)定义为δ

推导过程:

  1. 根据交互原理,可以列出方程组:

t0+θ+δ=t1
t2-θ+δ=t3 // 这里我也还没理解为啥是这样的? 如果看到这里的同学可以思考一下
  1. 求解方程组,得到以下结果:

θ=(t1-t0+t2-t3)/2  
δ=(t1-t0+t3-t2)/2 

记忆时可以采用极限法,分别假设延时和偏差为0.

客户端时间校准

对于时间要求不那么精准设备,客户端可把服务端端的返回时间t2固化为本地时间。

但是作为一个标准的通信协议,必须计算上网络的传输延时,需要把t2+δ固化为本地时间。

以上client时间校准算法只为理解过程,不代表真实做法

NTP报文

NTP报文示例

其中192.10.10.189为NTP的server端,192.10.10.32为client端。

NTP报文格式
  • NTP 有两种不同类型的报文, 一种是 时钟同步报文, 一种是 控制报文.

  • 控制报文 仅用于需要网络管理的场合, 对于时钟同步功能不是必需的, 暂不做分析.

  • 时钟同步报文格式如下:

NTP报文格式如上图所示,它的字段含义参考如下:

  • LI (Leap Indicator): 闰秒标识器, 长度为 2 Bits, 用来预警最近一分钟插入 1s 或者删除 1s.

C语言代码实现

#include <stdio.h>
#include "ntp.h"

int main (void) 
{
    const char * server_list[] = {
        "windows.com",    
        "time.windows.com",
    };

    for (int i = 0; i < sizeof(server_list) / sizeof(server_list[0]); i++) {
        struct tm * t = ntp_get_time(server_list[i]);
        if (t) {
            printf("current time is : %s\n", asctime(t));
            return 0;
        }
    }
    printf("ntp: get time failed, stop\n");
    return -1;
}
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include "ntp.h"

struct tm * ntp_get_time (const char * server) 
{
    printf("ntp: try to get time from %s\n", server);

    int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (sockfd < 0) {
        printf("ntp: create socket error\n");
        return NULL;
    }

    struct hostent * ent = gethostbyname(server);
    if (!ent) {
        printf("resolve host name failed.");
        goto failed;
    }

    char str[INET_ADDRSTRLEN];
    printf("server ip is: %s\n", inet_ntop(ent->h_addrtype, ent->h_addr, str, sizeof(str)));

    struct timeval tmo;
    tmo.tv_sec = NTP_REQ_TMO;
    tmo.tv_usec = 0;
    setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tmo, sizeof(tmo));

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(NTP_SERVER_PORT);
    addr.sin_addr = *(struct in_addr *)ent->h_addr;
    if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        printf("ntp: connect to ntp server failed.");
        goto failed;
    }

    for (int i = 0; i < NTP_REQ_RETRY; i++) {
        ntp_pkt_t pkt;
        memset(&pkt, 0, sizeof(pkt));
        pkt.LI_VN_Mode = (NTP_MODE << 0) | (NTP_VERSION << 3);

        if (send(sockfd, (const void *)&pkt, sizeof(ntp_pkt_t), 0) < 0) {
            printf("ntp: send to ntp failed.\n");
            break;
        }

        memset(&pkt, 0, sizeof(pkt));
        if (recv(sockfd, (void *)&pkt, sizeof(ntp_pkt_t), 0) < 0) {
            printf("ntp: recv failed.\n");
            continue;
        }

        pkt.trans_ts.seconds = ntohl(pkt.trans_ts.seconds);

        // 1970.1.1. - 1900.1.1
        time_t t = pkt.trans_ts.seconds - NTP_TIME_1900_1970;
        close(sockfd);
        return localtime(&t);
    }
failed:
    close(sockfd);
    return NULL;
}
#ifndef NTP_H
#define NTP_H

#include <time.h>
#include <stdint.h>

#define NTP_TIME_1900_1970  2208988800
#define NTP_SERVER_PORT     123
#define NTP_VERSION         3
#define NTP_MODE            3

#define NTP_REQ_TMO         3
#define NTP_REQ_RETRY       3


#pragma pack(1)
// 1000.2 s
// 1900.1.1 0:00:00  s
typedef struct _ntp_ts_t 
{
    uint32_t seconds;
    uint32_t fraction;
}ntp_ts_t;

typedef struct _ntp_pkt_t {
    uint8_t LI_VN_Mode;
    uint8_t stratum;
    uint8_t poll;
    uint8_t precision;
    uint32_t root_delay;
    uint32_t root_dispersion;
    uint32_t ref_id;
    ntp_ts_t ref_ts;
    ntp_ts_t orig_ts;
    ntp_ts_t recv_ts;
    ntp_ts_t trans_ts;
}ntp_pkt_t;

#pragma pack()

struct tm * ntp_get_time (const char * server);

#endif

文章来源: NTP入门介绍

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
rk3568 android11从入门到实战项目专栏是一本关于使用rk3568芯片开发Android 11应用的指南和实践教程。本专栏包含多个章节,主要围绕rk3568芯片和Android 11进行介绍和讲解,旨在帮助读者了解如何使用rk3568芯片进行应用开发,并将所学知识应用到实际项目中。 目录和介绍如下: 第一章:rk3568简介 本章主要介绍了rk3568芯片的基本信息,包括其性能特点、硬件规格以及适用的应用场景等。读者将通过本章了解到rk3568芯片的工作原理和基本特性。 第二章:Android 11简介 本章将介绍Android 11的特性和更新内容,包括新的用户界面、增强的隐私和安全特性,以及其他一些性能优化和功能改进。读者将了解到Android 11相较于之前的版本有哪些改进和新功能。 第三章:rk3568开发环境搭建 本章将指导读者如何搭建rk3568开发环境,包括SDK的安装、驱动的配置以及相关工具的设置等。通过本章的学习,读者将具备开发rk3568应用所需的基本环境。 第四章:Android应用开发基础 本章将介绍Android应用开发的基本知识,包括Android应用架构、布局设计、界面元素及交互等方面的内容。读者将通过本章学习到如何基于Android平台进行应用开发。 第五章:rk3568与Android 11集成开发 本章将介绍如何将rk3568芯片与Android 11进行集成开发,包括相关API的使用、硬件调用、外设控制等方面的内容。读者将学习到如何充分发挥rk3568与Android 11的协同作用,实现更强大的功能和扩展性。 第六章:实战项目案例 本章将以实际项目案例为基础,通过实践演练来加深读者对rk3568与Android 11开发的理解和掌握程度。项目案例将包括应用程序的设计、开发和测试等环节,并结合rk3568芯片的特性和Android 11的功能来实现一个完整的实际应用。 通过学习本专栏,读者将掌握使用rk3568芯片进行Android 11应用开发的基本技能和知识,并能够在实际项目中运用所学知识。这对于对rk3568芯片和Android 11开发感兴趣的从业人员和学习者来说,将是一本实用的学习资料和指导手册。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值