const protobuf = require('protobufjs'); const ws = require('nodejs-websocket'); class ClientNet { constructor(url, protoPbFile, msgIdPbFile) { this.url = url; this.conn = null; this.protoPbFile = protoPbFile; this.msgIdPbFile = msgIdPbFile; this.protoPb = null; this.msgIdPb = null; this.cmMsgId = null; this.smMsgId = null; this.recvBuf = Buffer.alloc(0); this.uniqId = 1000; this.msgHandlerMap = new Map(); } async init() { { const {err, root} = await protobuf.load(this.protoPbFile); this.protoPb = root; } { const {err, root} = await protobuf.load(this.msgIdPbFile); this.msgIdPb = root; this.cmMsgId = this.msgIdPb.lookup('CMMessageId_e'); this.smMsgId = this.msgIdPb.lookup('SMMessageId_e'); } } async connect() { this.conn = await ws.connect(this.url); this.on('binary', this.#onReceive.bind(this)); } on(eventName, ...args) { this.conn.on(eventName, ...args); } async #onReceive(inStream) { inStream.on('readable', async () => { //console.log('inStream.readable'); const newData = inStream.read(); if (newData) { this.recvBuf = Buffer.concat([this.recvBuf, newData]); await this.#onParsePacket(); } }); inStream.on('end', () => { //console.log('inStream.end', this.recvBuf.length); }); inStream.on('close', () => { //console.log('inStream.close'); }); } async #onParsePacket() { while (this.recvBuf.length > 12) { const msgSize = this.recvBuf.readUInt16LE(0); const msgId = this.recvBuf.readUInt16LE(2); const seqId = this.recvBuf.readUInt32LE(4); const magicCode = this.recvBuf.readUInt16LE(8); const ext = this.recvBuf.readUInt16LE(10); if (this.recvBuf.length >= 12 + msgSize) { await this.#processMsg(msgId, this.recvBuf.slice(12, 12 + msgSize)); this.recvBuf = this.recvBuf.slice(12 + msgSize); } } } async #processMsg(msgId, buff) { const handlers = this.msgHandlerMap.get(msgId); if (handlers) { let msg = null; handlers.forEach((value, key) => { if (!msg) { msg = value.msgType.decode(buff); } console.log(msg, msg.accountInfo.userInfo.baseData.accountId); value.cb(msg); }); } } registerMsgHandle(msgName, cb) { const msgType = this.protoPb.lookupType(msgName); const msgId = this.smMsgId.values['_' + msgName]; if (!msgId) { return null; } const handle = { msgId: msgId, msgName: msgName, msgType: msgType, cb: cb, uniqId: ++this.uniqId} ; if (!this.msgHandlerMap.has(msgId)) { this.msgHandlerMap.set(msgId, new Map([ [handle.uniqId, handle] ])); } else { this.msgHandlerMap[msgId].set(handle.uniqId, handle); } return handle; } unRegisterMsgHandle(handle) { if (this.msgHandlerMap.has(handle.msgId)) { if (this.msgHandlerMap[handle.msgId].has(handle.uniqId)) { this.msgHandlerMap[handle.msgId].delete(handle.uniqId); } } } async sendMsg(name, msg) { const msgType = this.protoPb.lookupType(name); const msgId = this.cmMsgId.values['_' + name]; const msgPb = msgType.create(msg); const msgBuf = msgType.encode(msg).finish(); let buf = Buffer.alloc(12); buf.writeInt16LE(msgBuf.length, 0); buf.writeInt16LE(msgId, 2); buf.writeInt32LE(0, 4); buf.writeUInt8('K'.charCodeAt(0), 8); buf.writeUInt8('S'.charCodeAt(0), 9); buf.writeInt16LE(0, 10); this.conn.sendBinary(Buffer.concat([buf, msgBuf])); console.log(name, msg); } } module.exports = ClientNet;