ቀላል የNTP ደንበኛ በመጻፍ ላይ

ሰላም የሀብራውተሮች። ዛሬ የእራስዎን ቀላል የኤንቲፒ ደንበኛ እንዴት እንደሚጽፉ ማውራት እፈልጋለሁ። በመሠረቱ, ውይይቱ ወደ ፓኬቱ መዋቅር እና ከኤንቲፒ አገልጋይ የሚሰጠው ምላሽ እንዴት እንደሚሰራ. ኮዱ በፓይቶን ውስጥ ይጻፋል, ምክንያቱም በእኔ አስተያየት, ለእንደዚህ ዓይነቶቹ ነገሮች በቀላሉ የተሻለ ቋንቋ የለም. Connoisseurs ከ ntplib ኮድ ጋር ያለውን ኮድ ተመሳሳይነት ትኩረት ይሰጣሉ - በእሱ "ተመስጦ" ነበር.

ስለዚህ NTP ለማንኛውም ምንድን ነው? NTP ከሰአት አገልጋዮች ጋር የመግባቢያ ፕሮቶኮል ነው። ይህ ፕሮቶኮል በብዙ ዘመናዊ ማሽኖች ውስጥ ጥቅም ላይ ይውላል. ለምሳሌ በዊንዶው ላይ የw32tm አገልግሎት።

በአጠቃላይ 5 የNTP ፕሮቶኮል ስሪቶች አሉ። የመጀመሪያው፣ ስሪት 0 (1985፣ RFC958) በአሁኑ ጊዜ ጊዜ ያለፈበት እንደሆነ ይቆጠራል። አዲሶች በአሁኑ ጊዜ ጥቅም ላይ ይውላሉ፣ 1ኛ (1988፣ RFC1059)፣ 2ኛ (1989፣ RFC1119)፣ 3ኛ (1992፣ RFC1305) እና 4ኛ (1996፣ RFC2030)። ስሪቶች 1-4 እርስ በርስ የሚጣጣሙ ናቸው, በአገልጋዮቹ ስልተ ቀመሮች ውስጥ ብቻ ይለያያሉ.

የፓኬት ቅርጸት

ቀላል የNTP ደንበኛ በመጻፍ ላይ

የመዝለል አመልካች (የማስተካከያ አመልካች) የመዝለል ሁለተኛ ማስጠንቀቂያን የሚያመለክት ቁጥር ነው። ትርጉም፡-

  • 0 - ምንም እርማት የለም
  • 1 - የቀኑ የመጨረሻ ደቂቃ 61 ሰከንድ ይይዛል
  • 2 - የቀኑ የመጨረሻ ደቂቃ 59 ሰከንድ ይይዛል
  • 3 - የአገልጋይ ውድቀት (ጊዜ ያለፈበት)

የስሪት ቁጥር (ስሪት ቁጥር) - NTP ፕሮቶኮል ስሪት ቁጥር (1-4).

ሞድ (ሞድ) - የፓኬት ላኪው የአሠራር ሁኔታ። ዋጋ ከ0 እስከ 7፣ በጣም የተለመደ፡

  • 3 - ደንበኛ
  • 4 - አገልጋይ
  • 5 - የስርጭት ሁነታ

