在Node.JS和TypeScript中实现UDP Command-Acknowledge通信协议

时间:2015-11-07 18:41:24

标签: node.js udp typescript

我尝试通过Node.JS和TypeScript中的UDP数据报实现自定义通信协议。在这个协议中,我有一些命令必须以特定的顺序发送到微控制器,每个命令必须等待前一个的ACK从微控制器发送之前。但是,鉴于异步和"以套接字为中心" Node.JS的哲学' dgram模块,我很难找到正确的方法来实现它。

截至目前,我创建了abstract class ProtocolCommand和各种具体的孩子(StartFirmwareUpgradeCommandWriteCommandEndFirmwareUpgradeCommand)。所有类都由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来使用承诺,并尽可能地避免回调,但我真的很难找到一种合适的方法来编写所有内容。任何帮助都会非常感激。

2 个答案:

答案 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。 (不要担心那里的所有角色事物)

我希望这会有所帮助。