Comment développer une application serveur TCP Node.js utilisant PM2 et Nginx sur Ubuntu 16.04

L’auteur a sélectionné OSMI pour recevoir un don dans le cadre du Write for DOnations programme.

introduction

https://nodejs.org [Node.js] est un environnement d’exécution JavaScript populaire à code source ouvert, construit sur le moteur Javascript V8 de Chrome. Node.js est utilisé pour créer des applications côté serveur et en réseau._TCP (Transmission Control Protocol) est un protocole de réseau qui fournit une livraison fiable, ordonnée et vérifiée des erreurs d’un flux de données entre applications. Un serveur TCP peut accepter une demande de connexion TCP et, une fois la connexion établie, les deux côtés peuvent échanger des flux de données.

Dans ce didacticiel, vous allez créer un serveur TCP Node.js de base, ainsi qu’un client pour tester le serveur. Vous exécuterez votre serveur en tant que processus d’arrière-plan à l’aide d’un puissant gestionnaire de processus Node.js appelé PM2. Ensuite, vous configurerez Nginx en tant que proxy inverse pour l’application TCP et testerez la connexion client-serveur à partir de votre ordinateur local.

Conditions préalables

Pour compléter ce tutoriel, vous aurez besoin de:

Étape 1 - Création d’une application TCP Node.js

Nous allons écrire une application Node.js utilisant TCP Sockets. Ceci est un exemple d’application qui vous aidera à comprendre la bibliothèque Net dans Node.js, ce qui nous permet de créer des applications clientes et serveur TCP brutes.

Pour commencer, créez un répertoire sur votre serveur dans lequel vous souhaitez placer votre application Node.js. Pour ce tutoriel, nous allons créer notre application dans le répertoire + ~ / tcp-node js-app:

mkdir ~/tcp-nodejs-app

Ensuite, basculez vers le nouveau répertoire:

cd ~/tcp-nodejs-app

Créez un nouveau fichier nommé + package.json + pour votre projet. Ce fichier répertorie les packages dont dépend l’application. La création de ce fichier rendra la construction reproductible car il sera plus facile de partager cette liste de dépendances avec d’autres développeurs:

nano package.json

Vous pouvez également générer le paquet + package.json + à l’aide de la commande + npm init +, qui vous demandera les détails de l’application, mais nous devrons toujours modifier manuellement le fichier pour ajouter des éléments supplémentaires, y compris un démarrage. commander. Par conséquent, nous allons créer manuellement le fichier dans ce tutoriel.

Ajoutez le fichier JSON suivant au fichier, qui spécifie le nom de l’application, la version, le fichier principal, la commande permettant de démarrer l’application et la licence du logiciel:

package.json

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

Le champ + scripts + vous permet de définir des commandes pour votre application. Le paramètre que vous avez spécifié ici vous permet d’exécuter l’application en exécutant + npm start + au lieu de + node server.js +.

Le fichier + package.json peut également contenir une liste de dépendances d’exécution et de développement, mais nous n’aurons aucune dépendance tierce pour cette application.

Maintenant que vous avez le répertoire du projet et la configuration de + package.json, créons le serveur.

Dans votre répertoire d’application, créez un fichier + server.js +:

nano server.js

Node.js fournit un module appelé + net + qui permet la communication serveur TCP / client. Chargez le module + net + avec + require () +, puis définissez des variables pour contenir le port et l’hôte du serveur:

server.js

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

Nous allons utiliser le port + 7070 + pour cette application, mais vous pouvez utiliser le port disponible de votre choix. Nous utilisons + 127.0.0.1 + pour le + HOST + qui garantit que notre serveur n’écoute que sur notre interface réseau locale. Plus tard, nous placerons Nginx devant cette application en tant que proxy inverse. Nginx maîtrise parfaitement les connexions multiples et la mise à l’échelle horizontale.

Ajoutez ensuite ce code pour générer un serveur TCP en utilisant la fonction + createServer () + du module + net +. Ensuite, commencez à écouter les connexions sur le port et l’hôte que vous avez définis à l’aide de la fonction + listen () + du module + net +:

server.js

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

Enregistrez + server.js + et démarrez le serveur:

npm start

Vous verrez cette sortie:

OutputTCP Server is running on port 7070

Le serveur TCP fonctionne sur le port + 7070 +. Appuyez sur + CTRL + C + pour arrêter le serveur.

Maintenant que nous savons que le serveur est à l’écoute, écrivons le code permettant de gérer les connexions client.

Lorsqu’un client se connecte au serveur, celui-ci déclenche un événement + connection +, que nous observerons. Nous allons définir un tableau de clients connectés, que nous appellerons + sockets +, et ajouterons chaque instance de client à ce tableau lors de la connexion du client.

Nous allons utiliser l’événement + data + pour traiter le flux de données des clients connectés, en utilisant le tableau + sockets + pour diffuser les données à tous les clients connectés.

Ajoutez ce code au fichier + server.js + pour implémenter ces fonctionnalités:

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');
       });
   });
});

Cela indique au serveur d’écouter les événements + data + envoyés par les clients connectés. Lorsque les clients connectés envoient des données au serveur, nous les renvoyons à tous les clients connectés en effectuant une itération dans le tableau + sockets +.

Ajoutez ensuite un gestionnaire pour les événements + close + qui seront déclenchés lorsqu’un client connecté met fin à la connexion. Chaque fois qu’un client se déconnecte, nous voulons supprimer le client du tableau + sockets + afin que nous ne lui diffusions plus. Ajoutez ce code à la fin du bloc de connexion:

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);
   });
});

Voici le code complet pour + 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);
   });
});

Enregistrez le fichier puis redémarrez le serveur:

