akerdi / WorkFace

面试经历
0 stars 0 forks source link

A tele interview #7

Open akerdi opened 1 year ago

akerdi commented 1 year ago

实现一个伪NTP服务器类,提供startNTPServer方法,stopNTPServer方法,getTimeFromNTPServer方法。其中startNTPServer使用UDP4 socket,对外提供时间信息,信息需要以二进制字节的方式发送,第1字节写入uint8型数字作为版本号,第2字节到第9字节(共8个字节)写入当前时间戳毫秒数。stopNTPServer停止UDP4 socket的服务。getTimeFromNTPServer连接startNTPServer启动的UDP4 socket,获取版本信息和时间戳信息,并打印出来。

akerdi commented 1 year ago
// server/index.js
const udp = require('dgram');
const util = require('util')

const LogLevel = {
  // ... more level
  Info: 4,
  Error: 3
}
const version = 1;

var logicInstance = null

function LogLog(level, msg) {
  switch (level) {
    case LogLevel.Info: {
      console.log(`serv::${msg}`)
    }
      break;
    case LogLevel.Error: {
      console.error(`serv::${msg}`)
    }
      break;
    default: {
      throw new Error("Unbound Log Level! Got level: " + level)
    }
  }
}
function LogInfo(msg) {
  LogLog(LogLevel.Info, msg)
}
function LogErr(msg) {
  LogLog(LogLevel.Error, msg)
}

class CliInfo {
  host = null
  port = -1
  constructor(host="localhost", port=65533) {
    this.host = host
    this.port = port
  }
}

class UDPManager {
  server = null // socket df
  sendMessage(msg, info) {
    if (info.port < 0) throw new Error(`port is invalid: Expect port > 0, Got port ${info.port}`)
    const that = this
    return new Promise((resolve, reject) => {
      that.server.send(msg, info.port, info.host, function(error) {
        if (error) {
          reject(err)
        } else {
          resolve(true)
        }
      })
    })
  }
  connectServ(cb) {
    const that = this
    this.server.on('error',function(error){
      LogErr('Error: ' + error);
      that.server.close();
    });

    // emits on new datagram msg
    this.server.on('message',function(msg,info) {
      const str = util.format('Received %d bytes from %s:%d\n',msg.length, info.address, info.port)
      LogInfo(str);
      cb && cb(msg, info)
    });
    this.server.on('listening', function() {
      var address = that.server.address()
      var port = address.port
      var family = address.family
      var ipaddr = address.address
      LogInfo("Server is listening at port" + port)
      LogInfo("Server ip :" + ipaddr)
      LogInfo("Server is IP4/IP6 : " + family)
    });
    this.server.on('close',function(){
      LogInfo("Socket is closed !")
    })
  }
  createServ() {
    this.server = udp.createSocket('udp4')
  }
  bindServ(port) {
    if (!port) throw new Error("Param port is required")
    this.server.bind(port)
  }
  closeServ() {
    this.server.close()
  }
  static instance
  static get sharedInstance() {
    if (!UDPManager.instance) {
      UDPManager.instance = new UDPManager
    }
    return UDPManager.instance
  }
}

class Logic {
  // demostrate getTimeFromNTPServer
  routeTrigger(msg, cli) {
    LogInfo("routeTrigger::start...")
    const demoHost = cli.address
    const demoport = cli.port
    const cli_info = new CliInfo(demoHost, demoport)
    this.startNTPServer(cli_info)
  }
  // info -> CliInfo
  startNTPServer(info) {
    LogInfo("startNTPServer::start...")
    const date_millsec = Date.now()
    LogInfo("--------", date_millsec)
    const msgDataView = this.responseStruct(date_millsec)
    UDPManager.sharedInstance.sendMessage(msgDataView, info)
      .then(() => {
        LogInfo("Msg send")
      })
      .catch(e => {
        LogErr("startNTPServer::" + e.message || e)
      })
  }
  stopNTPServe() {
    UDPManager.sharedInstance.closeServ()
    // clear UDPManager memory
  }
  responseStruct(dateStr) {
    LogInfo("responseStruct::start...")
    const dateStr_split_two_part_array = this.dateStrSplitApart(String(dateStr))
    LogInfo("responseStruct::dateStr_split_two_part_array: " + dateStr_split_two_part_array)
    const bufferArr = new ArrayBuffer(1+4+4);
    const view = new DataView(bufferArr)
    view.setUint8(0, version, false)
    view.setUint32(1, dateStr_split_two_part_array[0], false)
    view.setUint32(5, dateStr_split_two_part_array[1], false)
    return Buffer.from(view.buffer)
  }
  dateStrSplitApart(dateStr) {
    const first_part_length = Math.floor((dateStr.length+1) / 2)
    const firstComponent = dateStr.substr(0, first_part_length)
    const secondComponent = dateStr.substr(first_part_length, dateStr.length-first_part_length)
    return [firstComponent, secondComponent]
  }
  testBuffer(buffer) {
    LogInfo("------------------" + buffer.length)
    const arrayBuffer = new Uint8Array(buffer.length)
    for (let i = 0; i < buffer.length; i++) {
      arrayBuffer[i] = buffer[i];
    }
    const view = new DataView(arrayBuffer.buffer, 0)
    const version_number = view.getUint8(0, false)
    const str1 = view.getUint32(1, false)
    const str2 = view.getUint32(5, false)
    LogInfo("testBuffer:: version: " + version_number + " str1: " + str1 + "||||||" + str2)
  }
}

function main() {
  logicInstance = new Logic
  const port = 2222
  UDPManager.sharedInstance.createServ();
  UDPManager.sharedInstance.connectServ((msg, addr_info) => {
    logicInstance.routeTrigger(msg, addr_info)
  });
  UDPManager.sharedInstance.bindServ(port);
}

main()
akerdi commented 1 year ago
// client/index.js
var udp = require('dgram')
var client = udp.createSocket('udp4')

function toArrayBuffer(buffer) {
  const arrayBuffer = new Uint8Array(buffer.length)
  for (let i = 0; i < buffer.length; ++i) {
    arrayBuffer[i] = buffer[i]
  }
  return arrayBuffer
}

client.on('message', function(netBuffer, info) {
  // littleendian
  const arrayBuffer = toArrayBuffer(netBuffer)
  const view = new DataView(arrayBuffer.buffer, 0)
  const version_number = view.getUint8(0, false)
  const str1 = view.getUint32(1, false)
  const str2 = view.getUint32(5, false)
  console.log("-----:: version: " + version_number + " str1: " + str1+str2)
})

var data = Buffer.from('');
//sending multiple msg
client.send([data], 2222, 'localhost', function(error) {
  if (error) {
    client.close()
  } else {
    console.log('Data sent !!!')
  }
});
akerdi commented 1 year ago
image