Kaixo, Habrausers. Gaur zure NTP bezero sinplea nola idatzi hitz egin nahi dut. Funtsean, elkarrizketa paketearen egitura eta NTP zerbitzariaren erantzuna prozesatzeko metodora joko du. Kodea Python-en idatziko da, iruditzen baitzait ez dagoela hizkuntza hoberik horrelako gauzetarako. Jakintsuek kodearen antzekotasuna nabarituko dute ntplib kodearekin - "inspiratu" nintzen.
Beraz, zer da zehazki NTP? NTP ordu zehatzeko zerbitzariekin elkarreragiteko protokoloa da. Protokolo hau makina moderno askotan erabiltzen da. Adibidez, w32tm zerbitzua windows-en.
NTP protokoloaren 5 bertsio daude guztira. Lehena, 0 bertsioa (1985, RFC958)), gaur egun zaharkituta dago. Orain berriak erabiltzen dira, 1. (1988, RFC1059), 2. (1989, RFC1119), 3. (1992, RFC1305) eta 4. (1996, RFC2030). 1-4 bertsioak elkarren artean bateragarriak dira; zerbitzariaren funtzionamendu-algoritmoetan soilik desberdinak dira.
Pakete formatua
Jauzi adierazlea (zuzenketa adierazlea) - koordinazio-segundoari buruzko abisua adierazten duen zenbakia. Esanahia:
- 0 - ez dago zuzenketarik
- 1 - eguneko azken minutuak 61 segundo ditu
- 2 - eguneko azken minutuak 59 segundo ditu
- 3 - zerbitzariaren matxura (ordua ez dago sinkronizatuta)
Bertsio zenbakia (bertsio-zenbakia) β NTP protokoloaren bertsio-zenbakia (1-4).
modua (modea) β pakete igorlearen funtzionamendu modua. 0tik 7ra arteko balioa, ohikoena:
- 3 - bezeroa
- 4 β zerbitzaria
- 5 - Igorpen modua
Estratua (geruza maila) β zerbitzariaren eta erreferentziako erlojuaren arteko tarteko geruzen kopurua (1 β zerbitzariak zuzenean erreferentziako erlojutik hartzen ditu datuak, 2 β zerbitzariak 1. geruza duen zerbitzari batetik datuak hartzen ditu, etab.).
Igerilekua ondoz ondoko mezuen arteko gehienezko tartea adierazten duen zenbaki oso sinatua da. NTP bezeroak zerbitzaria galdeketa egitea espero duen tartea zehazten du hemen, eta NTP zerbitzariak galdeketa egin nahi duen tartea zehazten du. Balioa segundoko logaritmo bitarraren berdina da.
Zehaztasun (zehaztasuna) sistemako erlojuaren zehaztasuna adierazten duen zenbaki oso sinatua da. Balioa segundoko logaritmo bitarraren berdina da.
Erro atzerapena (zerbitzariaren atzerapena) - erlojuaren irakurketak NTP zerbitzarira iristeko behar duen denbora, segundoko puntu finko gisa.
Erroen sakabanaketa (zerbitzariaren hedapena) - NTP zerbitzariaren erlojuaren irakurketak segundotan hedatzea puntu finko batekin.
Erref id (iturriaren identifikatzailea) β erlojuaren id. Zerbitzariak 1. estratoa badu, ref id erloju atomikoaren izena da (4 ASCII karaktere). Zerbitzariak beste zerbitzari bat erabiltzen badu, erref idak zerbitzari honen helbidea dauka.
Azken 4 eremuek denbora adierazten dute - 32 bit - zati osoa, 32 bit - zati zatikia.
Erreferentzia β zerbitzariko erlojuaren azken irakurketak.
Jatorria β paketea bidali zeneko ordua (zerbitzariak beteta - behean honi buruzko informazio gehiago).
Jaso β zerbitzariak paketea jaso duen ordua.
Transmititu β paketea zerbitzaritik bezeroari bidaltzeko ordua (bezeroak beteta, honetaz gehiago behean).
Ez ditugu azken bi eremuak kontuan hartuko.
Idatz dezagun gure paketea:
Paketearen kodea
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
Zerbitzariari pakete bat bidaltzeko (eta jasotzeko), byte array bihurtzeko gai izan behar dugu.
Eragiketa honetarako (eta alderantzizkoa) bi funtzio idatziko ditugu: pack() eta unpack():
pakete funtzioa
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))
deskonprimitu funtzioa
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
Alferrarentzat, aplikazio gisa - pakete bat kate eder bihurtzen duen kodea
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)
Zerbitzariari pakete bat bidaltzea
Eremu beteak dituen pakete bat zerbitzarira bidali behar da Bertsio, modua ΠΈ Transmititu. Urtean Transmititu tokiko makinan uneko ordua zehaztu behar duzu (1ko urtarrilaren 1900etik segundu kopurua), bertsioa - 1-4 edozein, modua - 3 (bezero modua).
Zerbitzariak, eskaera onartu ondoren, NTP paketearen eremu guztiak betetzen ditu, eremuan kopiatuz. Jatorria baliotik Transmititu, eskaeran etorri zena. Niretzat misterio bat da zergatik bezeroak ezin duen berehala bete eremuan duen denboraren balioa Jatorria. Ondorioz, paketea itzultzen denean, bezeroak 4 denbora balio ditu - eskaera bidali zen ordua (Jatorria), zerbitzariak eskaera jaso duen ordua (Jaso), zerbitzariak erantzuna bidali duen ordua (Transmititu) eta bezeroak erantzuna jaso zuen ordua - iritsiko (ez paketean). Balio hauek erabiliz ordu egokia ezarri dezakegu.
Paketeak bidaltzeko eta jasotzeko kodea
# 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)
Zerbitzaritik datuak prozesatzea
Zerbitzariaren datuak prozesatzea Raymond M. Smullyan-en (1978) arazo zaharreko jaun ingelesaren ekintzen antzekoa da: βGizon batek ez zuen eskumuturreko erlojurik, baina horma-erloju zehatz bat zegoen etxean, batzuetan ahaztu egiten zuena. haizeari. Egun batean, erlojua berriro haizea ematea ahaztuta, bere laguna bisitatzera joan zen, arratsaldea berarekin pasa zuen eta etxera itzuli zenean, erlojua behar bezala jartzea lortu zuen. Nola lortu zuen hori egitea bidaia-denbora aldez aurretik ezagutzen ez bazen? Erantzuna hauxe da: βEtxetik irtetean, pertsona batek bere erlojua haizea ematen du eta eskuak zein posiziotan dauden gogoratzen du. Lagun batengana etorri eta gonbidatuak utzita, iritsi eta irteteko ordua jasotzen du. Horri esker, zenbat denbora egon zen bisitan jakiteko. Etxera itzuli eta erlojuari begira, pertsona batek bere absentziaren iraupena zehazten du. Denbora horri bisitan eman zuen denbora kenduz gero, pertsona batek hara eta itzulera bidaiatzen emandako denbora jakiten du. Gonbidatuak irteteko denborari errepidean emandako denboraren erdia gehituz, etxera iristeko ordua jakiteko eta bere erlojuaren orratzak egokitzeko aukera duΒ».
Aurkitu zerbitzariak eskaera batean lan egiten duen ordua:
- Aurkitu paketearen bidaia-denbora bezerotik zerbitzarira: ((Iritsi β Sortu) β (Igorri β Jaso)) / 2
- Aurkitu bezeroaren eta zerbitzariaren denboraren arteko aldea:
Jaso - Sortu - ((Iritsi - Sortu) - (Igorri - Jaso)) / 2 =
2 * Jaso - 2 * Sortu - Iritsi + Sortu + Igorri - Jaso =
Jaso β Sortu β Iritsi + Igorri
Bertako orduari ematen diogun balioa gehitzen diogu eta bizitzaz gozatzen dugu.
Emaitzaren irteera
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)
Baliagarria
Iturria: www.habr.com