使用go對NTP發起請求獲取當前時間

厚礼蝎發表於2024-07-27
package main

import (
	"encoding/binary"
	"fmt"
	"log"
	"net"
	"time"
)

// Packet 定義一個結構體,用於表示 NTP 資料包
type Packet struct {
	Settings       uint8  // 設定欄位
	Stratum        uint8  // 層級欄位
	Poll           int8   // 輪詢欄位
	Precision      int8   // 精度欄位
	RootDelay      uint32 // 根延遲欄位
	RootDispersion uint32 // 根離散欄位
	ReferenceID    uint32 // 參考 ID 欄位
	RefTimeSec     uint32 // 參考時間秒部分
	RefTimeFrac    uint32 // 參考時間分數部分
	OrigTimeSec    uint32 // 原始時間秒部分
	OrigTimeFrac   uint32 // 原始時間分數部分
	RxTimeSec      uint32 // 接收時間秒部分
	RxTimeFrac     uint32 // 接收時間分數部分
	TxTimeSec      uint32 // 傳輸時間秒部分
	TxTimeFrac     uint32 // 傳輸時間分數部分
}

func NewPacket() *Packet {
	return &Packet{}
}
func NewPacketSettings(settings uint8) *Packet {
	return &Packet{Settings: settings}
}

// 定義 NTP 紀元時間與 UNIX 紀元時間的偏移量
const ntpEpochOffset = 2208988800

var (
	host    = "pool.ntp.org"
	timeout = 15 * time.Second
)

// sendRequest 函式用於傳送 NTP 請求
func sendRequest(conn net.Conn, req *Packet) error {
	return binary.Write(conn, binary.BigEndian, req) // 以大端位元組序將請求包寫入連線
}

// readResponse 函式用於讀取 NTP 響應
func readResponse(conn net.Conn, rsp *Packet) error {
	return binary.Read(conn, binary.BigEndian, rsp) // 以大端位元組序從連線讀取響應包
}

// parseTime 函式用於解析 NTP 響應中的時間
func parseTime(rsp *Packet) time.Time {
	secs := float64(rsp.TxTimeSec) - ntpEpochOffset // 計算自 UNIX 紀元的秒數
	nanos := (int64(rsp.TxTimeFrac) * 1e9) >> 32    // 計算秒的小數部分
	return time.Unix(int64(secs), nanos)            // 返回完整的時間
}

// GetCurrentTime Ntp 函式用於從 NTP 伺服器獲取當前時間
func GetCurrentTime() {
	// 建立一個 UDP 連線到指定的 NTP 伺服器
	conn, err := net.Dial("udp", fmt.Sprintf("%s:123", host))
	if err != nil {
		log.Fatalf("Failed to connect: %v", err) // 連線失敗則記錄錯誤並退出
	}
	defer conn.Close() // 函式結束時關閉連線

	// 設定連線的讀寫超時時間為 15 秒
	if err := conn.SetDeadline(time.Now().Add(timeout)); err != nil {
		log.Fatalf("Failed to set deadline: %v", err) // 設定超時失敗則記錄錯誤並退出
	}

	req := NewPacketSettings(0x1B) // 建立一個 NTP 請求包

	// 傳送請求包到 NTP 伺服器
	if err := sendRequest(conn, req); err != nil {
		log.Fatalf("Failed to send request: %v", err) // 傳送請求失敗則記錄錯誤並退出
	}

	rsp := NewPacket() // 建立一個空的 NTP 響應包
	// 從 NTP 伺服器讀取響應包
	if err := readResponse(conn, rsp); err != nil {
		log.Fatalf("Failed to read server response: %v", err) // 讀取響應失敗則記錄錯誤並退出
	}

	currentTime := parseTime(rsp)                          // 解析響應包中的時間
	fmt.Printf("Current time: %v\n", currentTime)          // 列印當前時間
	fmt.Println(currentTime.Format("2006-01-02 15:04:05")) // 或者格式化列印
}

func main() {
	GetCurrentTime()
}

相關文章