最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

使用Go語(yǔ)言編寫(xiě)一個(gè)NTP服務(wù)器的流程步驟

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

NTP服務(wù)介紹

NTP服務(wù)器【Network Time Protocol(NTP)】是用來(lái)使計(jì)算機(jī)時(shí)間同步化的一種協(xié)議。

  • 應(yīng)用場(chǎng)景說(shuō)明
    為了確保封閉局域網(wǎng)內(nèi)多個(gè)服務(wù)器的時(shí)間同步,我們計(jì)劃部署一個(gè)網(wǎng)絡(luò)時(shí)間同步服務(wù)器(NTP服務(wù)器)。這一角色將由一臺(tái)個(gè)人筆記本電腦承擔(dān),該筆記本將連接到局域網(wǎng)中,并以其當(dāng)前時(shí)間為基準(zhǔn)。我們將利用這臺(tái)筆記本電腦作為NTP服務(wù)器,對(duì)局域網(wǎng)內(nèi)的多個(gè)運(yùn)行CentOS 8的服務(wù)器進(jìn)行時(shí)間校準(zhǔn),以保證系統(tǒng)時(shí)間的一致性和準(zhǔn)確性。

NTP協(xié)議

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

NTP報(bào)文說(shuō)明

  • NTP的報(bào)文是48字節(jié)
  1. 第1個(gè)字節(jié)可以理解為簡(jiǎn)易的報(bào)文頭,這8個(gè)bit包含Leap Indicator、NTP Version、Mode
    a> LI 占用2個(gè)bit
    b> VN 占用3個(gè)bit,筆者編寫(xiě)的服務(wù)器設(shè)置為版本v4.0
    c> Mode 占用3個(gè)bit,ntp server時(shí)為4,ntp client時(shí)為3
  2. 第2個(gè)字節(jié)為 Peer Clock Stratum
  3. 第3個(gè)字節(jié)為 Peer Polling Interval
  4. 第4個(gè)字節(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 參考時(shí)間戳
  9. 第25 - 32字節(jié)為 Originate Timestamp 起始時(shí)間戳
  10. 第33 - 40字節(jié)為 Receive Timestamp 接收時(shí)間戳
  11. 第 41 - 48字節(jié) Transmit Timestamp 傳輸時(shí)間戳

根據(jù)NTP報(bào)文編碼實(shí)現(xiàn)Go語(yǔ)言的結(jié)構(gòu)體

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 // 報(bào)文頭: 包含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 參考時(shí)間戳
	OrigTS  uint64 // Originate Timestamp 起始時(shí)間戳
	RecvTS  uint64 // Receive Timestamp   接收時(shí)間戳
	TransTS uint64 // Transmit Timestamp  傳輸時(shí)間戳
}

NTP服務(wù)器的源碼

  • ntpsrv.go
package main

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

const (
	STANDARD_PACKET_SIZE = 48 // 標(biāo)準(zhǔn)NTP的報(bào)文大小
)

type NTPServer struct {
	srvAddress string

	conn *net.UDPConn
	wait sync.WaitGroup

	ntpPack      NtpPacket // NTP協(xié)議報(bào)文
	requestCount uint64    // 請(qǐng)求計(jì)數(shù)
}

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 // 報(bào)文頭: 包含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 參考時(shí)間戳
	OrigTS  uint64 // Originate Timestamp 起始時(shí)間戳
	RecvTS  uint64 // Receive Timestamp   接收時(shí)間戳
	TransTS uint64 // Transmit Timestamp  傳輸時(shí)間戳
}

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實(shí)例
	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時(shí)間轉(zhuǎn)換為NTP時(shí)間
func ToNTPTime(t time.Time) uint64 {
	seconds := uint32(t.Unix()) + 2208988800 // NTP時(shí)間從1900年開(kāi)始計(jì)算
	fraction := uint32(float64(t.Nanosecond()) * (1 << 32) / 1e9)
	return uint64(seconds)<<32 | uint64(fraction)
}

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

// 啟動(dòng)NTP服務(wù)器
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
}

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

// 接收數(shù)據(jù)
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客戶(hù)端消息的時(shí)間
		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é)數(shù)組轉(zhuǎn)換為16進(jìn)制字符串
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] = ' ' // 每個(gè)16進(jìn)制數(shù)據(jù)之間加空格
		}
	}
	return string(hexString)
}

func ParseUDPPacket(buf []byte) (*NtpPacket, error) {
	if len(buf) < STANDARD_PACKET_SIZE { // 最小有效長(zhǎng)度為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日志庫(kù)
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)
	}
}
  • 代碼細(xì)節(jié)說(shuō)明
    NTP服務(wù)器在回復(fù)NTP客戶(hù)端的消息中其中OrigTS uint64(Originate Timestamp 起始時(shí)間戳)必須是NTP客戶(hù)端發(fā)送來(lái)的TransTS uint64(Transmit Timestamp 傳輸時(shí)間戳)。

驗(yàn)證GoNTPSrv

上述實(shí)現(xiàn)的NTP服務(wù)已經(jīng)過(guò)Go語(yǔ)言中開(kāi)源的NTP Client庫(kù) https://github.com/beevik/ntp 驗(yàn)證。

  • UDP數(shù)據(jù)包
# 客戶(hù)端發(fā)送的數(shù)據(jù)
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

# 服務(wù)器返回的數(shù)據(jù)
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的簡(jiǎn)單源碼
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)
	}
}

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

