欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

使用Go語言編寫一個NTP服務器的流程步驟

 更新時間:2024年11月11日 08:21:30   作者:曉琴兒  
NTP服務器【Network?Time?Protocol(NTP)】是用來使計算機時間同步化的一種協(xié)議,為了確保封閉局域網內多個服務器的時間同步,我們計劃部署一個網絡時間同步服務器(NTP服務器),本文給大家介紹了使用Go語言編寫一個NTP服務器的流程步驟,需要的朋友可以參考下

NTP服務介紹

NTP服務器【Network Time Protocol(NTP)】是用來使計算機時間同步化的一種協(xié)議。

  • 應用場景說明
    為了確保封閉局域網內多個服務器的時間同步,我們計劃部署一個網絡時間同步服務器(NTP服務器)。這一角色將由一臺個人筆記本電腦承擔,該筆記本將連接到局域網中,并以其當前時間為基準。我們將利用這臺筆記本電腦作為NTP服務器,對局域網內的多個運行CentOS 8的服務器進行時間校準,以保證系統(tǒng)時間的一致性和準確性。

NTP協(xié)議

  • NTP通信協(xié)議的傳輸層協(xié)議是UDP
  • NTP通信協(xié)議的應用層協(xié)議是NTP

NTP報文說明

  • NTP的報文是48字節(jié)
  1. 第1個字節(jié)可以理解為簡易的報文頭,這8個bit包含Leap Indicator、NTP Version、Mode
    a> LI 占用2個bit
    b> VN 占用3個bit,筆者編寫的服務器設置為版本v4.0
    c> Mode 占用3個bit,ntp server時為4,ntp client時為3
  2. 第2個字節(jié)為 Peer Clock Stratum
  3. 第3個字節(jié)為 Peer Polling Interval
  4. 第4個字節(jié)為 Peer Clock Precision
  5. 第5 - 8字節(jié)為 Root Delay
  6. 第9 - 12字節(jié)為 Root Dispersion
  7. 第13- 16字節(jié)為 Reference Identifier
  8. 第17 - 24字節(jié)為 Reference Timestamp 參考時間戳
  9. 第25 - 32字節(jié)為 Originate Timestamp 起始時間戳
  10. 第33 - 40字節(jié)為 Receive Timestamp 接收時間戳
  11. 第 41 - 48字節(jié) Transmit Timestamp 傳輸時間戳

根據NTP報文編碼實現Go語言的結構體

type NtpPacket struct {
	/*
		LI: 2bit      00   Leap Indicator(0)
		VN: 3bit      100  NTP Version(4)
		Mode: 3bit    100  Mode: server(4), client(3)
	*/
	Header    uint8 // 報文頭: 包含LI、VN、Mode
	Stratum   uint8 // Peer Clock Stratum: primary reference (1)
	Poll      uint8 // Peer Polling Interval: invalid (0)
	Precision uint8 // Peer Clock Precision: 0.000000 seconds

	RootDelay uint32 // Root Delay
	RootDisp  uint32 // Root Dispersion
	RefID     uint32 // Reference Identifier

	RefTS   uint64 // Reference Timestamp 參考時間戳
	OrigTS  uint64 // Originate Timestamp 起始時間戳
	RecvTS  uint64 // Receive Timestamp   接收時間戳
	TransTS uint64 // Transmit Timestamp  傳輸時間戳
}

NTP服務器的源碼

  • ntpsrv.go
package main

import (
	hldlog "NTPServer/log4go"
	"encoding/binary"
	"fmt"
	"log"
	"net"
	"sync"
	"time"
)

const (
	STANDARD_PACKET_SIZE = 48 // 標準NTP的報文大小
)

type NTPServer struct {
	srvAddress string

	conn *net.UDPConn
	wait sync.WaitGroup

	ntpPack      NtpPacket // NTP協(xié)議報文
	requestCount uint64    // 請求計數
}

type NtpPacket struct {
	/*
		LI: 2bit      00   Leap Indicator(0)
		VN: 3bit      100  NTP Version(4)
		Mode: 3bit    100  Mode: server(4), client(3)
	*/
	Header    uint8 // 報文頭: 包含LI、VN、Mode
	Stratum   uint8 // Peer Clock Stratum: primary reference (1)
	Poll      uint8 // Peer Polling Interval: invalid (0)
	Precision uint8 // Peer Clock Precision: 0.000000 seconds

	RootDelay uint32 // Root Delay
	RootDisp  uint32 // Root Dispersion
	RefID     uint32 // Reference Identifier

	RefTS   uint64 // Reference Timestamp 參考時間戳
	OrigTS  uint64 // Originate Timestamp 起始時間戳
	RecvTS  uint64 // Receive Timestamp   接收時間戳
	TransTS uint64 // Transmit Timestamp  傳輸時間戳
}

