NTP校时源码(一个教完整的NTP服务项目示例,摘自国嵌实验手册)

序号

功能需求

说明

1

基本功能

1.  根据NTP 服务协议,与外部 ntp 服务器进行通讯。

2.  解析ntp 协议包,从中提取有效信息。

3.  通过计算得到网络延时、本地时差,并计算出本地的标准时间。

4.  重新设定本地时间。

2

扩展功能

1.  设置校准时间间隔,即每隔指定时间校准一次。

2.  实现服务日志,对启动服务后的程序状态进行记录。

3.  实现后台运行,使程序脱离终端在后台运行,程序需通过解析配置文档获得配置信息。

3

服务模式

1.  交互式运行:在终端交互式运行服务程序,交互式获取配置信息。

2.  守护进程模式:作为守护进程在后台运行,通过配置文件获取配置信息,配置文件默认路劲:/etc/ntpclient/ntpclient.conf

4

启动脚本

1.  默认路径:/etc/ini.d/ntpclient

2.  启动选项:start ,后台启动服务。

3.  启动选项:stop ,终止后台服务。

4.  启动选项:status ,查看服务状态。

5.  启动选项:restart ,重启服务。

5

安装脚本

1.  安装选项:-i ,安装程序。

2.  安装选项:-d ,卸载程序。

实验要求视频讲解:

<<Linux应用程序开发班 >>/ 8 -NTP 网络协议实现 / 培训视频 /NTP网络协议实现 -项目要求 .avi

 

背景知识:
1.  培训视频:

Linux 应用程序开发班》 / 8 -NTP 网络协议实现 /培训视频 /NTP网络协议实现 .avi

2.  NTP协议介绍:

网络时间协议(NTP )是一种通过因特网服务于计算机时钟的同步时间协议。它提供了一种同步时间机制,能在庞大而复杂多样的因特网中用光速调整时间分配。它使用的是可返回时间设计方案,其特点是:时间服务器是一种分布式子网,能自我组织操作、分层管理配置,经过有线或无线方式同步逻辑时钟达到国家标准时间。此外,通过本地路由选择运算法则及时间后台程序,服务器可以重新分配标准时间。

NTP 的校时涉及三个概念 — 时间偏差、时间延迟及差量,它们与指定参考时钟都是相关联的。时钟偏差表示本地时钟与参考时钟之间的偏差数;时间延迟表示在指定时间内由一方发送消息到另一方接收到消息间的延时时间;差量表示了相对于参考时钟本地时钟的最大偏差错误。因为大多数主机时间服务器通过其它对等时间服务器达到同步,所以这三个参量都有两个组成部分:其一是由对等决定的部分,这部分是相对于原始标准时间的参考来源而言;其二是由主机衡量的部分,这部分是相对于对等而言。每一部分在协议中都是独立维持的,从而可以使错误控制和子网本身的管理操作变得容易。它们不仅提供了偏移和延迟的精密测量,而且提供了明确的最大错误范围,这样用户接口不但可以决定时间,而且可以决定时间的准确度。

3.  NTP协议包结构:

进行网络协议实现时最重要的是了解协议数据格式。除了可扩展部分,基本的NTP 数据包有 48  个字节,其中 NTP 包头 16  字节,时间戳 32  个字节。其协议格式下表所示。

2

5

8

16

24

32bit

LI 2

VN 3

Mode 3

Stratum 8

Poll 8

Precision 8

Root Delay

Root Dispersion

Reference Identifier

Reference timestamp 64

Originate Timestamp 64

Receive Timestamp 64

Transmit Timestamp 64

