සරල NTP සේවාදායකයක් ලිවීම

හෙලෝ, හබ්‍රවුසර්ස්. අද මම ඔබේම සරල NTP සේවාදායකයා ලියන්නේ කෙසේද යන්න ගැන කතා කිරීමට අවශ්යයි. මූලික වශයෙන්, සංවාදය පැකට්ටුවේ ව්යුහය සහ NTP සේවාදායකයෙන් ප්රතිචාරය සැකසීමේ ක්රමය වෙත හැරෙනු ඇත. කේතය පයිතන් වලින් ලියා ඇත, මන්ද එවැනි දේ සඳහා වඩා හොඳ භාෂාවක් නොමැති බව මට පෙනේ. ntplib කේතය සමඟ කේතයේ සමානකම රසවිඳින්නන් සටහන් කරනු ඇත - මම එයින් "ආනුභාවයෙන්" සිටියෙමි.

ඉතින් ඇත්තටම NTP යනු කුමක්ද? NTP යනු නිශ්චිත කාල සේවාදායකයන් සමඟ අන්තර්ක්‍රියා කිරීම සඳහා වන ප්‍රොටෝකෝලයකි. මෙම ප්රොටෝකෝලය බොහෝ නවීන යන්ත්රවල භාවිතා වේ. උදාහරණයක් ලෙස, windows හි w32tm සේවාව.

NTP ප්‍රොටෝකෝලයේ සම්පූර්ණ අනුවාද 5 ක් ඇත. පළමු, අනුවාදය 0 (1985, RFC958)), දැනට යල් පැන ගිය එකක් ලෙස සැලකේ. දැන් අලුත් ඒවා භාවිතා කරන්නේ, 1 වන (1988, RFC1059), 2nd (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 සේවාදායක ඔරලෝසු කියවීම් පැතිරීම.
Ref id (මූලාශ්ර හඳුනාගැනීම්) - ඔරලෝසු හැඳුනුම්පත. සේවාදායකයට ස්ථර 1 තිබේ නම්, ref id යනු පරමාණුක ඔරලෝසුවේ නමයි (ASCII අක්ෂර 4). සේවාදායකය වෙනත් සේවාදායකයක් භාවිතා කරන්නේ නම්, ref id හි මෙම සේවාදායකයේ ලිපිනය අඩංගු වේ.
අවසාන ක්ෂේත්‍ර 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

සේවාදායකය වෙත පැකට්ටුවක් යැවීමට (සහ ලබා ගැනීමට), අපට එය බයිට් අරාවක් බවට පත් කිරීමට හැකි විය යුතුය.
මෙම (සහ ප්‍රතිලෝම) මෙහෙයුම සඳහා, අපි කාර්යයන් දෙකක් ලියන්නෙමු - pack() සහ unpack():

ඇසුරුම් කාර්යය

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))

unpack කාර්යය

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)

සේවාදායකයෙන් දත්ත සැකසීම

සේවාදායකයෙන් දත්ත සැකසීම Raymond M. Smullyan (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)

ප්රයෝජනවත් සබැඳිය.

මූලාශ්රය: www.habr.com

අදහස් එක් කරන්න