Как разработать приложение TCP-сервера Node.js с использованием PM2 и Nginx в Ubuntu 16.04

Автор выбрал OSMI, чтобы получить пожертвование как часть Write for DOnations program.

Вступление

https://nodejs.org [Node.js] - это популярная среда выполнения JavaScript с открытым исходным кодом, построенная на движке Chrome V8 Javascript. Node.js используется для создания серверных и сетевых приложений. _TCP (Transmission Control Protocol) _ - сетевой протокол, который обеспечивает надежную, упорядоченную и проверенную доставку потока данных между приложениями. Сервер TCP может принять запрос на соединение TCP, и как только соединение установлено, обе стороны могут обмениваться потоками данных.

В этом руководстве вы создадите базовый TCP-сервер Node.js вместе с клиентом для тестирования сервера. Вы будете запускать свой сервер как фоновый процесс, используя мощный менеджер процессов Node.js под названием PM2. Затем вы настроите Nginx в качестве обратного прокси-сервера для приложения TCP и протестируете соединение клиент-сервер с локального компьютера.

Предпосылки

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

Шаг 1 - Создание приложения TCP Node.js

Мы напишем приложение Node.js с использованием сокетов TCP. Это пример приложения, которое поможет вам понять библиотеку Net в Node.js, которая позволяет нам создавать необработанные TCP-серверные и клиентские приложения.

Для начала создайте на своем сервере каталог, в котором вы хотели бы разместить приложение Node.js. Для этого урока мы создадим наше приложение в каталоге + ~ / tcp-node js-app:

mkdir ~/tcp-nodejs-app

Затем переключитесь на новый каталог:

cd ~/tcp-nodejs-app

Создайте новый файл с именем + package.json + для вашего проекта. В этом файле перечислены пакеты, от которых зависит приложение. Создание этого файла сделает воспроизводимую сборку, так как будет проще поделиться этим списком зависимостей с другими разработчиками:

nano package.json

Вы также можете сгенерировать + package.json + с помощью команды + npm init +, которая запросит у вас подробную информацию о приложении, но нам все равно придется вручную изменить файл, чтобы добавить дополнительные части, включая запуск команда. Поэтому мы вручную создадим файл в этом руководстве.

Добавьте следующий файл JSON в файл, который указывает имя приложения, версию, основной файл, команду для запуска приложения и лицензию на программное обеспечение:

package.json

{
 "name": "tcp-nodejs-app",
 "version": "1.0.0",
 "main": "server.js",
 "scripts": {
   "start": "node server.js"
 },
 "license": "MIT"
}

Поле + scripts + позволяет вам определять команды для вашего приложения. Указанная здесь настройка позволяет вам запускать приложение, запуская + npm start + вместо + node server.js +.

Файл + package.json + может также содержать список зависимостей времени выполнения и разработки, но у нас не будет сторонних зависимостей для этого приложения.

Теперь, когда у вас есть каталог проекта и настройка + package.json, давайте создадим сервер.

В каталоге вашего приложения создайте файл + server.js +:

nano server.js

Node.js предоставляет модуль под названием + net +, который позволяет TCP-серверу и клиенту взаимодействовать. Загрузите модуль + net + с помощью + require () +, затем определите переменные для хранения порта и хоста для сервера:

server.js

const net = require('net');
const port = 7070;
const host = '127.0.0.1';

Мы будем использовать порт + 7070 + для этого приложения, но вы можете использовать любой доступный порт, который вам нравится. Мы используем + 127.0.0.1 + для + HOST +, который гарантирует, что наш сервер прослушивает только наш локальный сетевой интерфейс. Позже мы разместим Nginx перед этим приложением в качестве обратного прокси. Nginx хорошо разбирается в обработке нескольких соединений и горизонтальном масштабировании.

Затем добавьте этот код для порождения TCP-сервера с помощью функции + createServer () + из модуля + net +. Затем начните прослушивать подключения к порту и хосту, которые вы определили, используя функцию + listen () + модуля + net +:

server.js

...
const server = net.createServer();
server.listen(port, host, () => {
   console.log('TCP Server is running on port ' + port +'.');
});

