Разработка простого чата на Socket.IO [2016] \ Node.js

Всем привет, дорогие хабрахабровцы! Недавно я начал изучать node.js и дошёл до самого интересного, а именно — Socket.Io. Поизучав информацию в интернете, я так и не смог найти подробного «гайда» по данному модулю, поэтому пришлось копать самому. Некоторые скажут, что можно и самому понять, что написано на сайте модуля, но некоторым этого будет не достаточно чтобы понять базу web-socket'ов, поэтому я решил написать эту статью для таких людей, а именно на самом 'чётком' примере — чате.

Установка модуля


Итак, давайте приступать?! Для начала нам нужно собственно установить наш Socket.Io, как его установить можете прочитать здесь, а для тех, кто не понял, поясню: есть клиент и сервер, на сервер нужно поставить сам модуль через npm в папку проекта (для этого надо заранее открыть её в консоли, а дальше устанавливать):

npm install socket.io --save

После того как установили модуль, в папке с проектом (куда ставили) появится папка «node_modules»

Теперь мы должны установить сам .js файл модуля в папку с проектом (.js файл будет использоваться на клиенте), ставить отсюда => *тык*. Вы скачаете архив, и оттуда вы должны достать файл «socket.io.js» и перекинуть в папку с проектом.

Для тех кто не понял, структура папки проекта с чатом должна быть такова на данный момент:

chat: (Сама папка)
|node_modules
|--socket.io (Внутри <i>node_modules</i>)
|socket.io.js

После того как вы скачали .js файл и установили модуль — можно приступать к следующему этапу.

Установка дополнительных модулей


Вам понадобятся также такие модули как:

1. express # Для создания сервера
2. log4js # Отличный логгер
3. http # Сам http-сервер

express:

npm install express --save

log4js:

npm install log4js

http:

npm install http

После всех этих процедур, папка «node_modules» должна пополниться (*логично вроде*). Теперь можно приступать к разработке нашего чата!

Разработка чата


Теперь мы можем приступать у разработке, но перед этим, надо создать .js-файл в директории проекта «chat.js» в чем мы и будем писать наш чат. Для начала открываем наш chat.js и подключаем все модули таким образом:

var express = require('express'); // Подключаем express
var app = express();
var server = require('http').Server(app); // Подключаем http через app
var io = require('socket.io')(server); // Подключаем socket.io и указываем на сервер
var log4js = require('log4js'); // Подключаем наш логгер
var logger = log4js.getLogger(); // Подключаем с модуля log4js сам логгер

Это все что нам нужно подключить в начале. Дальше создаем переменную куда укажем наш порт для прослушивания (для сервера):

var port = 3000; // Можно любой другой порт

После этого можно прологгировать старт скрипта таким образом:

logger.debug('Script has been started...'); // Логгируем.

Дальше ставим на «прослушку» для сервера порт таким образом:

server.listen(port); // Теперь мы можем подключиться к нашему серверу через localhost:3000 при запущенном скрипте

Теперь мы должны сделать так, чтобы клиенту который подключается к нашему серверу «localhost:3000», получал пакет файлов таких как index.html; main.css; socket.io; main.js; чтобы отображалась сама страница с нашим чатом.

Это делается просто, создаем папку «public» в корне проекта, и закидываем туда наш «socket.io.js», а дальше создаем там такие файлы как index.html, main.js, main.css(Не будем пользоваться, это для наглядности). Наша структура проекта должна быть примерно такая:

chat:
|node_modules:
|-socket.io
|-express
|-log4js
|-http
|public:
|-index.html
|-socket.io.js
|-<i>main.css</i>
|-main.js
|chat.js

Дальше делаем такой «финт» который будет отправлять клиенту содержимое этой папки при подключении:

chat.js:

app.use(express.static(__dirname + '/public')); // Отправляет "статические" файлы из папки public при коннекте // __dirname - путь по которому лежит chat.js

Отлично, теперь при подключении на localhost:3000 (при запущенном скрипте) у нас будет открываться «index.html», только страница будет пустая из-за того что у нас «index.html» пустой :)