npm start

Nous avons un serveur TCP entièrement fonctionnel sur notre machine. Ensuite, nous écrirons un client pour se connecter à notre serveur.

Étape 2 - Création d’un client TCP Node.js

Notre serveur TCP Node.js est en cours d’exécution. Créons donc un client TCP pour vous connecter au serveur et le tester.

Le serveur Node.js que vous venez d’écrire est toujours en cours d’exécution, bloquant votre session de terminal en cours. Nous souhaitons que cela continue à mesure que nous développons le client, ouvrez donc une nouvelle fenêtre ou un nouvel onglet Terminal. Puis reconnectez-vous au serveur à partir du nouvel onglet.

ssh @

Une fois connecté, accédez au répertoire + tcp-node js-app:

cd tcp-nodejs-app

Dans le même répertoire, créez un nouveau fichier nommé + client.js:

nano client.js

Le client utilisera la même bibliothèque + net + que celle utilisée dans le fichier + server.js pour se connecter au serveur TCP. Ajoutez ce code au fichier pour vous connecter au serveur à l’aide de l’adresse IP + 127.0.0.1 + sur le port + 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);
});

Ce code essaiera d’abord de se connecter au serveur TCP pour s’assurer que le serveur que nous avons créé est en cours d’exécution. Une fois la connexion établie, le client envoie +" Hello From Client "+ client.address (). Address au serveur à l’aide de la fonction` + client.write a`. Notre serveur recevra ces données et les renverra au client.

Une fois que le client reçoit les données du serveur, nous souhaitons qu’il imprime la réponse du serveur. Ajoutez ce code pour intercepter l’événement + data + et imprimer la réponse du serveur à la ligne de commande:

client.js

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

Enfin, gérez les déconnexions du serveur en ajoutant ce code:

client.js

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

Enregistrez le fichier + client.js +.

Exécutez la commande suivante pour démarrer le client:

node client.js

La connexion sera établie et le serveur recevra les données, en les renvoyant au client:

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

Revenez au terminal sur lequel le serveur est en cours d’exécution. Le résultat suivant s’affiche:

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

Vous avez vérifié que vous pouvez établir une connexion TCP entre votre serveur et les applications client.

Appuyez sur + CTRL + C + pour arrêter le serveur. Puis passez à l’autre session de terminal et appuyez sur les touches + CTRL + C + pour arrêter le client. Vous pouvez maintenant déconnecter cette session de terminal de votre serveur et revenir à votre session de terminal d’origine.

Dans l’étape suivante, nous allons lancer le serveur avec PM2 et l’exécuter en arrière-plan.

Étape 3 - Exécution du serveur avec PM2

Vous avez un serveur de travail qui accepte les connexions client, mais il s’exécute au premier plan. Exécutons le serveur à l’aide de PM2 afin qu’il s’exécute en arrière-plan et puisse redémarrer normalement.

Premièrement, installez PM2 sur votre serveur globalement en utilisant + npm +:

sudo npm install pm2 -g

Une fois PM2 installé, utilisez-le pour exécuter votre serveur. Au lieu d’exécuter + npm start + pour démarrer le serveur, vous utiliserez la commande + pm2 +. Démarrer le serveur:

pm2 start server.js

Vous verrez la sortie comme ceci:

[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

Le serveur fonctionne maintenant en arrière-plan. Cependant, si nous redémarrons la machine, celle-ci ne fonctionnera plus, alors créons un service systemd pour cette machine.

Exécutez la commande suivante pour générer et installer les scripts de démarrage systemd de PM2. Assurez-vous de l’exécuter avec + sudo + pour que les fichiers systemd s’installent automatiquement.

sudo pm2 startup

Vous verrez cette sortie:

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 fonctionne maintenant en tant que service systemd.

Vous pouvez lister tous les processus que PM2 gère avec la commande + pm2 list:

pm2 list

Vous verrez votre application dans la liste, avec l’ID «+ 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 │
└──────────┴────┴──────┴──────┴────────┴─────────┴────────┴─────┴───────────┴───────┴──────────┘

Dans la sortie précédente, vous remarquerez que + regarder + est désactivé. Il s’agit d’une fonctionnalité qui recharge le serveur lorsque vous modifiez l’un des fichiers de l’application. C’est utile en développement, mais nous n’avons pas besoin de cette fonctionnalité en production.

Pour obtenir plus d’informations sur les processus en cours, utilisez la commande + pm2 show, suivie de son ID. Dans ce cas, l’ID est + 0 +:

pm2 show

Cette sortie indique le temps de disponibilité, l’état, les chemins d’accès aux fichiers journaux et d’autres informations sur l’application en cours d’exécution:

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

Si le statut de l’application indique une erreur, vous pouvez utiliser le * chemin du journal des erreurs * pour l’ouvrir et consulter le journal des erreurs pour déboguer l’erreur:

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

Si vous apportez des modifications au code du serveur, vous devrez redémarrer le processus de l’application pour appliquer les modifications, comme suit:

pm2 restart

PM2 gère maintenant l’application. Nous allons maintenant utiliser Nginx pour envoyer des requêtes proxy au serveur.

Étape 4 - Configurer Nginx en tant que serveur proxy inverse

Votre application est en cours d’exécution et à l’écoute sur + 127.0.0.1 +, ce qui signifie qu’elle acceptera uniquement les connexions à partir de la machine locale. Nous allons configurer Nginx en tant que proxy inverse, qui gérera le trafic entrant et le dirigera vers notre serveur.

Pour ce faire, nous allons modifier la configuration de Nginx afin qu’elle utilise les +stream {} + ` et https://nginx.org /en/docs/stream/ngx_stream_proxy_module.html [