ስትራታም (የመሸፈኛ ደረጃ) - በአገልጋዩ እና በማጣቀሻው መካከል ያለው መካከለኛ የንብርብሮች ብዛት (1 - አገልጋዩ በቀጥታ ከማጣቀሻው ሰዓት ይወስዳል ፣ 2 - አገልጋዩ ከ 1 ደረጃ ፣ ወዘተ ጋር ከአገልጋዩ መረጃ ይወስዳል)።
ፑል በተከታታይ መልእክቶች መካከል ያለውን ከፍተኛ ክፍተት የሚወክል የተፈረመ ኢንቲጀር ነው። የNTP ደንበኛ አገልጋዩን ለመጠየቅ የሚጠብቀውን የጊዜ ክፍተት እዚህ ይገልፃል፣ እና የNTP አገልጋዩ ድምጽ እንዲሰጥ የሚጠብቀውን የጊዜ ክፍተት ይገልጻል። እሴቱ ከሰከንዶች ሁለትዮሽ ሎጋሪዝም ጋር እኩል ነው።
ትክክልነት (ትክክለኛነት) የስርዓት ሰዓቱን ትክክለኛነት የሚወክል ፊርማ ኢንቲጀር ነው። እሴቱ ከሰከንዶች ሁለትዮሽ ሎጋሪዝም ጋር እኩል ነው።
ሥር መዘግየት (የአገልጋይ መዘግየት) ሰዓቱ ወደ NTP አገልጋይ ለመድረስ የሚፈጀው ጊዜ ነው፣ እንደ ቋሚ ነጥብ የሰከንዶች ቁጥር።
ሥር መበታተን (የአገልጋይ መበታተን) - የ NTP አገልጋይ ሰዓት መበታተን እንደ ቋሚ-ነጥብ የሴኮንዶች ቁጥር.
ማጣቀሻ መታወቂያ (ምንጭ መታወቂያ) - የመመልከቻ መታወቂያ። አገልጋዩ ስትራተም 1 ካለው፣ ከዚያ ref id የአቶሚክ ሰዓት (4 ASCII ቁምፊዎች) ስም ነው። አገልጋዩ ሌላ አገልጋይ የሚጠቀም ከሆነ፣ ሪፍ መታወቂያው የዚህን አገልጋይ አድራሻ ይይዛል።
የመጨረሻዎቹ 4 መስኮች ጊዜን ይወክላሉ - 32 ቢት - ኢንቲጀር ክፍል ፣ 32 ቢት - ክፍልፋይ።
ማጣቀሻ - በአገልጋዩ ላይ ያለው የቅርብ ጊዜ ሰዓት።
አመጣጥ - ፓኬጁ የተላከበት ጊዜ (በአገልጋዩ የተሞላ - ከዚህ በታች ባለው ተጨማሪ)።
ተቀበል - ፓኬጁ በአገልጋዩ የተቀበለበት ጊዜ።
አሳለፈ - ፓኬጁ ከአገልጋዩ ወደ ደንበኛው የተላከበት ጊዜ (በደንበኛው የተሞላ ፣ ከዚህ በታች ባለው ተጨማሪ)።

የመጨረሻዎቹ ሁለት መስኮች ግምት ውስጥ አይገቡም.

የእኛን ጥቅል እንፃፍ፡-

የጥቅል ኮድ

class NTPPacket:
    _FORMAT = "!B B b b 11I"

    def __init__(self, version_number=2, mode=3, transmit=0):
        # Necessary of enter leap second (2 bits)
        self.leap_indicator = 0
        # Version of protocol (3 bits)
        self.version_number = version_number
        # Mode of sender (3 bits)
        self.mode = mode
        # The level of "layering" reading time (1 byte)
        self.stratum = 0
        # Interval between requests (1 byte)
        self.pool = 0
        # Precision (log2) (1 byte)
        self.precision = 0
        # Interval for the clock reach NTP server (4 bytes)
        self.root_delay = 0
        # Scatter the clock NTP-server (4 bytes)
        self.root_dispersion = 0
        # Indicator of clocks (4 bytes)
        self.ref_id = 0
        # Last update time on server (8 bytes)
        self.reference = 0
        # Time of sending packet from local machine (8 bytes)
        self.originate = 0
        # Time of receipt on server (8 bytes)
        self.receive = 0
        # Time of sending answer from server (8 bytes)
        self.transmit = transmit

ፓኬት ወደ አገልጋዩ ለመላክ (እና ለመቀበል) ወደ ባይት ድርድር መቀየር መቻል አለብን።
ለዚህ (እና በተገላቢጦሽ) አሠራር ሁለት ተግባራትን እንጽፋለን - ጥቅል () እና ጥቅል ()

ጥቅል ተግባር

def pack(self):
        return struct.pack(NTPPacket._FORMAT,
                (self.leap_indicator << 6) + 
                    (self.version_number << 3) + self.mode,
                self.stratum,
                self.pool,
                self.precision,
                int(self.root_delay) + get_fraction(self.root_delay, 16),
                int(self.root_dispersion) + 
                    get_fraction(self.root_dispersion, 16),
                self.ref_id,
                int(self.reference),
                get_fraction(self.reference, 32),
                int(self.originate),
                get_fraction(self.originate, 32),
                int(self.receive),
                get_fraction(self.receive, 32),
                int(self.transmit),
                get_fraction(self.transmit, 32))

