مرحبا يا حبري اليوم أريد أن أتحدث عن كيفية كتابة عميل NTP البسيط الخاص بي. في الأساس ، سنتحدث عن بنية الحزمة وكيفية معالجة الاستجابة من خادم NTP. سيتم كتابة الرمز في بيثون ، لأنه ، على ما يبدو لي ، لا يمكن العثور على أفضل لغة لمثل هذه الأشياء. سيهتم الخبراء بالتشابه بين الكود والرمز ntplib - لقد كنت "مستوحاة" من ذلك.
فما هو بالضبط NTP؟ NTP - بروتوكول التفاعل مع خوادم الوقت بالضبط. يستخدم هذا البروتوكول في العديد من الأجهزة الحديثة.
على سبيل المثال ، خدمة w32tm في ويندوز.هناك 5 إصدارات من بروتوكول NTP في المجموع. الإصدار الأول (0 ، 1985 ، RFC958)) يعتبر حالياً قديمًا. الآن تستخدم الأحدث ، الأولى (1988 ، RFC1059) ، والثانية (1989 ، RFC1119) ، الثالثة (1992 ، RFC1305) والرابعة (1996 ، RFC2030). الإصدارات 1-4 متوافقة مع بعضها البعض ، فهي تختلف فقط في خوارزميات تشغيل الخادم.
تنسيق الحزمة
مؤشر قفزة (
مؤشر تصحيح) - رقم يظهر تحذير حول التنسيق الثاني. وهذا يعني:
- 0 - لا تصحيح
- 1 - الدقيقة الأخيرة من اليوم تحتوي على 61 ثانية
- 2 - الدقيقة الأخيرة من اليوم تحتوي على 59 ثانية
- 3 - عطل الخادم (الوقت غير متزامن)
رقم الإصدار -
رقم إصدار بروتوكول NTP (1-4).
الوضع - وضع تشغيل مرسل الحزمة. القيمة من 0 إلى 7 ، والأكثر شيوعًا:
- 3 - العميل
- 4 - الخادم
- 5 - وضع البث
الطبقة (مستوى
الطبقة ) - عدد الطبقات الوسيطة بين الخادم والساعة المرجعية (1 - يأخذ الخادم البيانات مباشرة من الساعة المرجعية ، 2 - يأخذ الخادم البيانات من الخادم بمستوى 1 ، إلخ).
Poll هو عدد صحيح موقّع يمثل الحد الأقصى الفاصل بين الرسائل المتتالية. هنا ، يشير عميل NTP إلى الفاصل الزمني الذي يتوقع عنده استقصاء الخادم ، ويشير خادم NTP إلى الفاصل الزمني الذي يتوقع استطلاع الرأي فيه. القيمة هي لوغاريتم الثواني.
الدقة هي عدد صحيح موقّع يمثل دقة ساعة النظام. القيمة هي لوغاريتم الثواني.
تأخير الجذر (
تأخير الخادم) - الوقت الذي تصل فيه الساعة إلى خادم NTP ، حيث يكون عدد الثواني بنقطة ثابتة.
تشتت الجذر - مبعثر ساعة خادم NTP كعدد الثواني بنقطة ثابتة.
معرف المرجع (
معرف المصدر) - معرف الساعة. إذا كان لدى الخادم طبقة من 1 ، فالمعرّف ref هو اسم الساعة الذرية (4 أحرف ASCII). إذا كان الخادم يستخدم خادمًا آخر ، فسيتم كتابة عنوان هذا الخادم في معرف المرجع.
آخر 4 حقول هي الوقت - 32 بت - الجزء الصحيح ، 32 بت - الجزء الكسري.
المرجع - آخر ساعة على الخادم.
تنشأ - الوقت الذي تم فيه إرسال الحزمة (تم ملؤها بواسطة الخادم - أكثر على ذلك أدناه).
وقت الاستلام - تم استلام الحزمة بواسطة الخادم.
نقل - وقت إرسال الحزمة من الخادم إلى العميل (شغلها العميل ، المزيد عن ذلك أدناه).
لن نفكر في الحقلين الأخيرين.
دعنا نكتب حزمة لدينا:
رمز الحزمةclass NTPPacket: _FORMAT = "!BB bb 11I" def __init__(self, version_number=2, mode=3, transmit=0):
لإرسال (وتلقي) حزمة إلى الخادم ، يجب أن نكون قادرين على تحويلها إلى مجموعة من وحدات البايت.
من أجل هذه العملية (والخلفية) ، سنكتب وظيفتين - 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))
لتحديد الجزء الكسري من الرقم للكتابة إلى الحزمة ، نحتاج إلى الدالة get_fraction ():
get_fraction () def get_fraction(number, precision): return int((number - int(number)) * 2 ** precision)
فك الوظيفة def unpack(self, data: bytes): unpacked_data = struct.unpack(NTPPacket._FORMAT, data) self.leap_indicator = unpacked_data[0] >> 6
للأشخاص كسول ، كتطبيق - رمز الذي يحول الحزمة إلى سلسلة جميلة 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)
إرسال حزمة إلى الخادم
من الضروري إرسال حزمة إلى الخادم مع حقول
الإصدار والوضع وإرسال المملوءة. في
Transmit ، تحتاج إلى تحديد الوقت الحالي على الجهاز المحلي (عدد الثواني منذ 1 يناير 1900) ، الإصدار - أي من 1-4 ، الوضع - 3 (وضع العميل).
بعد قبول الطلب ، يملأ الخادم جميع الحقول الموجودة في حزمة NTP عن طريق نسخ قيمة
الإرسال من الطلب إلى حقل
الأصل . إنه لغزا بالنسبة لي لماذا لا يمكن للعميل أن يملأ على الفور القيمة الزمنية في حقل
المنشأ . نتيجة لذلك ، عند وصول الحزمة مرة أخرى ، يكون للعميل 4 مرات - وقت إرسال الطلب (
منشأ ) ، وقت تلقي الخادم للطلب (
تلقي ) ، والوقت الذي أرسل فيه الخادم الاستجابة (
الإرسال ) ، والوقت الذي تلقى فيه العميل الاستجابة -
الوصول (ليس في الحزمة). باستخدام هذه القيم ، يمكننا ضبط الوقت الصحيح.
معالجة البيانات من الخادم
تشبه معالجة البيانات من الخادم تصرفات رجل نبيل من المهمة القديمة لريمون م. سوليان (1978): "لم يكن لدى شخص ساعة ، ولكن من ناحية أخرى كانت هناك ساعة حائط دقيقة نسيها في بعض الأحيان. ذات مرة ، بعد أن نسي بدء الساعة مرة أخرى ، ذهب لزيارة صديقه ، وقضى المساء في ذلك المكان ، وعندما عاد إلى المنزل ، تمكن من ضبط الوقت بشكل صحيح. كيف تمكن من القيام بذلك إذا كان وقت السفر غير معروف مسبقًا؟ " الجواب هو: "عندما يغادر المنزل ، يبدأ الشخص بالساعة ويتذكر في وضع اليد. عند القدوم إلى صديق ومغادرة الضيوف ، يلاحظ وقت وصوله ومغادرته. هذا يتيح له معرفة مقدار ما كان يزوره. بعد العودة إلى المنزل والنظر إلى الساعة ، يحدد الشخص مدة غيابه. بطرح هذا الوقت من الوقت الذي يقضيه في الزيارة ، يتعلم الشخص الوقت الذي يقضيه في الرحلة ذهابًا وإيابًا. وبعد إضافة نصف الوقت الذي يقضيه في الرحلة إلى وقت مغادرة الضيوف ، فإنه يحصل على فرصة لمعرفة وقت وصوله إلى المنزل وترجمة عقارب الساعة وفقًا لذلك ".
نجد الخادم وقت العمل بناء على الطلب:
- نجد وقت مسار الحزمة من العميل إلى الخادم: ((الوصول - الأصل) - (الإرسال - التلقي)) / 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)
رابط مفيد.