ntp客户端

1.由于有时候需要设置windos或Linux的时间,使用ntp自带的客户端总是不大方便,因此用go写了一个客户端。

下面是代码

package main

import (
    "bytes"
    "encoding/binary"
    "errors"
    "flag"
    "fmt"
    "log"
    "net"
    "os/exec"
    "runtime"
    "time"
)

func main() {
    addr := flag.String("i", "cn.ntp.org.cn", "Ntp server address")
    port := flag.Int("p", 123, "Ntp server port")
    unix := flag.Bool("u", false, "Don't convert timestamp")
    show := flag.Bool("s", false, "Show the detailed")
    set := flag.Bool("set", false, "set system time")
    flag.Parse()

    ntp, err := NewNtp(*addr, *port)
    if err != nil {
        log.Fatal(err)
    }
    defer ntp.Close()

    ntpTimeStamp, err := ntp.GetTimeStamp(!*unix, *show)
    if err != nil {
        log.Fatal(err)
    } else if ntpTimeStamp > 0 {
        var (
            ntpTime = time.Unix(ntpTimeStamp, 0)
            d       = ntpTime.Format("2006-01-02")
            t       = ntpTime.Format("15:04:05")
        )
        if *set { // 设置系统时间
            var cmd *exec.Cmd
            if runtime.GOOS == `windows` {
                cmd = exec.Command("cmd", "/c", fmt.Sprintf("date %s & time %s", d, t))
            } else {
                cmd = exec.Command("date", "-s", fmt.Sprintf("@%d", ntpTimeStamp))
            }
            fmt.Println(cmd.Args)
            if err = cmd.Run(); err != nil {
                log.Fatal(err)
            }
        } else {
            fmt.Printf(`{"date":"%s","time":"%s","timestamp":%d}`, d, t, ntpTimeStamp)
            fmt.Println()
        }
    }
}

// ntp的起始时间戳 1900年1月1日 0时0分0秒 2208988800
const UnixStaTimestamp = 2208988800

type (
    ntp struct {
        conn net.Conn // udp连接
        data data     // ntp封装数据
    }
    // NTP协议 http://www.ntp.org/documentation.html
    data struct {
        //1:32bits
        Li        uint8 //2 bits
        Vn        uint8 //3 bits
        Mode      uint8 //3 bits
        Stratum   uint8
        Poll      uint8
        Precision uint8

        RootDelay           int32
        RootDispersion      int32
        ReferenceIdentifier int32

        ReferenceTimestamp uint64 //指示系统时钟最后一次校准的时间
        OriginateTimestamp uint64 //指示客户向服务器发起请求的时间
        ReceiveTimestamp   uint64 //指服务器收到客户请求的时间
        TransmitTimestamp  uint64 //指示服务器向客户发时间戳的时间
    }
)

func NewNtp(ip string, port int) (*ntp, error) {
    var (
        ntp = &ntp{data: data{Li: 0, Vn: 3, Mode: 3, Stratum: 0}}
        err error
    )
    ntp.conn, err = net.Dial("udp", fmt.Sprintf("%s:%d", ip, port))
    if err != nil {
        return nil, err
    }
    return ntp, nil
}

func (my *ntp) Close() error {
    return my.conn.Close()
}