የማሸግ ተግባር

def unpack(self, data: bytes):
        unpacked_data = struct.unpack(NTPPacket._FORMAT, data)

        self.leap_indicator = unpacked_data[0] >> 6  # 2 bits
        self.version_number = unpacked_data[0] >> 3 & 0b111  # 3 bits
        self.mode = unpacked_data[0] & 0b111  # 3 bits

        self.stratum = unpacked_data[1]  # 1 byte
        self.pool = unpacked_data[2]  # 1 byte
        self.precision = unpacked_data[3]  # 1 byte

        # 2 bytes | 2 bytes
        self.root_delay = (unpacked_data[4] >> 16) +
            (unpacked_data[4] & 0xFFFF) / 2 ** 16
         # 2 bytes | 2 bytes
        self.root_dispersion = (unpacked_data[5] >> 16) +
            (unpacked_data[5] & 0xFFFF) / 2 ** 16 

        # 4 bytes
        self.ref_id = str((unpacked_data[6] >> 24) & 0xFF) + " " + 
                      str((unpacked_data[6] >> 16) & 0xFF) + " " +  
                      str((unpacked_data[6] >> 8) & 0xFF) + " " +  
                      str(unpacked_data[6] & 0xFF)

        self.reference = unpacked_data[7] + unpacked_data[8] / 2 ** 32  # 8 bytes
        self.originate = unpacked_data[9] + unpacked_data[10] / 2 ** 32  # 8 bytes
        self.receive = unpacked_data[11] + unpacked_data[12] / 2 ** 32  # 8 bytes
        self.transmit = unpacked_data[13] + unpacked_data[14] / 2 ** 32  # 8 bytes

        return self

ለሰነፎች ፣ እንደ መተግበሪያ - ጥቅሉን ወደ ቆንጆ ሕብረቁምፊ የሚቀይር ኮድ

def to_display(self):
        return "Leap indicator: {0.leap_indicator}n" 
                "Version number: {0.version_number}n" 
                "Mode: {0.mode}n" 
                "Stratum: {0.stratum}n" 
                "Pool: {0.pool}n" 
                "Precision: {0.precision}n" 
                "Root delay: {0.root_delay}n" 
                "Root dispersion: {0.root_dispersion}n" 
                "Ref id: {0.ref_id}n" 
                "Reference: {0.reference}n" 
                "Originate: {0.originate}n" 
                "Receive: {0.receive}n" 
                "Transmit: {0.transmit}"
                .format(self)

ጥቅል ወደ አገልጋዩ በመላክ ላይ

የተሞሉ መስኮች ያለው ፓኬት ወደ አገልጋዩ ይላኩ። ትርጉም, ሞድ и አሳለፈ. በ አሳለፈ የአሁኑን ጊዜ በአካባቢያዊ ማሽን (ከጃንዋሪ 1, 1900 ጀምሮ የሰከንዶች ብዛት) ፣ ስሪት - ከ1-4 ማንኛውም ፣ ሞድ - 3 (የደንበኛ ሁነታ) ላይ መግለጽ አለብዎት።