Накидаем эскиз чата в «index.html» (без стилей). Можете просто копировать код в «index.html»

<html>
  <head>
    <meta charset="utf-8">
    <title>chat</title>
    <script src="socket.io.js"></script>
    <script src="https://code.jquery.com/jquery-3.1.0.min.js" charset="utf-8"></script>
    <script src="main.js" charset="utf-8"></script>
    <!-- Здесь можете подключить ваш main.css -->
  </head>
  <body>
    <textarea name="name" rows="8" cols="40"></textarea>
    <p></p>
    <input type="text" name="text" size="20">
    <button type="button" name="button">Отправить</button>
  </body>
</html>

Прекрасно! Теперь если мы запустим скрипт и откроем localhost:3000, мы увидим наш *кривой* эскиз ) Также вы можете увидеть, что я подключил jQuery, благодаря ему мы будем извлекать текст из поля «удобно».

Как мы можем понять, сейчас наш чат не работает, по сколько не настроены «евенты» в скрипте. Сейчас мы будем всё разбирать. Для начала мы должны подключиться с клиента к серверу (socket.io) в «main.js»

var port = 3000; // Указываем порт на котором у на стоит сокет
var socket = io.connect('http://localhost:' + port); // Тут мы объявляем "socket" (дальше мы будем с ним работать) и подключаемся сразу к серверу через порт

Теперь когда мы написали подключение socket.io к серверу через порт, самое время обработать это событие на сервере.

io.connect(port) — Создает событие 'connection', нам надо сделать его обработчик на сервере, иначе подключение будет *пустое*, а нам надо еще присвоить никнейм тому кто приконнектился, а это делается в обработчике, просто читаем дальше.

Создаём обработчик в скрипте «chat.js»

io.on('connection', function (socket) { // Создаем обработчик события 'connection' которое создает io.connect(port); с аргументом socket
  var name = 'U' + (socket.id).toString().substr(1,4); // Создаем никнейм нашему клиенту. В начале буква 'U' дальше берем 3 символа ID (сокета) после первого символа, и все это клеим с помощью '+'
  socket.broadcast.emit('newUser', name); // Отсылает событие 'newUser' всем подключенным, кроме текущего. На клиенте навешаем обработчик на 'newUser' (Отправляет клиентам событие о подключении нового юзера)
  socket.emit('userName', name); // Отправляем текущему клиенту событие 'userName' с его ником (name) (Отправляем клиенту его юзернейм)
  logger.info(name + ' connected to chat!'); // Логгирование
});

Итак, для тех, кто не понял… io.on('event', function(arg){}) — создаёт прослушку на событие 'event' с аргументом arg; передавать аргументы будем чуть позже ) socket.id — ID подключения этого сокета (сокет — клиент), socket.broadcast.emit('newUser', name); — отправка события 'newUser' всем кроме текущего сокета, с переменной name (текущего сокета). socket.emit('userName', name); — отправляет событие 'userName' только текущему сокету c переменной name.

Для тех, кто не понял — сервер и клиент могут отправлять и принимать события одинаково:

socket.emit('event') — отправляет на сервер\клиент
socket.on — прослушивает события на клиенте
io.on — прослушивает события на сервере

Теперь создаём в «main.js» прослушки на 'newUser', 'userName'…

socket.on('userName', function(userName){ // Создаем прослушку 'userName' и принимаем переменную name в виде аргумента 'userName'
console.log('You\'r username is => ' + userName); // Логгирование в консоль браузера
$('textarea').val($('textarea').val() + 'You\'r username => ' + userName + '\n'); // Выводим в поле для текста оповещение для подключенного с его ником
});

socket.on('newUser', function(userName){ // Думаю тут понятно уже =)
console.log('New user has been connected to chat | ' + userName); // Логгирование
$('textarea').val($('textarea').val() + userName + ' connected!\n'); // Это событие было отправлено всем кроме только подключенного, по этому мы пишем другим юзерам в поле что 'подключен новый юзер' с его ником
});