Сохраните + server.js + и запустите сервер:

npm start

Вы увидите этот вывод:

OutputTCP Server is running on port 7070

TCP-сервер работает на порту + 7070 +. Нажмите + CTRL + C +, чтобы остановить сервер.

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

Когда клиент подключается к серверу, сервер запускает событие + connection +, которое мы будем наблюдать. Мы определим массив подключенных клиентов, который мы назовем + sockets +, и добавим каждый экземпляр клиента в этот массив при подключении клиента.

Мы будем использовать событие + data + для обработки потока данных от подключенных клиентов, используя массив + sockets + для широковещательной передачи данных всем подключенным клиентам.

Добавьте этот код в файл + server.js + для реализации этих функций:

server.js

...

let sockets = [];

server.on('connection', function(sock) {
   console.log('CONNECTED: ' + sock.remoteAddress + ':' + sock.remotePort);
   sockets.push(sock);

   sock.on('data', function(data) {
       console.log('DATA ' + sock.remoteAddress + ': ' + data);
       // Write the data back to all the connected, the client will receive it as data from the server
       sockets.forEach(function(sock, index, array) {
           sock.write(sock.remoteAddress + ':' + sock.remotePort + " said " + data + '\n');
       });
   });
});

Это говорит серверу прослушивать события + data +, отправленные подключенными клиентами. Когда подключенные клиенты отправляют какие-либо данные на сервер, мы возвращаем их всем подключенным клиентам, просматривая массив + sockets +.

Затем добавьте обработчик для событий + close +, которые будут срабатывать, когда подключенный клиент завершит соединение. Всякий раз, когда клиент отключается, мы хотим удалить клиента из массива + sockets +, чтобы мы больше не передавали его. Добавьте этот код в конце блока подключения:

server.js

let sockets = [];
server.on('connection', function(sock) {

   ...

   // Add a 'close' event handler to this instance of socket
   sock.on('close', function(data) {
       let index = sockets.findIndex(function(o) {
           return o.remoteAddress === sock.remoteAddress && o.remotePort === sock.remotePort;
       })
       if (index !== -1) sockets.splice(index, 1);
       console.log('CLOSED: ' + sock.remoteAddress + ' ' + sock.remotePort);
   });
});

Вот полный код для + server.js:

server.js

const net = require('net');
const port = 7070;
const host = '127.0.0.1';

const server = net.createServer();
server.listen(port, host, () => {
   console.log('TCP Server is running on port ' + port + '.');
});

let sockets = [];

server.on('connection', function(sock) {
   console.log('CONNECTED: ' + sock.remoteAddress + ':' + sock.remotePort);
   sockets.push(sock);

   sock.on('data', function(data) {
       console.log('DATA ' + sock.remoteAddress + ': ' + data);
       // Write the data back to all the connected, the client will receive it as data from the server
       sockets.forEach(function(sock, index, array) {
           sock.write(sock.remoteAddress + ':' + sock.remotePort + " said " + data + '\n');
       });
   });

   // Add a 'close' event handler to this instance of socket
   sock.on('close', function(data) {
       let index = sockets.findIndex(function(o) {
           return o.remoteAddress === sock.remoteAddress && o.remotePort === sock.remotePort;
       })
       if (index !== -1) sockets.splice(index, 1);
       console.log('CLOSED: ' + sock.remoteAddress + ' ' + sock.remotePort);
   });
});

Сохраните файл и снова запустите сервер:

npm start

У нас есть полнофункциональный TCP-сервер, работающий на нашей машине. Далее мы напишем клиент для подключения к нашему серверу.

Шаг 2 - Создание TCP-клиента Node.js

Наш TCP-сервер Node.js запущен, поэтому давайте создадим TCP-клиент для подключения к серверу и тестирования сервера.

Сервер Node.js, который вы только что написали, все еще работает, блокируя текущий сеанс терминала. Мы хотим, чтобы это продолжалось при разработке клиента, поэтому откройте новое окно терминала или вкладку. Затем снова подключитесь к серверу из новой вкладки.

ssh @

После подключения перейдите в каталог + tcp-node js-app:

