هڪ سادي NTP ڪلائنٽ لکڻ

هيلو، هبروسرز. اڄ مان ڳالهائڻ چاهيان ٿو ته توهان جي پنهنجي سادي اين ٽي پي ڪلائنٽ کي ڪيئن لکجي. بنيادي طور تي، گفتگو پيڪٽ جي جوڙجڪ ۽ NTP سرور کان جواب جي پروسيسنگ جو طريقو ڏانهن رخ ڪندو. ڪوڊ پائٿون ۾ لکيو ويندو، ڇاڪاڻ ته مون کي لڳي ٿو ته اهڙين شين لاءِ ڪا به بهتر ٻولي ناهي. Connoisseurs نوٽ ڪندا ڪوڊ جي هڪجهڙائي کي ntplib ڪوڊ سان - مان ان کان ”حوصلو“ حاصل ڪري چڪو هوس.

پوء ڇا واقعي NTP آهي؟ NTP صحيح وقت جي سرورن سان رابطي لاءِ هڪ پروٽوڪول آهي. هي پروٽوڪول ڪيترن ئي جديد مشينن ۾ استعمال ڪيو ويندو آهي. مثال طور، ونڊوز ۾ w32tm سروس.

مجموعي طور تي NTP پروٽوڪول جا 5 نسخا آھن. پهريون، نسخو 0 (1985، RFC958))، هن وقت متروڪ سمجهيو وڃي ٿو. هاڻي نوان استعمال ڪيا ويا آهن، پهريون (1، RFC1988)، ٻيو (1059، RFC2)، ٽيون (1989، RFC1119) ۽ چوٿون (3، RFC1992). نسخو 1305-4 هڪ ٻئي سان هم آهنگ آهن؛ اهي صرف سرور آپريشن الگورتھم ۾ مختلف آهن.

پيڪيج فارميٽ

هڪ سادي NTP ڪلائنٽ لکڻ

ليپ اشارو (اصلاح جو اشارو) - هڪ انگ جيڪو اشارو ڏئي ٿو هڪ ڊيڄاريندڙ همراه جي سيڪنڊ بابت. مطلب:

  • 0 - ڪابه اصلاح
  • 1 - ڏينهن جو آخري منٽ 61 سيڪنڊن تي مشتمل آهي
  • 2 - ڏينهن جو آخري منٽ 59 سيڪنڊن تي مشتمل آهي
  • 3 - سرور جي خرابي (وقت هم وقت نه آهي)

نسخه نمبر (ورزن نمبر) - NTP پروٽوڪول ورزن نمبر (1-4).

فيشن (موڊ) - پيڪٽ موڪليندڙ جو آپريٽنگ موڊ. قدر 0 کان 7 تائين، سڀ کان عام:

  • 3 - ڪسٽمر
  • 4 - سرور
  • 5 - براڊڪاسٽ موڊ

اسٽريم (ليئرنگ ليول) - سرور ۽ ريفرنس ڪلاڪ جي وچ ۾ وچولي پرت جو تعداد (1 - سرور سڌو سنئون ريفرنس ڪلاڪ مان ڊيٽا وٺي ٿو، 2 - سرور سرور کان ڊيٽا وٺي ٿو ليئر 1، وغيره).
تلاء هڪ دستخط ٿيل عدد آهي جيڪو مسلسل پيغامن جي وچ ۾ وڌ کان وڌ وقفي جي نمائندگي ڪري ٿو. NTP ڪلائنٽ ھتي بيان ڪري ٿو وقفو جنھن تي اھو سرور کي پول ڪرڻ جي اميد رکي ٿو، ۽ NTP سرور اھو وقفو بيان ڪري ٿو جنھن تي اھو پولنگ ٿيڻ جي اميد رکي ٿو. قدر سيڪنڊن جي بائنري لاگارٿم جي برابر آهي.
سڌائي (درستگي) ھڪ دستخط ٿيل عدد آھي جيڪو نظام جي گھڙي جي درستگي کي ظاھر ڪري ٿو. قدر سيڪنڊن جي بائنري لاگارٿم جي برابر آهي.
روٽ دير (سرور ۾ دير) - ڪلاڪ جي ريڊنگز کي NTP سرور تائين پهچڻ جو وقت، سيڪنڊن جي مقرر پوائنٽ نمبر جي طور تي.
روٽ پکيڙڻ (سرور اسپريڊ) - اين ٽي پي سرور ڪلاڪ ريڊنگس جو اسپريڊ چند سيڪنڊن جي طور تي مقرر پوائنٽ سان.
ريفري ID (ذريعو سڃاڻپ ڪندڙ) - گھڙي جي سڃاڻپ. جيڪڏهن سرور وٽ اسٽريٽم 1 آهي، ته پوءِ ريف 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

سرور ڏانهن پيڪٽ موڪلڻ (۽ وصول ڪرڻ) لاءِ، اسان کي ان کي بائيٽ صف ۾ تبديل ڪرڻ جي قابل هوندو.
ھن (۽ ريورس) آپريشن لاءِ، اسين ٻه ڪم لکنداسين - 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 (ڪلائنٽ موڊ).

سرور، درخواست قبول ڪرڻ کان پوء، اين ٽي پي پيڪٽ ۾ سڀني شعبن ۾ ڀريو، فيلڊ ۾ نقل ڪندي بڻيو قدر کان ڏيون، جيڪو درخواست ۾ آيو. اهو مون لاء هڪ راز آهي ڇو ته گراهڪ فوري طور تي پنهنجي وقت جي قيمت کي فيلڊ ۾ نه ڀريو بڻيو. نتيجي طور، جڏهن پيڪٽ واپس اچي ٿو، ڪلائنٽ وٽ 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. 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

تبصرو شامل ڪريو