func (srv *NTPServer) NewNtpPacket() *NtpPacket {
	// 初始化Header字段
	header := uint8(0)
	header |= (0 << 6) // LI: 2bit 00
	header |= (4 << 3) // VN: 3bit 100
	header |= (4 << 0) // Mode: 3bit 100

	// 創(chuàng)建新的NtpPacket實例
	packet := &NtpPacket{
		Header:    header,
		Stratum:   0x01,
		Poll:      0x00,
		Precision: 0x00,
		RootDelay: 0,
		RootDisp:  0,
		RefID:     0,
		RefTS:     0,
		OrigTS:    0,
		RecvTS:    0,
		TransTS:   0,
	}

	return packet
}

func (pack *NtpPacket) SetTimestamp(timestamp time.Time, field string) {
	ntpTime := ToNTPTime(timestamp)
	switch field {
	case "RefTS":
		pack.RefTS = ntpTime
	case "OrigTS":
		pack.OrigTS = ntpTime
	case "RecvTS":
		pack.RecvTS = ntpTime
	case "TransTS":
		pack.TransTS = ntpTime
	}
}

// toNTPTime 將Unix時間轉換為NTP時間
func ToNTPTime(t time.Time) uint64 {
	seconds := uint32(t.Unix()) + 2208988800 // NTP時間從1900年開始計算
	fraction := uint32(float64(t.Nanosecond()) * (1 << 32) / 1e9)
	return uint64(seconds)<<32 | uint64(fraction)
}

func NewNTPServer(srvAddr string) *NTPServer {
	return &NTPServer{srvAddress: srvAddr}
}

// 啟動NTP服務器
func (srv *NTPServer) Start() error {
	addr, err := net.ResolveUDPAddr("udp", srv.srvAddress)
	if err != nil {
		return err
	}
	hldlog.Info(fmt.Sprintf("<%s:%d>", addr.IP.String(), addr.Port))

	conn, err := net.ListenUDP("udp", addr)
	if err != nil {
		return err
	}

	srv.wait.Add(1)
	srv.conn = conn

	go RecvMsg(srv)

	return nil
}

// 關閉NTP服務器
func (srv *NTPServer) Stop() {
	srv.conn.Close()
	srv.wait.Wait()
}

// 接收數據
func RecvMsg(srv *NTPServer) {
	defer srv.wait.Done()
	buffer := make([]byte, 2*1024)

	for {
		n, remoteAddr, err := srv.conn.ReadFromUDP(buffer[0:])
		if err != nil {
			fmt.Println("ReadFromUDP error:", err)
			return
		}
		hldlog.Info(fmt.Sprintf("[Recv] %d bytes from <%s>", n, remoteAddr.String()))
		if n != STANDARD_PACKET_SIZE {
			continue
		}

		// 接收到NTP客戶端消息的時間
		recvMsgTime := time.Now().UTC()

		recvHexString := BytesToHex(buffer[:n])
		hldlog.Info(fmt.Sprintf("[Recv] %s", recvHexString))

		udpPacket, err := ParseUDPPacket(buffer[:n])
		if err != nil {
			log.Printf("Error parsing UDP packet: %v", err)
			continue
		}
		ntpPack := srv.NewNtpPacket()
		ntpPack.SetTimestamp(time.Now().UTC(), "RefTS")
		ntpPack.OrigTS = udpPacket.TransTS
		ntpPack.SetTimestamp(recvMsgTime, "RecvTS")
		ntpPack.SetTimestamp(time.Now().UTC(), "TransTS")

		sendPacket := ntpPack.Serialize()

		sendLen, err := srv.conn.WriteToUDP(sendPacket, remoteAddr)
		if err != nil {
			log.Println(err.Error())
			continue
		}

		if sendLen > 0 {
			hldlog.Info(fmt.Sprintf("[Send] %s", BytesToHex(sendPacket)))
		}

		srv.requestCount++
	}
}

func (pack *NtpPacket) Serialize() []byte {
	packet := make([]byte, 48)

	// binary.BigEndian.PutUint32(packet[0:4], pack.Header)
	packet[0] = pack.Header
	packet[1] = pack.Stratum
	packet[2] = pack.Poll
	packet[3] = pack.Precision
	binary.BigEndian.PutUint32(packet[4:8], pack.RootDelay)
	binary.BigEndian.PutUint32(packet[8:12], pack.RootDisp)
	binary.BigEndian.PutUint32(packet[12:16], pack.RefID)
	binary.BigEndian.PutUint64(packet[16:24], pack.RefTS)
	binary.BigEndian.PutUint64(packet[24:32], pack.OrigTS)
	binary.BigEndian.PutUint64(packet[32:40], pack.RecvTS)
	binary.BigEndian.PutUint64(packet[40:48], pack.TransTS)

	return packet
}

