Навиштани муштарии оддии NTP

Салом, Ҳабраузерҳо. Имрӯз ман мехоҳам дар бораи чӣ гуна навиштани муштарии оддии NTP худ сӯҳбат кунам. Асосан, сӯҳбат ба сохтори баста ва усули коркарди посух аз сервери NTP мегузарад. Рамз бо Python навишта мешавад, зеро ба назарам чунин менамояд, ки забони беҳтаре барои чунин чизҳо вуҷуд надорад. Донишҷӯён шабоҳати кодро бо рамзи 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 ҳамчун шумораи сонияҳо бо нуқтаи собит.
Рақам ID (идентификатори манбаъ) - ID соат. Агар сервер дорои қабати 1 бошад, ref id номи соати атомӣ мебошад (4 аломати ASCII). Агар сервер сервери дигарро истифода барад, он гоҳ 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))

функсияи кушодан

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)

Ирсоли баста ба сервер

Бастаи дорои майдонҳои пуршуда бояд ба сервер фиристода шавад Version, муд и додан. Дар додан шумо бояд соати чориро дар мошини маҳаллӣ (шумораи сонияҳо аз 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)

Муфид Садо Ояндасоз.

Манбаъ: will.com

Илова Эзоҳ