|
研究了一下 NTP 授时服务,MV/MZ 基于 nw.js 可以直接用。
UDP 协议丢包率挺高,真实场景里估计需要一些重试机制。
核心脚本来自 GitHub:https://github.com/luk3skyw4lker/node-ntp-sync
用法:(因为不是新手向,所以没有做 RM 封装,自行取用 response 的值)
- const NTP = window.NtpClient;
- const client = new NTP('ntp.ntsc.ac.cn',
- 123,
- { timeout: 3000 });
- client
- .syncTime()
- .then(response => console.log('TIME:', response))
- .catch(console.log);
复制代码
脚本:
- (function(){
- const udp = require('dgram');
- function toMsecs(buffer, offset) {
- let seconds = 0;
- let fraction = 0;
- for (let i = 0; i < 4; ++i) {
- seconds = seconds * 256 + buffer[offset + i];
- }
- for (let i = 4; i < 8; ++i) {
- fraction = fraction * 256 + buffer[offset + i];
- }
- return seconds + fraction / Math.pow(2, 32);
- }
- const toFrac = ts => {
- return Math.floor(Math.abs(ts - Math.floor(ts)) * Math.pow(2, 32));
- };
- const sysToNTP = timestamp => timestamp + NTPPacket.NTP_DELTA;
- const writeInMillis = (buffer, offset, ts, addDelta) => {
- const seconds = addDelta ? ts + NTP_DELTA : ts;
- const fraction = toFrac(ts);
- // seconds
- buffer[offset + 0] = (seconds & 0xff000000) >> 24;
- buffer[offset + 1] = (seconds & 0x00ff0000) >> 16;
- buffer[offset + 2] = (seconds & 0x0000ff00) >> 8;
- buffer[offset + 3] = seconds & 0x000000ff;
- // fraction
- buffer[offset + 4] = (fraction & 0xff000000) >> 24;
- buffer[offset + 5] = (fraction & 0x00ff0000) >> 16;
- buffer[offset + 6] = (fraction & 0x0000ff00) >> 8;
- buffer[offset + 7] = fraction & 0x000000ff;
- return buffer;
- };
- const MODES = {
- CLIENT: 3,
- SERVER: 4
- };
- const NTP_DELTA = 2208988800;
- class NTPPacket {
- constructor(mode) {
- Object.assign(this, {
- mode: mode || 4,
- leap: 0,
- version: 3,
- stratum: 0,
- poll: 0,
- precision: 0,
- rootDelay: 0,
- rootDispersion: 0,
- referenceId: 0,
- referenceTimestamp: 0,
- originateTimestamp: 0,
- rxTimestamp: 0,
- txTimestamp: 0
- });
- }
- static parse(data) {
- if (data.length < 48) {
- throw new Error('Invalid NTP Package');
- }
- const packet = new NTPPacket(4);
- // Control bytes
- packet.leap = (data[0] >> 6) & 0x3;
- packet.version = (data[0] >> 3) & 0x7;
- packet.mode = data[0] & 0x7;
- packet.stratum = parseInt(data[1]) || 2;
- packet.poll = parseInt(data[2]) || 10;
- packet.precision = parseInt(data[3]);
- packet.rootDelay = data.slice(4, 8).readFloatBE(0) / 2 ** 16;
- packet.rootDispersion = data.slice(8, 12).readFloatBE(0) / 2 ** 16;
- packet.referenceId = data.slice(12, 16);
- // Timestamps where the 4 first bytes are the
- // int part and the 4 last are the frac part
- // const refTimestampHigh = data.slice(16, 20).readUint32BE();
- // const refTimestampLow = data.slice(20, 24).readFloatBE();
- packet.referenceTimestamp = toMsecs(data, 16);
- // const origTimestampHigh = data.slice(24, 28).readUint32BE();
- // const origTimestampLow = data.slice(28, 32).readUint32BE();
- packet.originateTimestamp = toMsecs(data, 24);
- // const rxTimestampHigh = data.slice(32, 36).readUint32BE();
- // const rxTimestampLow = data.slice(36, 40).readUint32BE();
- packet.rxTimestamp = toMsecs(data, 32);
- // const txTimestampHigh = data.slice(40, 44).readUint32BE();
- // const txTimestampLow = data.slice(44, 48).readUint32BE();
- packet.txTimestamp = toMsecs(data, 40);
- return packet;
- }
- bufferize(packet) {
- const buffer = Buffer.alloc(48).fill(0x00);
- buffer[0] = (packet.leap << 6) | (packet.version << 3) | (this.mode << 0);
- buffer[1] = packet.stratum;
- buffer[2] = packet.poll;
- buffer[3] = packet.precision;
- buffer.writeUInt32BE(packet.rootDelay, 4);
- buffer.writeUInt32BE(packet.rootDispersion, 8);
- buffer.writeUInt32BE(packet.referenceId, 12);
- // Reference Timestamp
- writeInMillis(buffer, 16, packet.referenceTimestamp, true);
- // Originate timestamp
- writeInMillis(
- buffer,
- 24,
- packet.originateTimestamp,
- this.mode !== MODES.SERVER // Don't add NTP_DELTA if the packet is server mode
- );
- // RX Timestamp
- writeInMillis(buffer, 32, packet.rxTimestamp, true);
- // TX Timestamp
- writeInMillis(buffer, 40, packet.txTimestamp, true);
- return buffer;
- }
- }
- function createPacket() {
- const packet = new NTPPacket(MODES.CLIENT);
- packet.originateTimestamp = Math.floor(Date.now() / 1000);
- return packet.bufferize(packet);
- }
- function parse(buffer) {
- const message = NTPPacket.parse(buffer);
- message.destinationTimestamp = Math.floor(Date.now() / 1000) + NTP_DELTA;
- message.time = new Date(Math.floor((message.rxTimestamp - NTP_DELTA) * 1000));
- // Timestamp Name ID When Generated
- // ------------------------------------------------------------
- // Originate Timestamp T1 time request sent by client
- // Receive Timestamp T2 time request received by server
- // Transmit Timestamp T3 time reply sent by server
- // Destination Timestamp T4 time reply received by client
- const T1 = message.originateTimestamp;
- const T2 = message.rxTimestamp;
- const T3 = message.txTimestamp;
- const T4 = message.destinationTimestamp;
- // The roundtrip delay d and system clock offset t are defined as:
- // -
- // d = (T4 - T1) - (T3 - T2) t = ((T2 - T1) + (T3 - T4)) / 2
- message.d = T4 - T1 - (T3 - T2);
- message.t = (T2 - T1 + (T3 - T4)) / 2;
- return message;
- }
- class Client {
- constructor(
- server = 'pool.ntp.org',
- port = 123,
- options = { timeout: 3000 }
- ) {
- this.server = server;
- this.port = port;
- this.socket = udp.createSocket('udp4');
- this.options = options;
- return this;
- }
- async syncTime() {
- return new Promise((resolve, reject) => {
- this.socket = udp.createSocket('udp4');
- const {
- server,
- port,
- options: { timeout }
- } = this;
- const packet = createPacket();
- this.socket.send(packet, 0, packet.length, port, server, err => {
- if (err) {
- this.socket.close();
- return reject(err);
- }
- const timer = setTimeout(() => {
- const error = new Error(
- "NTP request timed out, server didn't answered"
- );
- this.socket.close();
- return reject(error);
- }, timeout);
- this.socket.once('message', data => {
- clearTimeout(timer);
- const message = parse(data);
- this.socket.close();
- return resolve(message);
- });
- });
- });
- }
- }
- window.NtpClient = Client;
- })();
复制代码
|
|