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

使用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客戶端消息的時(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客戶端的消息中其中OrigTS uint64(Originate Timestamp 起始時(shí)間戳)必須是NTP客戶端發(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ù)包
# 客戶端發(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)文章

  • 詳解minio分布式文件存儲(chǔ)

    詳解minio分布式文件存儲(chǔ)

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

    深入Golang的接口interface

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

    golang mapstructure庫(kù)的具體使用

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

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

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

    Go語(yǔ)言中的iota關(guān)鍵字的使用

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

    Go語(yǔ)言Goroutinue和管道效率詳解

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

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

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

    golang使用iconv報(bào)undefined:XXX的問(wèn)題處理方案

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

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

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

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

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

最新評(píng)論