func (my *ntp) GetTimeStamp(isUnix, show bool) (int64, error) {
    //注意网络上使用的是大端字节排序
    buf := &bytes.Buffer{}
    head := (my.data.Li << 6) | (my.data.Vn << 3) | ((my.data.Mode << 5) >> 5)
    binary.Write(buf, binary.BigEndian, uint8(head))
    binary.Write(buf, binary.BigEndian, my.data.Stratum)
    binary.Write(buf, binary.BigEndian, my.data.Poll)
    binary.Write(buf, binary.BigEndian, my.data.Precision)
    //写入其他字节数据
    binary.Write(buf, binary.BigEndian, my.data.RootDelay)
    binary.Write(buf, binary.BigEndian, my.data.RootDispersion)
    binary.Write(buf, binary.BigEndian, my.data.ReferenceIdentifier)
    binary.Write(buf, binary.BigEndian, my.data.ReferenceTimestamp)
    binary.Write(buf, binary.BigEndian, my.data.OriginateTimestamp)
    binary.Write(buf, binary.BigEndian, my.data.ReceiveTimestamp)
    binary.Write(buf, binary.BigEndian, my.data.TransmitTimestamp)

    ret, err := my.conn.Write(buf.Bytes())
    if err != nil {
        return 0, err
    }

    buffer := make([]byte, 2048)
    ret, err = my.conn.Read(buffer)
    if err != nil {
        return 0, err
    } else if ret <= 0 {
        return 0, errors.New("return 0 byte")
    }

    var bit8 uint8
    // 貌似这binary.Read只能顺序读,不能跳着读,想要跳着读只能使用切片bf
    rb := bytes.NewReader(buffer)
    binary.Read(rb, binary.BigEndian, &bit8)
    // 向右偏移6位得到前两位LI即可
    my.data.Li = bit8 >> 6
    // 向右偏移2位,向右偏移5位,得到前中间3位
    my.data.Vn = (bit8 << 2) >> 5
    // 向左偏移5位,然后右偏移5位得到最后3位
    my.data.Mode = (bit8 << 5) >> 5
    binary.Read(rb, binary.BigEndian, &my.data.Stratum)
    binary.Read(rb, binary.BigEndian, &my.data.Poll)
    binary.Read(rb, binary.BigEndian, &my.data.Precision)

    // 32bits
    binary.Read(rb, binary.BigEndian, &my.data.RootDelay)
    binary.Read(rb, binary.BigEndian, &my.data.RootDispersion)
    binary.Read(rb, binary.BigEndian, &my.data.ReferenceIdentifier)

    // 以下几个字段都是64位时间戳(NTP都是64位的时间戳)
    binary.Read(rb, binary.BigEndian, &my.data.ReferenceTimestamp)
    binary.Read(rb, binary.BigEndian, &my.data.OriginateTimestamp)
    binary.Read(rb, binary.BigEndian, &my.data.ReceiveTimestamp)
    binary.Read(rb, binary.BigEndian, &my.data.TransmitTimestamp)
    // 转换为unix时间戳,先左偏移32位拿到64位时间戳的整数部分
    if isUnix {
        my.data.ReferenceTimestamp = (my.data.ReceiveTimestamp >> 32) - UnixStaTimestamp
        if my.data.OriginateTimestamp > 0 {
            my.data.OriginateTimestamp = (my.data.OriginateTimestamp >> 32) - UnixStaTimestamp
        }
        my.data.ReceiveTimestamp = (my.data.ReceiveTimestamp >> 32) - UnixStaTimestamp
        my.data.TransmitTimestamp = (my.data.TransmitTimestamp >> 32) - UnixStaTimestamp
    } else {
        show = true // 不转换时间戳时只打印详细结果
    }

    if show {
        fmt.Printf("LI:%d\r\n版本:%d\r\n模式:%d\r\n精度:%d\r\n轮询:%d\r\n系统精度:%d\r\n延时:%ds\r\n最大误差:%d\r\n时钟表示:%d\r\n时间戳:%d %d %d %d\r\n",
            my.data.Li,
            my.data.Vn,
            my.data.Mode,
            my.data.Stratum,
            my.data.Poll,
            my.data.Precision,
            my.data.RootDelay,
            my.data.RootDispersion,
            my.data.ReferenceIdentifier,
            my.data.ReferenceTimestamp,
            my.data.OriginateTimestamp,
            my.data.ReceiveTimestamp,
            my.data.TransmitTimestamp,
        )
        return 0, nil
    } else if my.data.ReferenceTimestamp == my.data.ReceiveTimestamp &&
        my.data.ReceiveTimestamp == my.data.TransmitTimestamp &&
        my.data.OriginateTimestamp == 0 {
        return int64(my.data.ReferenceTimestamp), nil
    }
    return 0, errors.New("ntp return error")
}
2.下面是使用方法
# .\ntp_client.exe -h
Usage of .\ntp_client.exe:
  -i string
        Ntp server address (default "cn.ntp.org.cn")
  -p int
        Ntp server port (default 123)
  -s    Show the detailed
  -set
        set system time
  -u    Don't convert timestamp
上面是显示帮助,默认使用的ntp服务器可以去
http://www.ntp.org.cn/pool.php
上找到

# .\ntp_client.exe
{"date":"2018-10-25","time":"14:41:41","timestamp":1540449701}
上面是直接运行的结果

# .\ntp_client.exe -s
LI:0
版本:3
模式:4
精度:2
轮询:0
系统精度:231
延时:7s
最大误差:69
时钟表示:176764167
时间戳:1540449797 0 1540449797 1540449797
这样可以显示ntp返回的数据具体内容

# .\ntp_client.exe -set
[cmd /c date 2018-10-25 & time 14:43:46]
# ./ntp_client -set
[date -s @1540441156]
这样可以设置windos或Linux的系统时间

-u的参数是显示没有减去ntp的起始时间戳的时间戳,一般没啥用

已有 2 条评论

  1. 易米学 易米学

    请问博主的加载时动画和评论验证是怎么搞的啊

    1. 加载动画我已经忘记咋搞的了,太早之前弄得啊5.gif。不过评论验证插件是我写的(http://forum.typecho.org/viewtopic.php?f=6&t=9825&p=37775&hilit=zj007#p37775)去论坛下载吧。

captcha