Простой сервер чата на Node.JS

Здравствуйте! В продолжении темы Node.JS хочу рассмотреть пример создания простого сервера чата на базе данной библиотеки.

Во-первых нужно определится с функционалом. Наш сервер должен принимать подключения от клиентов по протоколу tcp, открывать потоки и рассылать всем клиентам полученные от каждого данные. Т.е. при получении данных от одного из клиентов нужно записать их в каждый из поток остальных клиентов.

Для того что-бы реализовать сервер в ноде нам понадобится стандартный модуль net. Подключим его добавив строку var net = require('net'); в начало кода программы. Теперь мы можем обращаться ко всему функционалу модуля через переменную net. В данном случае мы будем использовать метод createServer который создает tcp сервер. В качестве параметров передается функция, которая будет обрабатывать подключения к серверу. При вызове в эту функцию передается один параметр - поток net.Stream, созданного соединения. Данный поток является потоком ввода/вывода и будет использоваться для непосредственного обмена данными с клиентом. Итак, создаем сервер:

var server = net.createServer(function (c) {
    //Обрабатываем новое соединение
});

Сервер мы создали, но соединения он принимать не будет, т.к. мы не назначили его на определённый ip:port или сокет. Для этого необходимо воспользоваться методом server.listen(port, [host], [callback]), если хотим назначить сервер "слушать" порт или server.listen(path, [callback]) для сокета. Назначим сервер "слушать" 10.32.3.23:8124:

server.listen(8124, '10.32.3.23');

Дальше для корректной работы нашего сервера необходимо описать обработчики некоторых событий. Т.к. net.Server и net.Stream являются EventEmitter, то они могут вызывать события. Рассмотрим нужные нам события сервера:

'connection' - function (stream) {} - вызывается при подключении нового клиента. Мы в данном примере будем использовать следующий обработчик:

function (c) {
    c.setEncoding('utf8'); //Устанавливаем кодировку
    streams.push(c);      //Добавляем поток в массив
    streams.forEach(function(stream){ //Обходим все сохраненные потоки
        stream.write(c.remoteAddress + ' - is connected\n', 'utf8'); //Выводим сообщение о новом клиенте
    });
    c.on('data', onStreamData); //Добавляем обработчик события 'data' нового потока
}

net.Stream.remote Address - ip адрес клиента подключенного к данному потоку.

'close' - function () {} - вызывается при закрытии сервера. Наш обработчик:

function(){
    streams.forEach(function(stream){ //Обходим все сохраненные потоки
        stream.write('Server is going down! Bye-bye!','utf8'); //Выводим сообщение
        stream.destroy(); //Уничтожаем поток
    });
}

Хочу обратить внимание, что в данном участке кода нет непосредственного завершения процесса, но он приводит к этому. Связанно это с тем что программы на node работают пока есть активные обработчики, а после выполнения этого кода все обработчики назначенные потокам будут отключены и это приведет к завершению процесса.

Теперь рассмотрим события потока:

'data' - function (data) {} - вызывается при получении данных от клиента. data - буфер, но если до этого вызван метод setEncoding, то data - строка в заданной кодировке. Рассмотрим наш обработчик:

function onStreamData(data){
    // Обработка команды завершения работы сервера
    if (0 === data.indexOf('.down')){
        server.close(); // Приводит к тому, что сервер больше не принимает соединения, а также вызывает событие 'close'
        return ;
    }
    // Заносим в переменную текущий поток
    var current_stream = this;
    streams.forEach(function(stream){
        if (stream != current_stream) //Если поток не является текущим потоком, то выводим полученные данные
            stream.write(current_stream.remoteAddress + ':' + data, 'utf8');
    });
}

Теперь полный код программы:

var net = require('net');
var streams = Array();

function onStreamData(data){
    if (0 === data.indexOf('.down')){
        server.close();
        return ;
    }
    var current_stream = this;
    streams.forEach(function(stream){
        if (stream != current_stream)
            stream.write(current_stream.remoteAddress + ':' + data, 'utf8');
    });
}

var server = net.createServer(function (c) {
    c.setEncoding('utf8');
    streams.push(c);
    streams.forEach(function(stream){
        stream.write(c.remoteAddress + ' - is connected\n', 'utf8');
    });
    c.on('data', onStreamData);
});

server.on('close', function(){
    streams.forEach(function(stream){
        stream.write('Server is going down! Bye-bye!','utf8');
        stream.destroy();
    });
});

server.maxConnections = 2; // Максимальное кол-во подключений
server.listen(8124, '10.32.3.23'); // Задайте свой внешний ip или localhost

Сохраните код в файле, например server.js. Теперь можно запустить сервер и проверить его в действии. Для запуска выполните:

node server.js

Для того что-бы подключится воспользуемся telnet:

telnet 10.32.3.23 8124

Не забудьте поменять ip и порт на тот, который вы использовали при вызове метода listen.

При желании можете расширить пример, дописать назначение псевдонимов клиентам или добавить специальные команды(такие как .down) для бана или выкидывания пользователей из чата.

Как вы смогли убедится Node.JS предоставляет весь необходимый функционал для создания сетевых приложений. В скором времени постараюсь продолжить тему разработки приложений на Node.JS новыми и интересными примерами. Спасибо за внимание :)