如上圖, 在 NTP 的架構之下, 有一個 NTP Server, 這個 NTP Server 必須維持精準的時間,目前本校所採用的時間是接收衛星訊號(Global Positioning System, GPS)的時間, 它的準確度可以逹到1ms以內, 而這時間提供其他的 NTP Client 或個人電腦系統時間作為校正的一個標準, 當所有的 NTP Client 向NTP Server 同步時, 時間的問題就完美地解決了. 接著我們對圖一的代號作一些說明如下:
T1-T0: 為Client 端向 NTP server要求提供對時服務的時間, 包括網路傳輸時間.
T2-T1: NTP server 處理對時服務的作業時間.
T3-T2: 為NTP server回覆 Client端對時服務的時間資料, 包括網路傳輸時間.
T3-T0: 總共花費的時間.
Client 端所獲得之時間與 NTP server時間約延遲(delay) T3-T2 至 T3-T1之間.
簡單的網路校時程式 ntpc.c
程式碼很簡單, CROSS 編譯也非常簡單, 沒有什麼複雜的 dependency. 因此我不打算詳細解說. 因為非常簡單, 所也與非常適合 embedded system. 程式碼如下:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdarg.h>
#include <stdlib.h>
#include <time.h>
#include <fcntl.h>
#include <signal.h>
#include <termios.h>
#include <netinet/tcp.h>
#define TIMEOUT 10 // seconds
#define JAN_1970 0x83aa7e80 // 2208988800 1970 - 1900 in seconds
#define NTP_PORT (123)
typedef uint32_t __u32;
/* How to multiply by 4294.967296 quickly (and not quite exactly)
* without using floating point or greater than 32-bit integers.
* If you want to fix the last 12 microseconds of error, add in
* (2911*(x))>>28)
*/
#define NTPFRAC(x) ( 4294*(x) + ( (1981*(x))>>11 ) )
/* The reverse of the above, needed if we want to set our microsecond
* clock (via settimeofday) based on the incoming time in NTP format.
* Basically exact.
*/
#define USEC(x) ( ( (x) >> 12 ) - 759 * ( ( ( (x) >> 10 ) + 32768 ) >> 16 ) )
// you'd better be root, or ntpclient will crash!
int settime(uint32_t *data) {
struct timeval tv_set;
unsigned int coarse;
unsigned int fine;
#define Data(i) ntohl(((uint32_t *)data)[i])
coarse = Data(10);
fine = Data(11);
#undef Data
// it would be even better to subtract half the slop
tv_set.tv_sec = coarse - JAN_1970;
// divide xmttime.fine by 4294.967296
tv_set.tv_usec = USEC(fine);
if (settimeofday(&tv_set,NULL)<0)
return -1;
return 0;
}
void send_packet(int usd) {
__u32 data[12];
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) {
fprintf(stderr,"size error\n");
return;
}
bzero((char *) data,sizeof(data));
data[0] = htonl (
( LI << 30 ) | ( VN << 27 ) | ( MODE << 24 ) |
( STRATUM << 16) | ( POLL << 8 ) | ( PREC & 0xff ) );
data[1] = htonl(1<<16); /* Root Delay (seconds) */
data[2] = htonl(1<<16); /* Root Dispersion (seconds) */
gettimeofday(&now,NULL);
data[10] = htonl(now.tv_sec + JAN_1970); // Transmit Timestamp coarse
data[11] = htonl(NTPFRAC(now.tv_usec)); // Transmit Timestamp fine
send(usd,data,48,0);
}
int ntpHandle(int usd) {
static uint32_t incoming_word[20];
#define incoming ((char *) incoming_word)
#define sizeof_incoming (sizeof(incoming_word)*sizeof(uint32_t))
fd_set fds;
struct sockaddr sa_xmit;
int sa_xmit_len=sizeof(sa_xmit);
int i, pack_len;
struct timeval to;
send_packet(usd);
to.tv_sec=TIMEOUT;
to.tv_usec=0;
for (;;) {
FD_ZERO(&fds);
FD_SET(usd,&fds);
i=select(usd+1,&fds,NULL,NULL,&to); // Wait on read or error
if ((i!=1)||(!FD_ISSET(usd,&fds))) {
if (errno==EINTR) {
fprintf(stderr,"interrupt by signal\n");
continue;
}
if (i<0) {
fprintf(stderr,"select error\n");
return -1;
}
fprintf(stderr,"timeout\n");
return -1;
}
pack_len=recvfrom(usd,incoming,sizeof_incoming,0,&sa_xmit,&sa_xmit_len);
if (pack_len<0) {
fprintf(stderr,"recvfrom error\n");
return -1;
//} else if (pack_len >0 && (unsigned)pack_len)
} else if (pack_len > 0) {
fprintf(stderr,"settime\n");
return settime(incoming_word);
}
return -1;
}
return -1;
}
int ntpclient(char *hostname) {
struct hostent *ntpserver;
struct sockaddr_in sa_dest;
struct sockaddr_in sa_rcvr;
int usd; // socket
int err;
// Startup sequence
if ((usd=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP))==-1) {
fprintf(stderr,"could not open the sockey\n");
return -1;
}
// setup receive
bzero((char *) &sa_rcvr, sizeof(sa_rcvr));
sa_rcvr.sin_family=AF_INET;
sa_rcvr.sin_addr.s_addr=htonl(INADDR_ANY);
//sa_rcvr.sin_port=htons(0); // udp_local_port, default of 0 means kernel chooses
sa_rcvr.sin_port=htons(5301); // udp_local_port, default of 0 means kernel chooses
if(bind(usd,(struct sockaddr *) &sa_rcvr,sizeof(sa_rcvr)) == -1) {
fprintf(stderr,"could not bind to udp port\n");
close(usd);
return -1;
}
listen(usd, 3);
// setup transmit
// get host name
bzero((char *) &sa_dest, sizeof(sa_dest));
ntpserver=gethostbyname(hostname);
if (ntpserver == NULL) {
fprintf(stderr,"could not resolve hostname %s\n", hostname);
close(usd);
return -1;
}
if (ntpserver->h_length != 4) {
fprintf(stderr,"oops %d\n",ntpserver->h_length);
close(usd);
return -1;
}
memcpy(&(sa_dest.sin_addr.s_addr),ntpserver->h_addr_list[0],4);
// connect ntp server
sa_dest.sin_family=AF_INET;
sa_dest.sin_port=htons(NTP_PORT);
if (connect(usd,(struct sockaddr *)&sa_dest,sizeof(sa_dest))==-1) {
fprintf(stderr,"could not connect to udp port %d\n", NTP_PORT);
close(usd);
return -1;
}
// ntp handle
err = ntpHandle(usd);
// exit
close(usd);
return err;
}
int main(int argc, char *argv[]) {
if(argc<2) {
fprintf(stderr, "Usage: ntpc tick.stdtime.gov.tw\n");
exit(-1);
}
return ntpclient(argv[1]);
}
在 Android 下設定 crontab
Android 下可以使用 crond, 條件是 busybox 必須編譯支援 crond; 同樣地 telnetd 以及 ftpd, tcpsvd 也是如此. BUSYBOX 如何編譯? 就請讀者自行實做囉.
... chmod 777 /data/ftp echo 'root::0:0:root:/data/ftp:/system/bin/sh' > /etc/passwd echo 'shell::2000:0:shell:/data/ftp:/system/bin/sh' >> /etc/passwd ... cd /system/bin ### CROND [ ! -L crond ] && ln -s busybox crond if [ ! -d /system/crontabs ]; then echo "" mkdir /system/crontabs ; chmod 777 /system/crontabs ; touch /system/crontabs/root ; chmod 666 /system/crontabs/root echo "30 2 * * * /system/bin/ntpc tick.stdtime.gov.tw > /dev/null 2>&1" > /system/crontabs/root fi ### TELNETD [ -L telnetd ] && telnetd -l /system/bin/sh & ### FTPD [ ! -d /data/ftp ] && mkdir /data/ftp chmod 777 /data/ftp ps | grep tcpsvd > /dev/null if [ $? -ne 0 ]; then echo "" if [ -L tcpsvd -a -L ftpd ]; then echo "" tcpsvd -u root -vE 0.0.0.0 21 ftpd -w /data/ftp & fi fi cd .. ...
/System/crontabs/root
echo "30 2 * * * /system/bin/ntpc tick.stdtime.gov.tw > /dev/null 2>&1" > /system/crontabs/root
30 2 * * * /system/bin/ntpc tick.stdtime.gov.tw > /dev/null 2>&1
這樣 每天 2:30 ntpc 會向 tick.stdtime.gov.tw 要求正確時間來校時. 提供讀者參考.
如果你覺得這篇文章對你有幫助, 請於留言板留個言, 以茲鼓勵. 謝謝! Email: jasonc@mail2000.com.tw .