አገልጋዩ ጥያቄውን ከተቀበለ በኋላ በ NTP ፓኬት ውስጥ ያሉትን ሁሉንም መስኮች ይሞላል, ወደ መስኩ ይገለበጣል አመጣጥ ዋጋ ከ አሳለፈበጥያቄው ውስጥ የመጣው. ደንበኛው በሜዳ ውስጥ ያለውን ጊዜ ዋጋ ወዲያውኑ መሙላት የማይችልበት ምክንያት ለእኔ እንቆቅልሽ ነው። አመጣጥ. በዚህ ምክንያት ፓኬጁ ተመልሶ ሲመጣ ደንበኛው 4 ጊዜ እሴቶች አሉት - ጥያቄው የተላከበት ጊዜ (አመጣጥ), አገልጋዩ ጥያቄውን የተቀበለበት ጊዜ (ተቀበልአገልጋዩ ምላሹን የላከበት ጊዜ (አሳለፈ) እና በደንበኛው ምላሽ የተቀበለበት ጊዜ - ይድረሱ (በጥቅሉ ውስጥ አይደለም). በእነዚህ እሴቶች ትክክለኛውን ጊዜ ማዘጋጀት እንችላለን.

ጥቅል መላክ እና ኮድ መቀበል

# Time difference between 1970 and 1900, seconds
FORMAT_DIFF = (datetime.date(1970, 1, 1) - datetime.date(1900, 1, 1)).days * 24 * 3600
# Waiting time for recv (seconds)
WAITING_TIME = 5
server = "pool.ntp.org"
port = 123
    
packet = NTPPacket(version_number=2, mode=3, transmit=time.time() + FORMAT_DIFF)
answer = NTPPacket()
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
    s.settimeout(WAITING_TIME)
    s.sendto(packet.pack(), (server, port))
    data = s.recv(48)
    arrive_time = time.time() + FORMAT_DIFF
    answer.unpack(data)

የውሂብ ሂደት ከአገልጋዩ

ከአገልጋዩ የተገኘው መረጃ ሂደት የእንግሊዛዊው ጨዋ ሰው ከቀድሞው የሬይመንድ ኤም.ስሞሊያን ችግር (1978) ድርጊት ጋር ተመሳሳይ ነው: - “አንድ ሰው የእጅ ሰዓት አልነበረውም ፣ ግን በቤት ውስጥ ትክክለኛ የግድግዳ ሰዓት ነበር ፣ አንዳንድ ጊዜ ነፋስን ረስተዋል. አንድ ቀን ሰዓቱን እንደገና ማስጀመር ረስቶ ጓደኛውን ሊጠይቀው ሄዶ ምሽቱን አብሮት አሳለፈ እና ወደ ቤት ሲመለስ ሰዓቱን በትክክል ማዘጋጀት ቻለ። የጉዞ ሰዓቱ አስቀድሞ ካልታወቀ እንዴት ይህን ማድረግ ቻለ? መልሱ ነው፡- “አንድ ሰው ከቤት ሲወጣ ሰዓቱን ከፍ አድርጎ የእጆቹን ቦታ ያስታውሳል። ወደ ጓደኛው በመምጣት እንግዶቹን ትቶ የመድረሱን እና የመነሻውን ጊዜ ያስተውላል. ይህ ለምን ያህል ጊዜ እንደሄደ ለማወቅ ያስችለዋል. ወደ ቤት መመለስ እና ሰዓቱን በመመልከት አንድ ሰው የማይኖርበትን ጊዜ ይወስናል. ሰውየው ከጎበኘበት ጊዜ በመቀነስ በመንገዱ ላይ ያሳለፈውን እና የተመለሰውን ጊዜ ይገነዘባል። በመንገዱ ላይ የሚያሳልፈውን ግማሽ ጊዜ እንግዶቹን ለቅቆ በሚወጣበት ጊዜ ላይ በመጨመር ወደ ቤት የሚደርስበትን ጊዜ ለማወቅ እና የሰዓቱን እጆች ለማስተካከል እድሉን ያገኛል.

አገልጋዩ በጥያቄው ላይ ሲሰራ የነበረውን ጊዜ ይፈልጉ፡-

  1. የፓኬቱን የጉዞ ጊዜ ከደንበኛው ወደ አገልጋዩ መፈለግ፡- ((መድረስ - መነሻ) - (አስተላልፍ - ተቀበል)) / 2
  2. በደንበኛው እና በአገልጋይ ጊዜ መካከል ያለውን ልዩነት ይፈልጉ-
    ተቀበል - መነሻ - ((መድረስ - መነሻ) - (አስተላልፍ - ተቀበል)) / 2 =
    2 * ተቀበል - 2 * መነሻ - ደረሰ + መነሻ + ማስተላለፍ - ተቀበል =
    ተቀበል - መነሻ - ደረሰ + አስተላልፍ

የተቀበለውን እሴት ወደ አካባቢያዊ ጊዜ እንጨምራለን እና በህይወት ይደሰቱ።

የውጤት ውጤት

time_different = answer.get_time_different(arrive_time)
result = "Time difference: {}nServer time: {}n{}".format(
    time_different,
    datetime.datetime.fromtimestamp(time.time() + time_different).strftime("%c"),
    answer.to_display())
print(result)

ጠቃሚ ሳንቲም.

ምንጭ: hab.com

አስተያየት ያክሉ