$('textarea').val('текст'); — изменение текста в текстовом поле. \n — переход на новую строку

Теперь когда мы откроем localhost:3000 мы увидим встречный текст в поле с нашим ником! Прекрасно? Я знаю ^^ Но согласитесь, это еще не чат. Теперь нам надо сделать так, чтобы при нажатии на кнопку «Отправить» <= index.html отправлялся текст на сервер. Для этого дописываем в «main.js» обработчик на кнопку 'button' через jQuery.

$(document).on('click', 'button', function(){ // Прослушка кнопки на клик
var message = $('input').val(); // Все что в поле для ввода записываем в переменную
socket.emit('message', message); // Отправляем событие 'message' на сервер c самим текстом (message)- как переменная
$('input').val(null); // Заполняем поле для ввода 'пустотой'
});

После этого мы можем спокойно делать обработчик события 'message' на самом сервере.

chat.js:

io.on('connection', function (socket) {
  var name = 'U' + (socket.id).toString().substr(1,4);
  socket.broadcast.emit('newUser', name);

logger.info(name + ' connected to chat!');
  socket.emit('userName', name);
  // Обработчик ниже // Мы его сделали внутри коннекта

socket.on('message', function(msg){ // Обработчик на событие 'message' и аргументом (msg) из переменной message
    logger.warn('-----------'); // Logging
    logger.warn('User: ' + name + ' | Message: ' + msg);
    logger.warn('====> Sending message to other chaters...');
    io.sockets.emit('messageToClients', msg, name); // Отправляем всем сокетам событие 'messageToClients' и отправляем туда же два аргумента (текст, имя юзера)
  });
});

Отлично! Теперь когда мы будем отправлять текст со страницы, он у нас будет логгироваться в консоли node, так же мы всем клиентам отправили событие 'messageToClients', сейчас мы будем на клиенте делать обработчик этого события…

main.js:

socket.on('messageToClients', function(msg, name){
console.log(name + ' | => ' + msg); // Логгирование в консоль браузера
$('textarea').val($('textarea').val() + name + ' : '+ msg +'\n'); // Добавляем в поле для текста сообщение типа (Ник : текст)
});

Наш чат готов! Теперь вы можете его модернизировать как угодно.

Этот пост был создан потому, что топики, которые на Хабре по созданию чата, были запощены 5 лет назад, а за это время много чего произошло и я решил создать свой топик ) Тем не менее, вот топик, по которому я вдохновлялся — ссылочка. Может я где-то ошибся, поправляйте! Все люди ошибаются.

Спасибо всем за внимание!
Ads
AdBlock has stolen the banner, but banners are not teeth — they will be back

More