cd tcp-nodejs-app

В том же каталоге создайте новый файл с именем + client.js:

nano client.js

Клиент будет использовать ту же библиотеку + net +, которая использовалась в файле + server.js для подключения к TCP-серверу. Добавьте этот код в файл для подключения к серверу, используя IP-адрес + 127.0.0.1 + на порту + 7070 +:

client.js

const net = require('net');
const client = new net.Socket();
const port = 7070;
const host = '127.0.0.1';

client.connect(port, host, function() {
   console.log('Connected');
   client.write("Hello From Client " + client.address().address);
});

Этот код сначала попытается подключиться к TCP-серверу, чтобы убедиться, что созданный нами сервер работает. Как только соединение установлено, клиент отправит на сервер +" Hello From Client "+ client.address (). Address с помощью функции` + client.write a`. Наш сервер получит эти данные и отправит их клиенту.

Как только клиент получит данные обратно с сервера, мы хотим, чтобы он напечатал ответ сервера. Добавьте этот код, чтобы перехватить событие + data + и распечатать ответ сервера в командной строке:

client.js

client.on('data', function(data) {
   console.log('Server Says : ' + data);
});

Наконец, обработайте отключение от сервера, добавив следующий код:

client.js

client.on('close', function() {
   console.log('Connection closed');
});

Сохраните файл + client.js +.

Запустите следующую команду, чтобы запустить клиент:

node client.js

Соединение будет установлено, и сервер получит данные, передав их клиенту:

client.js OutputConnected
Server Says : 127.0.0.1:34548 said Hello From Client 127.0.0.1

Вернитесь к терминалу, на котором работает сервер, и вы увидите следующий вывод:

server.js OutputCONNECTED: 127.0.0.1:34550
DATA 127.0.0.1: Hello From Client 127.0.0.1

Вы убедились, что можете установить TCP-соединение между вашим сервером и клиентскими приложениями.

Нажмите + CTRL + C +, чтобы остановить сервер. Затем переключитесь на другой сеанс терминала и нажмите + CTRL + C +, чтобы остановить клиента. Теперь вы можете отключить этот сеанс терминала от вашего сервера и вернуться к исходному сеансу терминала.

На следующем шаге мы запустим сервер с PM2 и запустим его в фоновом режиме.

Шаг 3 - Запуск сервера с PM2

У вас есть рабочий сервер, который принимает клиентские подключения, но он работает на переднем плане. Давайте запустим сервер, используя PM2, чтобы он работал в backgrand и мог перезагружаться.

Сначала установите PM2 на ваш сервер глобально, используя + npm +:

sudo npm install pm2 -g

После установки PM2 используйте его для запуска вашего сервера. Вместо запуска + npm start + для запуска сервера вы будете использовать команду + pm2 +. Запустите сервер:

pm2 start server.js

Вы увидите вывод так:

