సాధారణ NTP క్లయింట్‌ను వ్రాయడం

హలో, హబ్రూజర్స్. ఈ రోజు నేను మీ స్వంత సాధారణ NTP క్లయింట్‌ను ఎలా వ్రాయాలి అనే దాని గురించి మాట్లాడాలనుకుంటున్నాను. ప్రాథమికంగా, సంభాషణ ప్యాకెట్ యొక్క నిర్మాణం మరియు NTP సర్వర్ నుండి ప్రతిస్పందనను ప్రాసెస్ చేసే పద్ధతికి మారుతుంది. కోడ్ పైథాన్‌లో వ్రాయబడుతుంది, ఎందుకంటే అలాంటి వాటికి మంచి భాష లేదని నాకు అనిపిస్తోంది. ntplib కోడ్‌తో కోడ్ యొక్క సారూప్యతను వ్యసనపరులు గమనిస్తారు - నేను దాని నుండి "ప్రేరేపిత" పొందాను.

కాబట్టి NTP అంటే ఏమిటి? NTP అనేది ఖచ్చితమైన సమయ సర్వర్‌లతో పరస్పర చర్య కోసం ఒక ప్రోటోకాల్. ఈ ప్రోటోకాల్ అనేక ఆధునిక యంత్రాలలో ఉపయోగించబడుతుంది. ఉదాహరణకు, విండోస్‌లోని w32tm సేవ.

మొత్తం NTP ప్రోటోకాల్ యొక్క 5 వెర్షన్లు ఉన్నాయి. మొదటిది, వెర్షన్ 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 సర్వర్ క్లాక్ రీడింగులను స్థిర బిందువుతో అనేక సెకన్లుగా విస్తరించడం.
Ref id (సోర్స్ ఐడెంటిఫైయర్) - క్లాక్ ఐడి. సర్వర్‌లో స్ట్రాటమ్ 1 ఉంటే, ref id అనేది అటామిక్ క్లాక్ పేరు (4 ASCII అక్షరాలు). సర్వర్ మరొక సర్వర్‌ని ఉపయోగిస్తుంటే, 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

సర్వర్‌కు ప్యాకెట్‌ను పంపడానికి (మరియు స్వీకరించడానికి), మేము దానిని బైట్ శ్రేణిగా మార్చగలగాలి.
ఈ (మరియు రివర్స్) ఆపరేషన్ కోసం, మేము రెండు ఫంక్షన్లను వ్రాస్తాము - ప్యాక్() మరియు అన్ప్యాక్():

ప్యాక్ ఫంక్షన్

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)

సర్వర్ నుండి డేటాను ప్రాసెస్ చేస్తోంది

సర్వర్ నుండి డేటాను ప్రాసెస్ చేయడం అనేది రేమండ్ M. స్ముల్లియన్ (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

ఒక వ్యాఖ్యను జోడించండి