我尝试通过Node.JS和TypeScript中的UDP数据报实现自定义通信协议。在这个协议中,我有一些命令必须以特定的顺序发送到微控制器,每个命令必须等待前一个的ACK从微控制器发送之前。但是,鉴于异步和"以套接字为中心" Node.JS的哲学' dgram
模块,我很难找到正确的方法来实现它。
截至目前,我创建了abstract class ProtocolCommand
和各种具体的孩子(StartFirmwareUpgradeCommand
,WriteCommand
,EndFirmwareUpgradeCommand
)。所有类都由Protocol
类使用,该类应该协调要执行的所有命令。我将下面的摘要和一个示例类附加在一起。此外,命令的数量是可变的(更具体地说,在StartFirmwareUpgrade之后,我有一个可变数量的Write命令,我将固件字节发送到微控制器)。
ProtocolCommand:
import q = require('q');
export abstract class ProtocolCommand {
protected socket:dgram.Socket;
protected ip:string;
protected port:number;
protected deferred;
constructor(socket:dgram.Socket, ip:string, port:number, deferred) {
this.socket = socket;
this.ip = ip;
this.port = port;
this.deferred = deferred;
}
protected callback(data, sender) {
this.socket.removeListener('message', this.callback);
this.deferred.resolve(data);
}
abstract executeCommand():void;
}
StartFirmwareUpgradeCommand:
import dgram = require('dgram');
import {ProtocolCommand} from "./ProtocolCommand";
import CRC = require('./CRC');
import q = require('q');
export class StartFirmwareUpgradeCommand extends ProtocolCommand {
private header = [0x00, 0x0C, 0x00, 0x19, 0x00, 0x00];
private data = [0x31, 0x32, 0x33, 0x34, 0x35, 0x36];
constructor(socket:dgram.Socket, ip:string, port:number, deferred) {
super(socket, ip, port, deferred);
}
executeCommand() {
let commandBytes = this.header.concat(this.data);
let crcBytes = CRC.CRC16(commandBytes);
commandBytes = commandBytes.concat(Math.floor(crcBytes / 0x100), crcBytes % 0x100);
this.socket.on('message', (data, sender) => {
this.callback(data, sender);
});
this.socket.send(new Buffer(commandBytes), 0, commandBytes.length, this.port, this.ip);
return this.deferred.promise;
}
}
协议:
import dgram = require('dgram');
import {StartFirmwareUpgradeCommand} from "./StartFirmwareUpgradeCommand";
import {EndFirmwareUpgradeCommand} from "./EndFirmwareUpgradeCommand";
import {DiscoveryCommand} from "./DiscoveryCommand";
import q = require('q');
export class Protocol {
private socket;
private ip:string;
private port:number;
constructor(ip:string, port:number) {
this.ip = ip;
this.port = port;
this.socket = dgram.createSocket('udp4');
this.socket.bind();
}
upgradeFirmware(data:Uint8Array) {
let globalDeferred = q.defer();
//FIXME UGLY AS HELL!
new StartFirmwareUpgradeCommand(this.socket, this.ip, this.port, globalDeferred).executeCommand()
.then((data) => {
})
.then((data) => {
});
//TODO send n*write firmware command, wait for every ack
for (let i = 0; i < data.length / 128; i++) {
}
//new EndFirmwareUpgradeCommand(this.socket, this.ip, this.port).executeCommand();
//TODO send end firmware command, wait for ack
}
}
正如您所看到的,我目前正在使用q
来使用承诺,并尽可能地避免回调,但我真的很难找到一种合适的方法来编写所有内容。任何帮助都会非常感激。
答案 0 :(得分:2)
根据@ gilamran的建议,这是一个RxJS实现。它只会在发送下一个请求之前等待收到响应。它不处理错误要求您刷新队列的情况。
import dgram = require('dgram');
import Rx = require('rx'); // or use rx.lite if you need something smaller.
let commandQueue = Rx.Subject();
let socket = dgram.createSocket('udp4');
let ip = '127.0.0.1';
let port = '10000';
// let `req` be an object with { ip, port, header, data }.
// sendCommand :: Request -> Observable of Responses
function sendCommand (req) {
// return this Observable so we can use Rx.Observable.concat later to
// block while waiting for a response.
return Rx.Observable.create(obs => {
let commandBytes = req.header.concat(req.data);
const crcBytes = CRC.CRC16(commandBytes);
commandBytes = commandBytes.concat(Math.floor(crcBytes / 0x100), crcBytes % 0x100);
this.socket.on('message', (data, sender) => {
// pass this information on for further processing?
obs.onNext({
data,
sender,
});
obs.onCompleted(); // close this observable so `.concat` switches to next request.
});
socket.send(new Buffer(commandBytes), 0, commandBytes.length, req.port, req.ip)
});
}
// ok. let's setup the downstream side of our queue.
// 1. take a request, send a packet, return an observable of one response.
// 2. wait for the observable of one response to complete.
let responses = commandQueue
.concatMap(sendCommand); // takes command requests and turns them into data/sender responses.
// we must subscribe to pull values through our observable chain.
responses.subscribe();
let cmdStartFirmwareUpgrade = (ip, port) => {
return {
ip,
port,
header = [0x00, 0x0C, 0x00, 0x19, 0x00, 0x00],
data = [0x31, 0x32, 0x33, 0x34, 0x35, 0x36],
};
};
let cmdDiscovery = (ip, port) => { /* ... */ };
let cmdEndFirmwareUpgrade = (ip, port) => { /* ... */ };
// now let's put some commands in the queue.
commandQueue.onNext(cmdStartFirmwareUpgrade(ip, port));
commandQueue.onNext(cmdDiscovery(ip, port));
commandQueue.onNext(cmdEndFirmwareUpgrade(ip, port));
这个例子当然可能是DRY'er,但是我更喜欢在课程中使用Ramda curried函数,所以我把那一点留了下来。
您可以在https://runkit.com/boxofrox/rxjs-queue找到此设计的顺序性演示。
答案 1 :(得分:1)
在这里使用q
对您不利,这就是原因:
q
旨在帮助您的图书馆用户以异步方式使用您的图书馆。是否更像是一个很好的&#34;让用户知道什么时候完成的方法。然后做一些他们想做的事情(在他们的代码中)。
但是在制定协议时,你不想做任何事情,然后到处都是,特别是如果你不知道接下来会发生什么。
用什么?
我在这里推荐rxjs
,它使用了observables,它可以更加直观地实现每个命令。从所有命令创建队列并以(异步)循环方式遍历所有命令也很容易。
你可以看几分钟here。 (不要担心那里的所有角色事物)
我希望这会有所帮助。