NTP网络校时(北斗卫星授时设备)技术核心源码让网络时间同步不再难
安徽京准科技提供 卫信号 ahjzsz
NTP校时源码(一个教完整的NTP服务项目示例,摘自国嵌实验手册)
目录(?)[-]
实验要求视频讲解l 背景知识培训视频NTP协议介绍NTP协议包结构Daemon 进程概念Daemon 程序编写src子目录Initd子目录安装脚本
序号 功能需求 说明
1 基本功能 1. 根据NTP 服务协议,与外部 ntp 服务器进行通讯。解析ntp 协议包,从中提取有效信息。通过计算得到网络延时、本地时差,并计算出本地的标准时间。重新设定本地时间。
2 扩展功能 1. 设置校准时间间隔,即每隔指定时间校准一次。实现服务日志,对启动服务后的程序状态进行记录。实现后台运行,使程序脱离终端在后台运行,程序需通过解析配置文档获得配置信息。
3 服务模式 1. 交互式运行:在终端交互式运行服务程序,交互式获取配置信息。守护进程模式:作为守护进程在后台运行,通过配置文件获取配置信息,配置文件默认路劲:/etc/ntpclient/ntpclient.conf 。
4 启动脚本 1. 默认路径:/etc/ini.d/ntpclient 。启动选项:start ,后台启动服务。启动选项:stop ,终止后台服务。启动选项:status ,查看服务状态。启动选项:restart ,重启服务。
5 安装脚本 1. 安装选项:-i ,安装程序。安装选项:-d ,卸载程序。
实验要求视频讲解:
<<Linux应用程序开发班 >>/ 第 8 天 -NTP 网络协议实现 / 培训视频 /NTP网络协议实现 -项目要求 .avi
背景知识:培训视频:
《Linux 应用程序开发班》 / 第 8 天 -NTP 网络协议实现 /培训视频 /NTP网络协议实现 .avi 。NTP协议介绍:
网络时间协议(NTP )是一种通过因特网服务于计算机时钟的同步时间协议。它提供了一种同步时间机制,能在庞大而复杂多样的因特网中用光速调整时间分配。它使用的是可返回时间设计方案,其特点是:时间服务器是一种分布式子网,能自我组织操作、分层管理配置,经过有线或无线方式同步逻辑时钟达到国家标准时间。此外,通过本地路由选择运算法则及时间后台程序,服务器可以重新分配标准时间。
NTP 的校时涉及三个概念 — 时间偏差、时间延迟及差量,它们与指定参考时钟都是相关联的。时钟偏差表示本地时钟与参考时钟之间的偏差数;时间延迟表示在指定时间内由一方发送消息到另一方接收到消息间的延时时间;差量表示了相对于参考时钟本地时钟的最大偏差错误。因为大多数主机时间服务器通过其它对等时间服务器达到同步,所以这三个参量都有两个组成部分:其一是由对等决定的部分,这部分是相对于原始标准时间的参考来源而言;其二是由主机衡量的部分,这部分是相对于对等而言。每一部分在协议中都是独立维持的,从而可以使错误控制和子网本身的管理操作变得容易。它们不仅提供了偏移和延迟的精密测量,而且提供了明确的最大错误范围,这样用户接口不但可以决定时间,而且可以决定时间的准确度。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)信息。Daemon 进程概念:
Daemon是长时间运行的进程,通常在系统启动后就运行,在系统关闭时才结束。一般说Daemon程序在后台运行,是因为它没有控制终端,无法和前台的用户交互。Daemon程序一般都作为服务程序使用,等待客户端程序与它通信。我们也把运行的Daemon程序称作守护进程。
比如,我们的网络服务程序,可以在完成创建套接口,绑定套接口,设置套接口为监听模式后,变成守护进程进入后台执行而不占用控制终端,这是网络服务程序的常用模式。Linux下的网络服务程序,如samba、FTP、Telnet一般都是由守护进程(Daemon)来实现的。Linux的守护进程一般都命名为*d的形式,如httpd,telnetd等等。守护进程一旦脱离了终端,退出就成了问题。可以使用命令:ps aux|grep,其中号为进程名,找到相应进程的ID,再使用命令:kill -SIGTERM ID,终止它。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);
}
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的定义方式值得大家注意。
#ifndefDEF_H
#defineDEF_H
#define CMD_NAME “ntpclient”
//ntp时间从年开始,本地时间从年开始,这是两者之间的差值
#define JAN_1970 0x83aa7e80 //3600s24h(365days70years+17days)
//x10(-6)*232 微妙数转 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 “” //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;
struct timeval newtime;
} NtpServResp;
//ntp客户端配置结构,对应 ntpclient.conf 中各项
typedef struct
{
char servaddr[256];
unsigned int port;
int psec;
int pmin;
int phour;
int timeout;
bool logen;
char logpath[256];
} NtpConfig;
#ifndef DEBUG
#define PDEBUG(fmt, args…)/
do {} while (0)
#else
#define PDEBUG(fmt, args…)/
printf( “[%s:%d]” fmt,func,LINE, # #args)
#endif
#endif
实验代码ntpclient.c,这是核心代码,在代码开头使用了extern申明了外部定义的函数,主要是日志、交互运行和后台运行的相关函数。前面提过,在项目代码中还使用select函数提供端口监控和超时控制的功能。在学习源代码之前,你可以通过下面的表格了解这个函数的用法。
select
函数功能 发送通过远端主机指定套接字数据
头文件 #include < select .h>
#include < time .h>
#include <sys/types.h>
#include <unistd.h>
函数原型 int select(int nfds, fd_set *readfds, fd_set *writefds,
f d_set *exceptfds, struct timevaltimeout);
参数说明 nfds :监控的文件描述符集中最大的文件描述符值+1 。
select()函数的接口主要是建立在一种叫 ‘fd_set’ 类型的基础上。它 (‘fd_set’) 是一组文件描述符 (fd) 的集合。由于 fd_set 类型的长度在不同平台上不同,因此应该用一组标准的宏定义来处理此类变量:
fd_set set;
FD_ZERO(&set); /将 set 清零/
FD_SET(fd, &set); /将 fd 加入 set/
FD_CLR(fd, &set); /将 fd 从 set 中清除/
FD_ISSET(fd, &set); /如果 fd 在 set 中则真 */
readfds :可读文件描述符集,监控该集合直到其中有元素可读或超时。
writefds :可写文件描述符集,监控该集合直到其中有元素可写或超时。
exceptfds :异常文件描述符集,监控该集合直到其中有元素发生异常或超时。
timeout :等待超时阀值。
NULL指针代表无限等待,否则是指向 timeval 结构的指针,代表最长等待时间。 ( 如果其中 tv_sec 和 tv_usec 都等于 0, 则文件描述符的状态不被影响,但函数并不挂起 )
返回值 成功返回 就绪的 fd数 , 超时 返回0 , 错误返回-1 。
范例 见项目代码
#include “stdinc.h”
#include “def.h”
extern int log_record( char *record, …);
extern void log_dlytime( struct timeval dly);
extern void log_offtime( struct timeval off);
extern void log_newtime( struct timeval new );
extern void sig_log_term( int signo);
extern int get_cfg_from_menu();
extern void init_daemon( void );
extern int record_pid_to_file( const char *pidfn);
extern int get_cfg_from_file( const char *cfn);
//配置信息变量
NtpConfig NtpCfg;
/*
构造并发送 ntp 协议包*/
void send_packet( int fd)
{
unsigned int data[12];
int ret;
struct timeval now;
#define LI 0 //协议头中的元素
#define VN 3 //版本
#define MODE 3 //模式 : 客户端请求
#define STRATUM 0
#define POLL 4 //连续信息间的最大间隔
#define PREC -6 //本地时钟精度
if ( sizeof (data) != 48)
{
printf( “data size error!/n” );
exit(1);
}
memset(( char *)data, 0, sizeof (data));
data[0] = htonl((LI << 30) | (VN << 27) | (MODE << 24)
| (STRATUM << 16) | (POLL << 8) | (PREC & 0xff));
data[1] = htonl(1 << 16);
data[2] = htonl(1 << 16);
//获得本地时间
gettimeofday(&now, NULL);
data[10] = htonl(now.tv_sec + JAN_1970);data[11] = htonl(NTPFRAC(now.tv_usec));PDEBUG( "tratime.fraction=%d/n" , data[11]);ret = send(fd, data, 48, 0);PDEBUG( "send packet to ntp server, ret: %d/n" , ret);
}
/*
获得并解析 ntp 协议包
@sock – 与时间服务器通讯的套接字
@resp – 从服务器应答中提取的有用信息
*/
bool get_server_time( int sock, NtpServResp *resp)
{
int ret;
unsigned int data[12];
NtpTime oritime, rectime, tratime, destime;
struct timeval offtime, dlytime;
struct timeval now;
bzero(data, sizeof (data));
ret = recvfrom (sock, data, sizeof (data), 0, NULL, 0);
if (ret == -1)
{
PDEBUG( “recvfrom was failed!/n” );
log_record( “recvfrom was failed!被迫终止 !/n” );
exit(1);
}
else if (ret == 0)
{
PDEBUG( “recvfrom receive 0!/n” );
return false ;
}
gettimeofday(&now, NULL);
destime.integer = now.tv_sec + JAN_1970;
destime.fraction = NTPFRAC (now.tv_usec);
#define DATA(i) ntohl((( unsigned int *)data)[i])
oritime.integer = DATA(6);
oritime.fraction = DATA(7);
rectime.integer = DATA(8);
rectime.fraction = DATA(9);
tratime.integer = DATA(10);
tratime.fraction = DATA(11);
#undef DATA
//与 send_packet 中发送的 tratime.faction 一致
PDEBUG( “oritime.faction=%d/n” ,htonl(oritime.fraction));
//Originate Timestamp T1 客户端发送请求的时间//Receive Timestamp T2 服务器接收请求的时间//Transmit Timestamp T3 服务器答复时间//Destination TimestampT4 客户端接收答复的时间//网络延时 d 和服务器与客户端的时差 t//d = (T2 - T1) + (T4 - T3); t = [(T2 - T1) + (T3 - T4)] / 2;
#define MKSEC(ntpt) ((ntpt).integer - JAN_1970)
#define MKUSEC(ntpt) (USEC((ntpt).fraction))
#define TTLUSEC(sec,usec) (( long long )(sec)*1000000 + (usec))
#define GETSEC(us) ((us)/1000000)
#define GETUSEC(us) ((us)%1000000)
long long orius, recus, traus, desus, offus, dlyus;orius = TTLUSEC(MKSEC(oritime), MKUSEC(oritime));recus = TTLUSEC(MKSEC(rectime), MKUSEC(rectime));traus = TTLUSEC(MKSEC(tratime), MKUSEC(tratime));desus = TTLUSEC(now.tv_sec, now.tv_usec);offus = ((recus - orius) + (traus - desus))/2;dlyus = (recus - orius) + (desus - traus);offtime.tv_sec = GETSEC(offus);offtime.tv_usec = GETUSEC(offus);dlytime.tv_sec = GETSEC(dlyus);dlytime.tv_usec = GETUSEC(dlyus);struct timeval new ;//粗略校时//new.tv_sec = tratime.integer - JAN_1970;//new.tv_usec = USEC(tratime.fraction);//精确校时new .tv_sec = destime.integer - JAN_1970 + offtime.tv_sec;new .tv_usec = USEC(destime.fraction) + offtime.tv_usec;resp->newtime = new ;resp->dlytime = dlytime;resp->offtime = offtime;return true ;
}
/*
更新本地时间@newtime – 要新的时间*/
int mod_localtime( struct timeval newtime)
{
//只有 root 用户拥有修改时间的权限
if (getuid() != 0 &&geteuid () != 0)
{
log_record( “不是 root 用户,无法进行时间校准,被迫终止 !/n” );
exit(1);
}
if (settimeofday(&newtime, NULL) == -1)
{
log_record( “设置时间失败 !/n” );
return -1;
}
else
{
log_record( “设置时间成功 !/n” );
}
return 0;
}
/*
连接时间服务器
*/
int ntp_conn_server( const char *servname, int port)
{
int sock;
int addr_len = sizeof ( struct sockaddr_in);
struct sockaddr_in addr_src; //本地 socket <netinet/in.h>
struct sockaddr_in addr_dst; //服务器 socket
//UDP数据报套接字
sock = socket(PF_INET, SOCK_DGRAM, 0);
if (sock == -1)
{
log_record( “套接字创建失败,被迫终止 ! /n” );
exit(1);
}
memset(&addr_src, 0, addr_len);
addr_src.sin_family = AF_INET;
addr_src.sin_port = htons(0);
addr_src.sin_addr.s_addr = htonl(INADDR_ANY); //<arpa/inet.h>
//绑定本地地址
if (-1 == bind(sock, ( struct sockaddr *) &addr_src, addr_len))
{
log_record( “绑定失败,被迫终止 !/n” );
exit (1);
}
memset(&addr_dst, 0, addr_len);
addr_dst.sin_family = AF_INET;
addr_dst.sin_port = htons(port);
struct hostent *host = gethostbyname(servname); //<netdb.h>
if (host == NULL)
{
log_record( “主机名获取错误,被迫终止 !/n” );
exit (1);
}
memcpy (&(addr_dst.sin_addr.s_addr), host->h_addr_list[0], 4);
PDEBUG( “Connecting to NTP_SERVER: %s ip: %s port: %d…/n” ,
servname, inet_ntoa(addr_dst.sin_addr), port);
if (-1 == connect(sock, ( struct sockaddr *) &addr_dst, addr_len))
{
log_record( “连接服务器失败,被迫终止 !/n” );
exit (1);
}
return sock;
}
/*
装入默认的配置*/
void load_default_cfg()
{
strcpy(NtpCfg.servaddr, DEF_NTP_SERVER);
NtpCfg.port = DEF_NTP_PORT;
NtpCfg.psec = DEF_PSEC;
NtpCfg.pmin = DEF_PMIN;
NtpCfg.phour = DEF_PHOUR;
NtpCfg.timeout = DEF_TIMEOUT;
NtpCfg.logen = DEF_LOGEN;
strcpy(NtpCfg.logpath, DEF_LOGPATH);
}
/*
初始化 ntp 客户端程序
*/
int ntpclient_init( int argc, char **argv)
{
int ret;
bzero(&NtpCfg, sizeof (NtpCfg));
if (!access(DAE_PID_PATH, F_OK))
{
PDEBUG(DAE_PID_PATH “已经存在,服务正在后台运行 !/n” );
exit(1);
}
//装入默认配置
load_default_cfg();
if (1 == argc || !strcmp(argv[1], “-D” )) //后台运行
{
//从配置文件获取配置
get_cfg_from_file(CONF_PATH);
init_daemon(); //初始化为 Daemonret= record_pid_to_file(DAE_PID_PATH); //记录 pid if (ret==-1){PDEBUG(DAE_PID_PATH "创建失败 !/n" );}log_record( "NTP服务开始在后台为您校准时间 !/n" );
}
else if (2 == argc && !strcmp(argv[1], “-i” )) //交互式运行
{
while ((ret=get_cfg_from_menu())0); if (ret
{
exit(1);
}
log_record( “NTP服务开始在终端为您校准时间 !/n” );
}
else
{
printf( “/n用法 : %s -i/-D/n”
" Or: %s/n"
" -i, 以交互方式在终端运行 /n"
" -D, 以守护进程方式运行 ( 默认 )/n/n"
"守护进程 PID 文件 : " DAE_PID_PATH “/n”
"配置文件 : " CONF_PATH “/n”
"启动脚本 : " INIT_PATH “/n”
“日志文件 : %s/n/n” ,
CMD_NAME, CMD_NAME, NtpCfg.logpath);
exit(1);
}
//保证日志存在
if (NtpCfg.logen)
{
int fd;
if ((fd=open(NtpCfg.logpath, O_RDONLY|O_CREAT))==-1)
{
PDEBUG( “日志打开失败,将关闭日志标志 !/n” );
}
close(fd);
}
return 0;
}
/*
程序入口
*/
int main( int argc, char **argv)
{
int sock;
int ret;
NtpServResp response;
struct timeval timeout; //<sys/time.h>
//初始化 ntpclient
ntpclient_init(argc, argv);
//注册 signal 处理函数
signal(SIGTERM, sig_log_term);
signal(SIGINT, sig_log_term);
//连接 ntp 服务器
sock = ntp_conn_server(NtpCfg.servaddr, NtpCfg.port);
//发送 ntp 包
send_packet(sock);
while (1)
{
fd_set fds_read;
FD_ZERO(&fds_read);
FD_SET(sock, &fds_read);
timeout.tv_sec = NtpCfg.timeout;timeout.tv_usec = 0;ret = select(sock + 1, &fds_read, NULL, NULL, &timeout);if (ret == -1){log_record( "select函数出错,被迫终止 !/n" );exit(0);}if (ret == 0 || !FD_ISSET (sock, &fds_read)){log_record( "等待服务器响应超时,重发请求 !/n" );//向服务器发送数据send_packet(sock);continue ;}if ( false == get_server_time(sock, &response)){continue ;}mod_localtime(response.newtime);log_offtime(response.offtime);log_dlytime(response.dlytime);log_newtime(response.newtime);//间隔指定时间校准一次sleep(NtpCfg.phour*3600+NtpCfg.pmin*60+NtpCfg.psec);//发送 ntp 包send_packet(sock);
}
close(sock);
exit(0);
}
实验代码interact.c,这里定义了交互运行相关的函数。
#include “stdinc.h”
#include “def.h”
extern NtpConfig NtpCfg;
/*
通过交互界面配置菜单获取配置信息
返回值: -1 – 退出程序
0 -- 设置完成
1 -- 开始校准
*/
int get_cfg_from_menu()
{
#define BACKALIGN “/r/t/t/t/t/t/t/t###/n”
fflush(stdout);
system( “clear” );
printf( “###########################################################/n”
“### 1. ntp服务器地址 : servaddr= %s” BACKALIGN
“### 2. 每隔多少秒校准一次 : psec= %d” BACKALIGN
“### 3. 每隔多少分校准一次 : pmin= %d” BACKALIGN
“### 4. 每隔多少时校准一次 : phour= %d” BACKALIGN
“### 5. 等待超时时间阀值 : timeout= %d” BACKALIGN
“### 6. 日志开关标志位 : logen= %d” BACKALIGN
“### 7. 日志存放路径 : logpath= %s” BACKALIGN
“### ### ###” BACKALIGN
"### r/R. 开始校准 " BACKALIGN
"### q/Q. 退出 " BACKALIGN
“###########################################################/n”
"输入操作命令或需要修改的参数序号 : " ,
NtpCfg.servaddr, NtpCfg.psec, NtpCfg.pmin, NtpCfg.phour,
NtpCfg.timeout, NtpCfg.logen, NtpCfg.logpath);
char str[256];
fgets(str, 256, stdin);
switch (str[0])
{
case ‘1’ :
{
printf( "servaddr = " );
scanf( “%s” , NtpCfg.servaddr);
break ;
}
case ‘2’ :
{
printf( "psec = " );
scanf( “%d” , &NtpCfg.psec);
if (NtpCfg.psec<0)
{
NtpCfg.psec=0;
}
break ;
}
case ‘3’ :
{
printf( "pmin = " );
scanf( “%d” , &NtpCfg.pmin);
if (NtpCfg.pmin<0)
{
NtpCfg.pmin=0;
}
break ;
}
case ‘4’ :
{
printf( "phour = " );
scanf( “%d” , &NtpCfg.phour);
if (NtpCfg.phour<0)
{
NtpCfg.phour=0;
}
break ;
}
case ‘5’ :
{
printf( "timeout = " );
scanf( “%d” , &NtpCfg.timeout);
if (NtpCfg.timeout<0)
{
NtpCfg.timeout=0;
}
break ;
}
case ‘6’ :
{
printf( "logen = " );
fgets(str, 256, stdin);
if (atoi(str))
{
NtpCfg.logen = true ;
}
else
{
NtpCfg.logen = false ;
}
break ;
}
case ‘7’ :
{
printf( "logpath = " );
scanf( “%s” , NtpCfg.logpath);
break ;
}
case ‘r’ :
case ‘R’ :
{
return 1;
}
case ‘q’ :
case ‘Q’ :
{
return -1;
}
default :
break ;
}
return 0;
}
实验代码daemon.c,这里定义了后台运行相关的函数。
#include “stdinc.h”
#include “def.h”
extern NtpConfig NtpCfg;
/*
初始化本进程为守护进程
*/
void init_daemon( void )
{
int pid;
int i;
if ((pid=fork())>0)
{
exit(0); //是父进程,结束父进程
}
else if (pid< 0)
{
exit(1); //fork失败,退出
}
//是第一子进程,后台继续执行
setsid(); //第一子进程成为新的会话组长和进程组长
//并与控制终端分离
if ((pid=fork())>0)
{
exit(0); //是第一子进程,结束第一子进程
}
else if (pid< 0)
{
exit(1); //fork失败,退出
}
//是第二子进程,继续
//第二子进程不再是会话组长
for (i=0;i< NOFILE;++i) //关闭打开的文件描述符
{
close(i);
}
chdir( “/tmp” ); //改变工作目录到 /tmp
umask(0); //重设文件创建掩模
return ;
}
/*
记录 pid 到文件@ 在 init_daemon 之后调用*/
int record_pid_to_file( const char *pidfn)
{
int fd;
char pid[30];
if ((fd=open(pidfn, O_WRONLY|O_CREAT))==-1)
{
return -1;
}
sprintf(pid, “%d/n” , getpid());
write(fd, pid, strlen(pid));
close(fd);
return 0;
}
/*
在字符串中找到第一个不是指定字符的位置*/
char * strnchr( char *str, char ch)
{
char *pstr = str;
while (*pstr)
{
if (*pstr!=ch)
{
return pstr;
}
pstr++;
}
return NULL;
}
/*
解析配置文件并加载配置信息
*/
int get_cfg_from_file( const char *cfn)
{
FILE *fp;
if ((fp = fopen(cfn, “r” ))==NULL)
{
PDEBUG( “配置文件打开失败 /n” );
return -1;
}
char line[256], arg[256], val[256];
char *pst;
while (fgets(line, 256, fp)!=NULL)
{
if ((pst=strnchr(line, ’ ’ ))NULL || pst[0]‘#’ )
{
continue ;
}
strcpy(arg, strtok(pst, " =" ));
if ((pst=strtok(NULL, " =" ))NULL) { continue ; } strcpy(val, pst); if (val[strlen(val)-1]
{
val[strlen(val)-1]=0;
}
//根据 arg 和 val 给 NtpCfg 结构赋值
if (!strcmp(arg, “servaddr” ))
{
strcpy(NtpCfg.servaddr, val);
}
else if (!(strcmp(arg, “psec” )))
{
NtpCfg.psec=atoi(val);
}
else if (!(strcmp(arg, “pmin” )))
{
NtpCfg.pmin=atoi(val);
}
else if (!(strcmp(arg, “phour” )))
{
NtpCfg.phour=atoi(val);
}
else if (!(strcmp(arg, “timeout” )))
{
NtpCfg.timeout=atoi(val);
}
else if (!(strcmp(arg, “logen” )))
{
if (atoi(val))
{
NtpCfg.logen= true ;
}
else
{
NtpCfg.logen= false ;
}
}
else if (!(strcmp(arg, “logpath” )))
{
strcpy(NtpCfg.logpath, val);
}
//PDEBUG("%s = %s/n", arg, val);
}
return 0;
}
实验代码log.c,这里定义了日志相关的函数。
#include “stdinc.h”
#include “def.h”
extern NtpConfig NtpCfg;
/*
日志记录*/
int log_record( char *record, …)
{
int fd;
char rbuf[256], str[256];
time_t now = time(NULL);
va_list ap;
va_start(ap, record);
bzero(rbuf, 256);
bzero(str, 256);
vsprintf(str, record, ap);
va_end(ap);
PDEBUG( “%s/n” , str);
if (!NtpCfg.logen) //日志标志没有打开
{
return 0;
}
sprintf(rbuf, “%s/t%s/n” , asctime(localtime(&now)), str);
if ((fd=open(NtpCfg.logpath, O_WRONLY|O_APPEND))==-1)
{
return -1;
}
write(fd, rbuf, strlen(rbuf));
close(fd);
return 0;
}
/*
记录 dlytime 、 offtime 、 newtime*/
void log_dlytime( struct timeval dly)
{
char str[256];
sprintf(str, “网络延时 : %7.3f s/n” ,
dly.tv_sec+( double )dly.tv_usec/1000000.0;
log_record(str);
}
void log_offtime( struct timeval off)
{
char str[256];
sprintf(str, “本地时差 : %7.3f s/n” ,
off.tv_sec+ ( double ) off.tv_usec/1000000.0);
log_record(str);
}
void log_newtime( struct timeval new )
{
char str[256];
struct tm *ltm;
ltm = localtime(& new .tv_sec);
sprintf(str, “正确时间 : %.4d-%.2d-%.2d %.2d:%.2d:%.2d/n” ,
ltm->tm_year + 1900, ltm->tm_mon + 1, ltm->tm_mday,
ltm->tm_hour, ltm->tm_min, ltm->tm_sec);
log_record(str);
}
/*
信号处理函数@ 程序被信号终止时,记录日志*/
void sig_log_term( int signo)
{
if (SIGTERM==signo)
{
log_record( “接收到 SIGTERM 信号 , 服务被终止 /n” );
}
else
{
log_record( “接收到 SIGINT 信号 , 服务被终止 /n” );
}
raise(SIGKILL);
} Init.d子目录:
实验代码 ntpclient 是该服务的初始化脚本,它的实现还利用到了系统定义的一些脚本函数,如 killproc 和daemon 等,它们定义在 /etc/init.d/functions 中 。读者可以深入分析 /etc/init.d/functions ,弄明白killproc是如何杀死相应的 daemon 进程的。
#!/bin/sh
默认目录 :
cmd=ntpclient
pidfile=/var/run/ntpclient.pid
logpath=/tmp/ntpclient.pid
config=/etc/ntpclient/ntpclient.conf
Source function library.
if [ -f /etc/init.d/functions ] ; then
. /etc/init.d/functions
elif [ -f /etc/rc.d/init.d/functions ] ; then
. /etc/rc.d/init.d/functions
else
exit 1
fi
Avoid using root’s TMPDIR
unset TMPDIR
Source networking configuration.
. /etc/sysconfig/network
Check that networking is up.
[ ${NETWORKING} = “no” ] && exit 1
Check that ${cmd}.conf exists.
[ -f $config ] || exit 6
RETVAL=0
start()
{
KIND=$cmd
echo -n $"Starting $KIND services: "
daemon cmdRETVAL=cmd RETVAL=cmdRETVAL=?
echo
return $RETVAL
}
stop()
{
KIND=$cmd
echo -n $"Shutting down $KIND services: "
killproc cmdRETVAL=cmd RETVAL=cmdRETVAL=?
echo
return $RETVAL
}
restart()
{
stop
start
}
rhstatus()
{
status cmdRETVAL=cmd RETVAL=cmdRETVAL=?
if [ $RETVAL -ne 0 ] ; then
return $RETVAL
fi
}
Allow status as non-root.
if [ “$1” = status ]; then
rhstatus
exit $?
fi
Check that we can write to it… so non-root users stop here
[ -w $config ] || exit 4
case “$1” in
start)
start
;;
stop)
stop
;;
restart)
restart
;;
reload)
reload
;;
status)
rhstatus
;;
*)
echo $“Usage: $0 {start|stop|restart|status}”
exit 2
esac
exit KaTeX parse error: Expected 'EOF', got '&' at position 64: …产生终端输出的命令后加的:2>&̲1 >/dev/null ,它…{cmd}/cmd.confinitpath=/etc/init.d/{cmd}.conf initpath=/etc/init.d/cmd.confinitpath=/etc/init.d/{cmd}
logpath=/tmp/cmd.logbinpath=/usr/sbin/{cmd}.log binpath=/usr/sbin/cmd.logbinpath=/usr/sbin/cmd
Usage()
{
echo "Usage: "
echo -e “/t${cmd} -i/t安装 cmd"echo−e"/t{cmd}" echo -e "/tcmd"echo−e"/t{cmd} -d/t卸载 ${cmd}/n”
}
if [ -n “$1” -a -z “$2” ];then
if [ "KaTeX parse error: Expected 'EOF', got '&' at position 52: …-f .Makefile 2>&̲1 >/dev/null …{cmd} ] || mkdir /etc/cmdcpconfig/{cmd} cp config/cmdcpconfig/{cmd}.conf KaTeX parse error: Expected 'EOF', got '&' at position 13: {cfgpath} 2>&̲1 >/dev/null …{cmd} KaTeX parse error: Expected 'EOF', got '&' at position 14: {initpath} 2>&̲1 >/dev/null …{cmd} ${binpath} 2>&1 >/dev/null
[ -f ${logpath} ] || touch ${logpath}
chmod 761 ${binpath} ${initpath} 2>&1 >/dev/null
chmod 640 ${cfgpath} ${logpath} 2>&1 >/dev/null
make clean -f .Makefile 2>&1 >/dev/null
echo -n “安装 ${cmd} 服务 :”
echo -e “/r/t/t/t/t/t/t/t [/033[32;32;5m确定 /033[0m]”
elif [ “$1” = “-d” ];then
[ -x ${initpath} ] && KaTeX parse error: Expected 'EOF', got '&' at position 19: …itpath} stop 2>&̲1 >/dev/null …{cmd}
rm -f ${initpath} ${initpath} ${binpath} ${logpath}
echo -n “卸载 ${cmd} 服务 :”
echo -e “/r/t/t/t/t/t/t/t [/033[32;32;5m确定 /033[0m]”
else
Usage
fi
else
Usage
fi