「ステートマシン」のスタイルでNode.jsのコードを記述する

Node.jsで、着信TCP接続を受け入れ、非標準プロトコルを使用してクライアントとの重要なダイアログを実行するサーバーを作成しますか? 私のプロジェクトで私が開発している例に興味があるかもしれません。 自明ではない対話とはどういう意味ですか?



比較してみましょう。



サーバーをノックし、接続を確立した後、識別子の数バイトをソケットに書き込み、次に現在の温度の数バイトを書き込むリモート温度センサー-これはプリミティブです。



サーバーからコマンドを受信して​​、測定頻度または測定値の平均化期間を変更できる同じセンサーは、すでにより複雑です。 これに加えて、たとえば、ファームウェアの更新をセンサーに送信する機能とコードは、その簡潔さを簡単に失う可能性があります。



そのような場合、オートマトンの理論の規範に準拠するふりをせずに、「ステートマシン」のスタイルでコードを整理します。



この記事の例は、 github.com / kityan / fsmConnectionから入手できます。 次に、いくつかの重要なポイントについて説明します。



メインアプリケーションのコード。



サーバー側のみを考慮してください。 コードは非常に簡単です。



var net = require('net'); var ClientConnection = require('./ClientConnection.js'); var config = {"socketTimeout":3000, "port": 30000} net.createServer(function(socket) {var clientConnection = new ClientConnection(socket, config);}) .listen(config.port, function () {console.log('Listening on: ' + config.port);});
      
      





接続を確立するたびに、サーバーはClientConnectionのインスタンスを作成し、ソケットと構成オブジェクトを渡します。



ClientConnectionモジュールのコードスニペット。



インスタンスフィールドを初期化します。



 var ClientConnection = function (socket, config){...}
      
      





プロトタイプでは、マシンを切り替えるClientConnection.toメソッドを定義します。



 ClientConnection.prototype.to = function (newState) { //  onExitHandler? if (this.currentState && this.states[this.currentState].onExitHandler && typeof this.states[this.currentState].onExitHandler == 'function') { this.states[this.currentState].onExitHandler.call(this); } var prevState = this.currentState; this.currentState = newState; //  inputHandler? if (this.currentState && this.states[this.currentState].inputHandler && typeof this.states[this.currentState].inputHandler == 'function') { this.handleInput = this.states[this.currentState].inputHandler.bind(this); } else { this.handleInput = this.noInputHandler } //  onEnterHandler? if (this.states[this.currentState].onEnterHandler && typeof this.states[this.currentState].onEnterHandler == 'function') { this.states[this.currentState].onEnterHandler.call(this, prevState); } return this; }
      
      





切り替えるときに、 onExitHandlerメソッドに以前の状態があるかどうかを確認し、ある場合はそれを呼び出します。

次に、マシンのhandleInputメソッドに、新しい状態のinputHandlerへのポインターを割り当てます。 最後に、新しい状態にonEnterHandlerメソッドがあるかどうかを確認します。 ある場合、それを呼び出します。



ClientConnection.to(newState)を呼び出した後はどうなりますか? onExitHandleronEnterHandlerの呼び出しが別の状態に切り替わらない場合、マシンはこの状態のままになります。 そして、すべてがソケットデータに依存します。 到着するすべてのパケットはhandleInputにルーティングされます 。 なんで?



実際には、インスタンスを作成すると、すぐに初期化状態に切り替わり、ソケットイベントのハンドラーがハングアップします。



 ClientConnection.prototype.states = { 'inital': { 'onEnterHandler': function(){ // socket events this.socket.on('timeout', function() {this.to('socket-timeout');}.bind(this)); this.socket.on('end', function() {this.to("socket-end");}.bind(this)); this.socket.on('error', function (exc) {this.to("socket-error").handleInput(exc);}.bind(this)); this.socket.on('close', function () {this.to("socket-close");}.bind(this)); this.socket.on('data', function (data) {this.handleInput(data);}.bind(this)); this.to("waitingForHelloFromClient"); } }, ... }
      
      







そして、次の状態に切り替えます。 私たちの場合、これは'waitingForHelloFromClient'です。



すべての状態は、 ClientConnection.prototype.statesオブジェクトで説明されてます。 有効な状態にはinputHandlerがありません。 そのような状態に切り替えるとき、 onEnterHandler内で何らかのアルゴリズムを実行し 、すぐに別の状態に切り替えます。 inputHandlerで停止して、イベントループの次の反復でコードが呼び出され、ソケットデータが表示された場合に処理できるようにします。 onExitHandlerに切り替えないことを強くお勧めします。



実際にはすべて。 コードが便利だと思われる場合-健康に使用してください。 批判は大歓迎です。



一般的なケースではより便利であることが判明する解決策(たとえば、 Machina.JS )があることに注意してください。



All Articles