Key Identifier optional )( 32

Message digest optional )( 128

·  LI:跳跃指示器,警告在当月最后一天的最终时刻插入的迫近闺秒(闺秒)。 

·  VN:版本号。 

·  Mode:模式。该字段包括以下值:0-预留;1-对称行为;3-客户机;4-服务器;5-广播;6-NTP 控制信息 

·  Stratum:对本地时钟级别的整体识别。 

·  Poll:有符号整数表示连续信息间的最大间隔。 

·  Precision:有符号整数表示本地时钟精确度。 

·  Root Delay:有符号固定点序号表示主要参考源的总延迟,很短时间内的位15到16间的分段点。 

·  Root Dispersion:无符号固定点序号表示相对于主要参考源的正常差错,很短时间内的位15到16间的分段点。 

·  Reference Identifier:识别特殊参考源。 

·  Originate Timestamp:这是向服务器请求分离客户机的时间,采用64位时标(Timestamp)格式。 

·  Receive Timestamp:这是向服务器请求到达服务器的时间,采用64位时标(Timestamp)格式。 

·  Transmit Timestamp:这是向客户机答复分离服务器的时间,采用64位时标(Timestamp)格式。 

Authenticator(Optional):当实现了 NTP 认证模式,主要标识符和信息数字域就包括已定义的信息认证代码(MAC)信息。 

4.  Daemon 进程概念:

Daemon是长时间运行的进程,通常在系统启动后就运行,在系统关闭时才结束。一般说Daemon程序在后台运行,是因为它没有控制终端,无法和前台的用户交互。Daemon程序一般都作为服务程序使用,等待客户端程序与它通信。我们也把运行的Daemon程序称作守护进程。

比如,我们的网络服务程序,可以在完成创建套接口,绑定套接口,设置套接口为监听模式后,变成守护进程进入后台执行而不占用控制终端,这是网络服务程序的常用模式。Linux下的网络服务程序,如samba、FTP、Telnet一般都是由守护进程(Daemon)来实现的。Linux的守护进程一般都命名为*d的形式,如httpd,telnetd等等。守护进程一旦脱离了终端,退出就成了问题。可以使用命令:ps aux|grep *,其中*号为进程名,找到相应进程的ID,再使用命令:kill -SIGTERM ID,终止它。

5.  Daemon 程序编写:

编写Daemon程序有一些基本的规则,以避免不必要的麻烦。

(1)  首先是程序运行后调用fork,并让父进程退出。子进程获得一个新的进程ID,但 继承了父进程的进程组ID。

(2)  调用setsid创建一个新的session,使自己成为新session和新进程组的leader,并使进程没有控制终端(tty)。

(3)  改变当前工作目录至根目录,以免影响可加载文件系统。或者也可以改变到某些特定的目录。

(4)  设置文件创建mask为0,避免创建文件时权限的影响。

(5)  关闭不需要的打开文件描述符。因为Daemon程序在后台执行,不需要于终端交互,通常就关闭STDIN、STDOUT和STDERR。其它根据实际情况处理。

另一个问题是Daemon程序不能和终端交互,也就无法使用printf方法输出信息了。我们可以使用syslog机制来实现信息的输出,方便程序的调试。当然,你也可以把这些信息输出到自己的日志文件中查看。

下面给出一段Daemon程序的例子:

 

#include <unistd.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <stdlib.h>

#include <stdio.h>

#include <syslog.h>

#include <signal.h>

 

int daemon_init(void)

{

    pid_t pid;

    if((pid = fork()) < 0)

        return(-1);

    else if(pid != 0)

        exit(0);  /* parent exit */

     /* child continues */

    setsid();  /* become session leader */

    chdir("/");  /* change working directory */

    umask(0);  /* clear file mode creation mask */

    close(0);  /* close stdin */

    close(1);  /* close stdout */

    close(2);  /* close stderr */

    return(0);

}

 

void sig_term(int signo)

{

    if(signo == SIGTERM)  //catched signal sent by kill(1) command 

    {

        syslog(LOG_INFO, "program terminated.");

        closelog();

        exit(0);

    }

}

 

int main(void)

{

    daemon_init();

    openlog("daemontest", LOG_PID, LOG_USER);

    syslog(LOG_INFO, "program started.");

    signal(SIGTERM, sig_term);  /* arrange to catch the signal */

    while(1)

    {

        sleep(1);  /* put your main program here */

    }

    return(0);

}

1.  src子目录:

实验代码stdinc.h,这是程序使用到的标准头文件的一个集合,这样做不仅可以减少编码工作量,也不至于因为修改了头文件的内容而减慢编译的速度。

#include   <stdio.h>

#include   <stdlib.h>

#include   <string.h>

#include   <stdarg.h>

#include   <unistd.h>

#include   <netinet/in.h>

#include   <sys/socket.h>

#include   <sys/types.h>

#include   <arpa/inet.h>

#include   <netdb.h>

#include   <sys/time.h>

#include   <time.h>

#include   <sys/select.h>

#include   <stdbool.h>

#include   <signal.h>

#include   <sys/param.h>

#include   <sys/stat.h>

#include   <fcntl.h>

实验代码def.h,这里主要是一些宏定义和结构类型定义。另外,调试宏函数PDEBUG的定义方式值得大家注意。

 

#ifndef  __DEF_H__

     #define  __DEF_H__

 

#define  CMD_NAME    "ntpclient"

//ntp时间从年开始,本地时间从年开始,这是两者之间的差值

#define  JAN_1970   0x83aa7e80      //3600s*24h*(365days*70years+17days)

//x*10^(-6)*2^32 微妙数转 NtpTime 结构的 fraction 部分

#define  NTPFRAC(x) (4294 * (x) + ((1981 * (x)) >> 11))  

//NTPFRAC的逆运算

#define  USEC(x) (((x) >> 12) - 759 * ((((x) >> 10) + 32768) >> 16))

 

#define  DEF_NTP_SERVER  "210.72.145.44"    //国家授时中心 ip

//#define DEF_NTP_SERVER "stdtime.gov.hk" //香港标准时间

//#define DEF_NTP_SERVER "pool.ntp.org"     //ntp官方时间

#define  DEF_NTP_PORT   123

#define  DEF_PSEC       10

#define  DEF_PMIN       0

#define  DEF_PHOUR      0

#define  DEF_TIMEOUT    10

#define  DEF_LOGEN      1

#define  DEF_LOGPATH     "/tmp/" CMD_NAME ".log"

 

#define  CONF_PATH       "/etc/" CMD_NAME "/" CMD_NAME ".conf"

#define  DAE_PID_PATH    "/var/run/" CMD_NAME ".pid"

#define  INIT_PATH       "/etc/init.d/" CMD_NAME

 

//ntp时间戳结构

typedef   struct   

{

     unsigned   int  integer;

     unsigned   int  fraction;

} NtpTime;

 

//校准信息结构

typedef   struct

{

     struct  timeval dlytime;

     struct  timeval offtime;

    

  • 0
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
好的,这是一个关于编程的问题,我可以为您提供帮助。以下是一个简单的 C 语言函数,用于连接至 NTP 服务器并获取当前时间。具体实现可能会因服务器而异。请注意,在使用此代码之前,您需要先安装相关的开发库并确保您的设备可以连接到互联网。 ``` #include <stdio.h> #include <stdlib.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <time.h> /** * 连接到 NTP 服务器并获取当前时间 * * @param hostname NTP 服务器的主机名或 IP 地址 * @return 当前时间的时间戳,以秒为单位,如果无法连接到服务器,则返回 -1 */ long ntp_get_time(char* hostname) { int sockfd; char buffer[48] = {0}; struct sockaddr_in servaddr; struct hostent *server; long timestamp; // 创建 socket sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (sockfd < 0) { perror("socket error"); return -1; } // 获取服务器的 IP 地址 server = gethostbyname(hostname); if (server == NULL) { perror("gethostbyname error"); return -1; } // 设置服务器地址 bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; bcopy((char *)server->h_addr, (char *)&servaddr.sin_addr.s_addr, server->h_length); servaddr.sin_port = htons(123); // NTP 端口号 // 发送 NTP 请求 buffer[0] = 0x1B; // NTP 协议版本号、模式等信息 if (sendto(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { perror("sendto error"); return -1; } // 接收服务器的响应 if (recv(sockfd, buffer, sizeof(buffer), 0) < 0) { perror("recv error"); return -1; } // 解析服务器响应的时间戳 timestamp = ntohs(*(unsigned short*)&buffer[40]); // 获取秒数 timestamp -= 2208988800UL; // 将 1900 年 1 月 1 日作为起始时间 return timestamp; } ``` 该函数使用 UDP 协议连接至 NTP 服务器,发送时间同步请求并解析服务器的响应,最后返回以秒为单位的当前时间戳。如果无法连接到服务器,则返回 -1。请记得为此函数添加必要的注释。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值