[secondary_label Output
[PM2] Spawning PM2 daemon with pm2_home=/home/sammy/.pm2
[PM2] PM2 Successfully daemonized
[PM2] Starting /home/sammy/tcp-nodejs-app/server.js in fork_mode (1 instance)
[PM2] Done.
┌────────┬──────┬────────┬───┬─────┬───────────┐
│ Name   │ mode │ status │ ↺ │ cpu │ memory    │
├────────┼──────┼────────┼───┼─────┼───────────┤
│ server │ fork │ online │ 0 │ 5%  │ 24.8 MB   │
└────────┴──────┴────────┴───┴─────┴───────────┘
Use `pm2 show <id|name>` to get more details about an app

Сервер теперь работает в фоновом режиме. Однако если мы перезагрузим компьютер, он больше не будет работать, поэтому давайте создадим для него системный сервис.

Выполните следующую команду, чтобы сгенерировать и установить сценарии запуска systemd PM2. Обязательно запустите это с + sudo +, чтобы системные файлы устанавливались автоматически.

sudo pm2 startup

Вы увидите этот вывод:

Output[PM2] Init System found: systemd
Platform systemd

...

[PM2] Writing init configuration in /etc/systemd/system/pm2-root.service
[PM2] Making script booting at startup...
[PM2] [-] Executing: systemctl enable pm2-root...
Created symlink from /etc/systemd/system/multi-user.target.wants/pm2-root.service to /etc/systemd/system/pm2-root.service.
[PM2] [v] Command successfully executed.
+---------------------------------------+
[PM2] Freeze a process list on reboot via:
$ pm2 save

[PM2] Remove init script via:
$ pm2 unstartup systemd

PM2 теперь работает как сервис systemd.

Вы можете перечислить все процессы, которыми управляет PM2, с помощью команды + pm2 list:

pm2 list

Вы увидите свое приложение в списке с идентификатором + 0 +:

Output┌──────────┬────┬──────┬──────┬────────┬─────────┬────────┬─────┬───────────┬───────┬──────────┐
│ App name │ id │ mode │ pid  │ status │ restart │ uptime │ cpu │ mem       │ user  │ watching │
├──────────┼────┼──────┼──────┼────────┼─────────┼────────┼─────┼───────────┼───────┼──────────┤
│ server   │   │ fork │ 9075 │ online │ 0       │ 4m     │ 0%  │ 30.5 MB   │ sammy │ disabled │
└──────────┴────┴──────┴──────┴────────┴─────────┴────────┴─────┴───────────┴───────┴──────────┘

В предыдущем выводе вы заметите, что + Watching `отключен. Это функция, которая перезагружает сервер при внесении изменений в любой из файлов приложения. Это полезно при разработке, но нам не нужна эта функция при производстве.

Чтобы получить больше информации о любом из запущенных процессов, используйте команду + pm2 show, за которой следует ее идентификатор. В этом случае идентификатор + 0 +:

pm2 show

Эти выходные данные показывают время работы, статус, пути к файлу журнала и другую информацию о работающем приложении:

OutputDescribing process with id  - name server
┌───────────────────┬──────────────────────────────────────────┐
│ status            │ online                                   │
│ name              │ server                                   │
│ restarts          │ 0                                        │
│ uptime            │ 7m                                       │
│ script path       │ /home/sammy/tcp-nodejs-app/server.js     │
│ script args       │ N/A                                      │
│ error log path    │ /home/sammy/.pm2/logs/server-error-0.log │
│ out log path      │ /home/sammy/.pm2/logs/server-out-0.log   │
│ pid path          │ /home/sammy/.pm2/pids/server-0.pid       │
│ interpreter       │ node                                     │
│ interpreter args  │ N/A                                      │
│ script id         │ 0                                        │
│ exec cwd          │ /home/sammy/tcp-nodejs-app               │
│ exec mode         │ fork_mode                                │
│ node.js version   │ 8.11.2                                   │
│ watch & reload    │ ✘                                        │
│ unstable restarts │ 0                                        │
│ created at        │ 2018-05-30T19:29:45.765Z                 │
└───────────────────┴──────────────────────────────────────────┘
Code metrics value
┌─────────────────┬────────┐
│ Loop delay      │ 1.12ms │
│ Active requests │ 0      │
│ Active handles  │ 3      │
└─────────────────┴────────┘
Add your own code metrics: http://bit.ly/code-metrics
Use `pm2 logs server [--lines 1000]` to display logs
Use `pm2 monit` to monitor CPU and Memory usage server

Если состояние приложения показывает ошибку, вы можете использовать * путь к журналу ошибок *, чтобы открыть и просмотреть журнал ошибок, чтобы отладить ошибку:

cat /home/tcp/.pm2/logs/server-error-0.log

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

pm2 restart

PM2 теперь управляет приложением. Теперь мы будем использовать Nginx для прокси-запросов на сервер.

Шаг 4 - Настройте Nginx в качестве обратного прокси-сервера

Ваше приложение работает и ожидает + 127.0.0.1 +, что означает, что оно будет принимать соединения только с локальной машины. Мы настроим Nginx в качестве обратного прокси-сервера, который будет обрабатывать входящий трафик и направлять его на наш сервер.

Для этого мы изменим конфигурацию Nginx, чтобы использовать +stream {} + ` и https://nginx.org. /en/docs/stream/ngx_stream_proxy_module.html [

Related