Comments 17

    +2
    Никогда не забывайте про package.json. Чтобы потом не гадать какие зависимости необходимы вашему серверу.
    И ставить модули поэтому надо с ключом --save, например «npm install express --save», тогда этот модуль сразу будет прописан в зависимостях.
      –2
      Спасибо за поправку, скоро поправлю )
      +5
      npm install http

      Что? Это https://npmjs.com/http, зачем он вам? Это пустой пакет, с одной версией 0.0.0, опубликованной три года назад.
      В Node.js есть встроенный модуль http, скорее всего, вы хотели его. Ваша ссылка, кстати, на https://npmjs.com/http-server ведёт почему-то.


      И да, как выше сказали — прописывайте зависимости в package.json.


      node-modules

      node_modules


      Вы скачаете архив, и оттуда вы должны достать файл «socket.io.js» и перекинуть в папку с проектом.

      Нет-нет-нет-нет. Не надо так делать. Используйте менеджеры зависимостей, например npm или bower — клиентские библиотеки тоже тяните оттуда, тогда они и у вас в репозитории валяться не будут, и обновлять проще, и сразу будет поднятно, какая версия чего.


      https://code.jquery.com/jquery-3.1.0.min.js

      Для изменения значения textarea? Серьёзно? И нет, не надо так тоже делать — если оно действительно нужно, поставьте зависимостью, не стоит со стороннего CDN тянуть. Помним про videojs.




      И, самое главное — зачем socket.io? Почему нельзя было ограничиться штатными вебсокетами?

        +6

        Совсем забыл:


        за это время много чего произошло

        Например, появился ECMAScript 2015 с const/let и стрелочными функциями. ESLint круто линтит код. IE9 и встроенный андроид браузер 4.3 (не путать с хромом на андроиде 4.3) умерли, штатные вебсокеты поддерживается всем. Использования jQuery для тривиальных операций не особо-то и нужно уже давно. =).


        И да — не в обиду автору, а улучшения ситуации для.


        В целом такие статьи с простыми примерами нужны, да.

          –2
          Спасибо за отзыв )
          0
          Не так давно реализовали чат на веб-сокетах с (слава богу) деградацией до поллинга. Какой же это геморой, никому не пожелаю, особенно на нагруженном проекте.
            +2
            Подключать jquery ради того чтобы «удобно» извлекать текст из поля? Тогда сразу агнуляр на прослушку самого поля ставьте! Мастхэв!
              –1
              :) jQuery обычно используется в больших проектах, по этому я считаю что проще воспользоваться им в таких случаях -)
                0
                jQuery как раз наоборот, года два как повсеместно выпиливается, большой проект с jQuery — либо легаси, либо бэкбон, либо стоит менять тех. лида. В свое время он был хорош, но сейчас избыточен, все его возможности делаются нативно в не особо больший код, а так же он тянет за собой желание неправильной работы (по современным, опять же, меркам) работы с DOM.
              +7
              http://socket.io/get-started/chat/
                –2
                Читайте внимательнее шапку ))
                  +5
                  >> я так и не смог найти подробного «гайда» по данному модулю

                  Вот я и помогаю вам его найти.
                0
                Пару месяцев назад понадобилась тоже создать чат на socket.io для прохождения собеседования (оно было успешно пройдено). Только задача состояла в том, чтобы создать кластерный чат с сохранением последних 10 сообщений в MongoDB. Если кому интересно, вот ссылочка на репозиторий.

                В сокетах есть один подводный камень, который всплывает при работе на кластерах (или многопоточных приложениях). Сокеты ломаются, при использовании протокола long polling, поэтому при создании многопоточного приложения используйте websocket. Так как протокол websocket не всеми старыми браузерами поддерживается, то по умолчанию в socket.io выбран протокол long polling.

                Если интересно, то могу написать более подробно в отдельном посте.
                  0
                  ** зануда мод
                  long polling это не протокол
                  +2
                  Я так понимаю, автор немножко «отрерайтил» урок от самого socket.io: http://socket.io/get-started/chat/
                  Socket.IO's «Hello world» is a chat app in just a few lines of code.

                  И, мягко говоря, непонятно, чем решение автора лучше кода от socket.io.
                  Хотя, справедливости ради, надо заметить что у них есть досадная ошибка — в jquery-коде они не оборачивают назначение событий в $(function (){, поэтому отправка формы срабатывает не так как надо, и с этим моментом приходится разбираться самому. Автор статьи этого избежал.
                    0
                    Да, я в шапке указал, что не все могут понять что да как там, по этому написал эту статью =)
                      +1
                      Если Вы чего-то не поняли, то лучше самому разобраться получше и тогда уже можно… Пойти еще поразбираться.

                      Это не плохо — изучать, помогать таким же новичкам (я истины не знаю, но статья тянет на новичка).
                      Но даже если и так, то вряд ли эта статья поможет понять малошарящему, почему используя express-generator(который рекомендует сам express) не получится Вашим способом настроить socket.IO, понять, как работает express, io.attach и прочее. А создать «простой чат на Socket.IO [2016] \ Node.js» можно взяв пример с сайта socket.io (как уже не раз упоминалось выше).

                      2. log4js # Отличный логгер

                      Чем этот логгер лучше стандартного 'debug', и почему выбор пал именно на него?

                  Only users with full accounts can post comments. Log in, please.