Вы пишите сервер на 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)? Если в вызовах onExitHandler и onEnterHandler не произошло переключения в другое состояние, машина остаётся в этом. И далее уже всё зависит от данных сокета. Все прилетающие пакеты будут направляться в 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, чтобы следующая итерация Event Loop могла вызывать код для обработки данных сокета, если они появятся. Категорически не рекомендуется делать переключения в onExitHandler.
Собственно всё. Если код покажется удобным — применяйте на здоровье. Критика приветствуется.
Хочу отметить, что есть решения (например, Machina.JS), которые в общем случае могут оказаться более удобными.
ссылка на оригинал статьи http://habrahabr.ru/post/272983/
Добавить комментарий