// BytesToHex 將字節(jié)數組轉換為16進制字符串
func BytesToHex(data []byte) string {
	hexString := make([]byte, 3*len(data)-1)
	for i, b := range data {
		high := "0123456789ABCDEF"[(b >> 4)]
		low := "0123456789ABCDEF"[(b & 0x0F)]
		hexString[i*3] = high
		hexString[i*3+1] = low
		if i < len(data)-1 {
			hexString[i*3+2] = ' ' // 每個16進制數據之間加空格
		}
	}
	return string(hexString)
}

func ParseUDPPacket(buf []byte) (*NtpPacket, error) {
	if len(buf) < STANDARD_PACKET_SIZE { // 最小有效長度為48字節(jié)
		return nil, fmt.Errorf("Invalid UDP packet length: %d", len(buf))
	}

	packet := &NtpPacket{
		// Header:    binary.BigEndian.Uint32(buf[0:4]),
		Header:    buf[0],
		Stratum:   buf[1],
		Poll:      buf[2],
		Precision: buf[3],
		RootDelay: binary.BigEndian.Uint32(buf[4:8]),
		RootDisp:  binary.BigEndian.Uint32(buf[8:12]),
		RefID:     binary.BigEndian.Uint32(buf[12:16]),
		RefTS:     binary.BigEndian.Uint64(buf[16:24]),
		OrigTS:    binary.BigEndian.Uint64(buf[24:32]),
		RecvTS:    binary.BigEndian.Uint64(buf[32:40]),
		TransTS:   binary.BigEndian.Uint64(buf[40:48]),
	}

	return packet, nil
}
  • main.go
package main

import (
	hldlog "NTPServer/log4go"
	"fmt"
	"gopkg.in/ini.v1"
	"time"
)

type NetAddr struct {
	IP   string
	Port string
}

var LocalHost = NetAddr{IP: "0.0.0.0", Port: "60123"}

func loadConfig() (NetAddr, error) {
	// 讀取INI配置文件
	iniConf, err := ini.Load("./config/config.ini")
	if err != nil {
		hldlog.Error(fmt.Sprintf("Fail to read INI file: %v", err))
		return LocalHost, nil
	}

	iniSection := iniConf.Section("LocalHost")
	return NetAddr{
		IP:   iniSection.Key("ip").String(),
		Port: iniSection.Key("port").String(),
	}, nil
}

// 初始化log4go日志庫
func init() {
	hldlog.LoadConfiguration("./config/log.xml", "xml")
}

func main() {
	hldlog.Info("===NTP SERVER Start(48 Bytes)===")

	LocalHost, err := loadConfig()
	if err != nil {
		hldlog.Error(fmt.Sprintf("Failed to load configuration: %v", err))
	}

	ntpSrv := NewNTPServer(fmt.Sprintf("%s:%s", LocalHost.IP, LocalHost.Port))
	ntpSrv.Start()

	for {
		time.Sleep(60 * time.Second)
	}
}
  • 代碼細節(jié)說明
    NTP服務器在回復NTP客戶端的消息中其中OrigTS uint64(Originate Timestamp 起始時間戳)必須是NTP客戶端發(fā)送來的TransTS uint64(Transmit Timestamp 傳輸時間戳)。

驗證GoNTPSrv

上述實現的NTP服務已經過Go語言中開源的NTP Client庫 https://github.com/beevik/ntp 驗證。

  • UDP數據包
# 客戶端發(fā)送的數據
23 00 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 39 1C 79 9E 83 D3 D5 82

# 服務器返回的數據
24 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 EA D9 CF EE AD 4D DC 2B 39 1C 79 9E 83 D3 D5 82 EA D9 CF EE AD 4D DC 2B EA D9 CF EE AD 4D DC 2B
  • NTP Client的簡單源碼
package main

import (
	hldlog "NTPCli/log4go"
	"fmt"
	"log"
	"os"
	"os/exec"
	"time"

	"github.com/beevik/ntp"
	"gopkg.in/ini.v1"
)

type NetAddr struct {
	IP   string
	Port int
}

var RemoteAddr = NetAddr{IP: "0.0.0.0", Port: 60123}

func init() {
	hldlog.LoadConfiguration("./config/log.xml", "xml")
}