相關(guān)文章

  • Golang中的四個(gè)括號(hào)示例詳解

    Golang中的四個(gè)括號(hào)示例詳解

    這篇文章主要介紹了Golang中的四個(gè)括號(hào),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),通過(guò)實(shí)例代碼補(bǔ)充介紹了有效的括號(hào)golang實(shí)現(xiàn),需要的朋友可以參考下
    2024-03-03
  • 源碼剖析Golang中map擴(kuò)容底層的實(shí)現(xiàn)

    源碼剖析Golang中map擴(kuò)容底層的實(shí)現(xiàn)

    之前的文章詳細(xì)介紹過(guò)Go切片和map的基本使用,以及切片的擴(kuò)容機(jī)制。本文針對(duì)map的擴(kuò)容,會(huì)從源碼的角度全面的剖析一下map擴(kuò)容的底層實(shí)現(xiàn),需要的可以參考一下
    2023-03-03
  • go語(yǔ)言中json數(shù)據(jù)的讀取和寫(xiě)出操作

    go語(yǔ)言中json數(shù)據(jù)的讀取和寫(xiě)出操作

    這篇文章主要介紹了go語(yǔ)言中json數(shù)據(jù)的讀取和寫(xiě)出操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-04-04
  • Golang: 內(nèi)建容器的用法

    Golang: 內(nèi)建容器的用法

    這篇文章主要介紹了Golang: 內(nèi)建容器的用法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-05-05
  • 使用Go語(yǔ)言與MQTT進(jìn)行通信的示例代碼

    使用Go語(yǔ)言與MQTT進(jìn)行通信的示例代碼

    本文介紹了如何使用 Go 編程語(yǔ)言與 MQTT(Message Queuing Telemetry Transport)進(jìn)行通信,MQTT 是一種輕量級(jí)的消息傳輸協(xié)議,廣泛應(yīng)用于物聯(lián)網(wǎng)和實(shí)時(shí)通信場(chǎng)景,通過(guò)本文的指導(dǎo),您將學(xué)習(xí)如何使用 Go 語(yǔ)言創(chuàng)建 MQTT 客戶(hù)端,進(jìn)行消息的發(fā)布和訂閱,需要的朋友可以參考下
    2023-12-12
  • Go語(yǔ)言學(xué)習(xí)之文件操作方法詳解

    Go語(yǔ)言學(xué)習(xí)之文件操作方法詳解

    這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言中一些常見(jiàn)的文件操作,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Go語(yǔ)言有一定的幫助,需要的可以參考一下
    2022-04-04
  • 淺析在Go語(yǔ)言中如何實(shí)現(xiàn)協(xié)程池

    淺析在Go語(yǔ)言中如何實(shí)現(xiàn)協(xié)程池

    gammazero/workerpool?就是用來(lái)實(shí)現(xiàn)協(xié)程池的?Go?包,本文我們將一起來(lái)學(xué)習(xí)一下其使用方法,并深入其源碼來(lái)探究下如何實(shí)現(xiàn)一個(gè)?Go?協(xié)程池,需要的可以了解下
    2025-06-06
  • Go語(yǔ)言按字節(jié)截取字符串的方法

    Go語(yǔ)言按字節(jié)截取字符串的方法

    這篇文章主要介紹了Go語(yǔ)言按字節(jié)截取字符串的方法,涉及Go語(yǔ)言操作字符串的技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下
    2015-02-02
  • Go路由注冊(cè)方法詳解

    Go路由注冊(cè)方法詳解

    Go語(yǔ)言中,http.NewServeMux()和http.HandleFunc()是兩種不同的路由注冊(cè)方式,前者創(chuàng)建獨(dú)立的ServeMux實(shí)例,適合模塊化和分層路由,靈活性高,但啟動(dòng)服務(wù)器時(shí)需要顯式指定,后者使用全局默認(rèn)的http.DefaultServeMux,適合簡(jiǎn)單場(chǎng)景,感興趣的朋友跟隨小編一起看看吧
    2025-02-02
  • 淺析Go語(yǔ)言如何避免數(shù)據(jù)競(jìng)爭(zhēng)Data?Race和競(jìng)態(tài)條件Race?Condition

    淺析Go語(yǔ)言如何避免數(shù)據(jù)競(jìng)爭(zhēng)Data?Race和競(jìng)態(tài)條件Race?Condition

    在并發(fā)編程中,數(shù)據(jù)競(jìng)爭(zhēng)?(Data?Race)?和?競(jìng)態(tài)條件?(Race?Condition)?是兩個(gè)常見(jiàn)的問(wèn)題,本文將簡(jiǎn)單介紹一下二者如何避免,有需要的可以了解下
    2025-01-01

最新評(píng)論

甘泉县| 烟台市| 肥城市| 关岭| 祁东县| 玛纳斯县| 贡觉县| 京山县| 武冈市| 龙川县| 喀喇沁旗| 繁峙县| 文水县| 潞西市| 威信县| 武夷山市| 凤城市| 嘉峪关市| 简阳市| 阳山县| 邵武市| 和政县| 闵行区| 工布江达县| 孟津县| 临澧县| 兴义市| 山阳县| 化隆| 蒲城县| 麻江县| 陈巴尔虎旗| 息烽县| 策勒县| 永和县| 昌平区| 沭阳县| 南安市| 江永县| 聂拉木县| 柳林县|