func main() {
	hldlog.Info("===NTP CLIENT Start===")

	currTime := time.Now()
	formattedTime := currTime.Format("2006-01-02 15:04:05.000")
	hldlog.Info(formattedTime)

	// 讀取INI配置文件
	iniConf, err := ini.Load("./config/config.ini")
	if err != nil {
		log.Fatalf("Fail to read INI file: %v", err)
	}

	remoteSection := iniConf.Section("NTP_SERVER")
	RemoteAddr.IP = remoteSection.Key("ip").String()
	RemoteAddr.Port, _ = remoteSection.Key("port").Int()
	hldlog.Info(fmt.Sprintf("ntp://%s:%d", RemoteAddr.IP, RemoteAddr.Port))

	// edu.ntp.org.cn
	// resp, err := ntp.Time("edu.ntp.org.cn")
	resp, err := ntp.Time(fmt.Sprintf("%s:%d", RemoteAddr.IP, RemoteAddr.Port))
	if err != nil {
		hldlog.Error(fmt.Sprintf("%v", err))
		os.Exit(-1)
	}
	hldlog.Info(resp.String())

	localTime := resp.Local()
	hldlog.Info(localTime.Format("2006-01-02 15:04:05.000"))

	// setTime(localTime)

	for {
		time.Sleep(60 * time.Second)
	}
}

到此這篇關于使用Go語言編寫一個NTP服務器的流程步驟的文章就介紹到這了,更多相關Go編寫NTP服務器內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • 詳解minio分布式文件存儲

    詳解minio分布式文件存儲

    MinIO 是一款基于 Go 語言的高性能、可擴展、云原生支持、操作簡單、開源的分布式對象存儲產品,這篇文章主要介紹了minio分布式文件存儲,需要的朋友可以參考下
    2023-10-10
  • 深入Golang的接口interface

    深入Golang的接口interface

    這篇文章主要介紹了深入Golang的接口interface,go不要求類型顯示地聲明實現了哪個接口,只要實現了相關的方法即可,編譯器就能檢測到,接下來關于接口interface的相關介紹需要的朋友可以參考下面文章內容
    2022-06-06
  • golang mapstructure庫的具體使用

    golang mapstructure庫的具體使用

    mapstructure用于將通用的map[string]interface{}解碼到對應的 Go 結構體中,或者執(zhí)行相反的操作,本文主要介紹了golang mapstructure庫的具體使用,感興趣的可以了解一下
    2023-09-09
  • 理解Golang中的數組(array)、切片(slice)和map

    理解Golang中的數組(array)、切片(slice)和map

    這篇文章主要介紹了理解Golang中的數組(array)、切片(slice)和map,本文先是給出代碼,然后一一分解,并給出一張內圖加深理解,需要的朋友可以參考下
    2014-10-10
  • Go語言中的iota關鍵字的使用

    Go語言中的iota關鍵字的使用

    這篇文章主要為大家詳細介紹了Go語言中的iota關鍵字的相關使用,文中的示例代碼講解詳細,對我們深入了解Go語言有一定的幫助,需要的可以參考下
    2023-08-08
  • Go語言Goroutinue和管道效率詳解

    Go語言Goroutinue和管道效率詳解

    這篇文章主要為大家介紹了Go語言Goroutinue和管道效率使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-09-09
  • golang封裝一個執(zhí)行命令行的函數(return?stderr/stdout/exitcode)示例代碼

    golang封裝一個執(zhí)行命令行的函數(return?stderr/stdout/exitcode)示例代碼

    在?Go?語言中,您可以使用?os/exec?包來執(zhí)行外部命令,不通過調用?shell,并且能夠獲得進程的退出碼、標準輸出和標準錯誤輸出,下面給大家分享golang封裝一個執(zhí)行命令行的函數(return?stderr/stdout/exitcode)的方法,感興趣的朋友跟隨小編一起看看吧
    2024-06-06
  • golang使用iconv報undefined:XXX的問題處理方案

    golang使用iconv報undefined:XXX的問題處理方案

    這篇文章主要介紹了golang使用iconv報undefined:XXX的問題處理方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • golang 定時任務方面time.Sleep和time.Tick的優(yōu)劣對比分析

    golang 定時任務方面time.Sleep和time.Tick的優(yōu)劣對比分析

    這篇文章主要介紹了golang 定時任務方面time.Sleep和time.Tick的優(yōu)劣對比分析,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-05-05
  • go?tar包歸檔文件處理操作全面指南

    go?tar包歸檔文件處理操作全面指南

    這篇文章主要為大家介紹了使用go?tar包歸檔文件處理操作全面指南,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-12-12

最新評論