如何在 Ubuntu 16.04 上使用 PM2 和 Nginx 开发 Node.js TCP 服务器应用程序

作者选择了 OSMI作为 写给捐款计划的一部分,以接收捐款。

介绍

Node.js是一个受欢迎的开源JavaScript运行环境,建立在Chrome的V8JavaScript引擎上。Node.js用于构建服务器侧和网络应用程序。_TCP(传输控制协议)_是一个网络协议,提供可靠,有序和错误检查的应用程序之间的数据流的交付。TCP服务器可以接受TCP连接请求,一旦连接建立,双方可以交换数据流。

在本教程中,您将构建一个基本的 Node.js TCP 服务器,以及一个客户端来测试服务器. 您将使用强大的 Node.js 流程管理器(PM2)(http://pm2.keymetrics.io/)运行您的服务器作为背景流程,然后将 Nginx配置为 TCP 应用程序的反向代理,并从本地机器测试客户端和服务器的连接。

前提条件

要完成本教程,您将需要:

  • 1个Ubuntu 16.04 服务器,通过跟随Ubuntu 16.04 初始服务器设置指南而设置,包括一个sudo非root用户和一个防火墙.
  • Nginx 安装在您的服务器上, 显示于 [How To Install Nginx on Ubuntu 16.04] (https://andsky.com/tech/tutorials/how-to-install-nginx-on-ubuntu-16-04). Nginx必须编译出 " 有流 " 选项,即通过Ubuntu 16.04上的 " apt " 软件包管理器对Nginx新安装的默认。
  • 如[如何在Ubuntu 16.04上安装节点(https://andsky.com/tech/tutorials/how-to-install-node-js-on-ubuntu-16-04]]所解释的,使用官方PPA安装的节点.js). .

第1步:创建一个Node.js TCP应用程序

这是一个样本应用程序,将帮助您理解Node.js中的Net库,这使我们能够创建原始的TCP服务器和客户端应用程序。

首先,在您的服务器上创建一个目录,您希望将您的 Node.js 应用程序放置在其中. 对于本教程,我们将在 ~/tcp-nodejs-app 目录中创建我们的应用程序:

1mkdir ~/tcp-nodejs-app

然后切换到新目录:

1cd ~/tcp-nodejs-app

为您的项目创建一个名为 package.json 的新文件. 此文件列出了应用程序依赖的包. 创建此文件将使构建可重复,因为将更容易与其他开发人员共享此依赖列表:

1nano package.json

您还可以使用npm init命令生成package.json,这将提示您了解应用程序的细节,但我们仍然需要手动更改文件以添加额外的片段,包括启动命令。

将下列 JSON 添加到文件中,指定应用程序的名称、版本、主文件、启动应用程序的命令和软件许可证:

 1[label package.json]
 2{
 3  "name": "tcp-nodejs-app",
 4  "version": "1.0.0",
 5  "main": "server.js",
 6  "scripts": {
 7    "start": "node server.js"
 8  },
 9  "license": "MIT"
10}

脚本字段允许您为您的应用程序定义命令. 您在这里指定的设置允许您通过运行npm start而不是运行node server.js来运行应用程序。

package.json文件也可能包含运行时间和开发依赖的列表,但我们不会为此应用程序提供任何第三方依赖。

现在你已经设置了项目目录和package.json,让我们创建服务器。

在应用程序目录中,创建一个server.js文件:

1nano server.js

Node.js 提供了一个名为net的模块,它允许 TCP 服务器和客户端进行通信. 将net模块加载到require(),然后定义变量以保持服务器的端口和主机:

1[label server.js]
2const net = require('net');
3const port = 7070;
4const host = '127.0.0.1';

我们将使用该应用程序的端口7070,但您可以使用任何可用的端口。我们正在使用127.0.0.1用于HOST,这确保我们的服务器只在本地网络接口上收听。

然后添加此代码,以从net模块中使用createServer()函数生成 TCP 服务器,然后使用net模块的listen()函数开始收听您定义的端口和主机上的连接:

1[label server.js]
2...
3const server = net.createServer();
4server.listen(port, host, () => {
5    console.log('TCP Server is running on port ' + port +'.');
6});

保存server.js并启动服务器:

1npm start

你会看到这个输出:

1[secondary_label Output]
2TCP Server is running on port 7070

TCP 服务器在端口 7070 上运行. 按 CTRL+C 来停止服务器。

现在我们知道服务器正在倾听,让我们编写代码来处理客户端连接。

当客户端连接到服务器时,服务器会触发一个连接事件,我们会观察到,我们会定义一组连接的客户端,我们会称之为插件,并在客户端连接时将每个客户端实例添加到该组。

我们将使用数据事件来处理来自连接客户端的数据流,使用插件数组向所有连接客户端传输数据。

将此代码添加到「server.js」文件中,以实现这些功能:

 1[label server.js]
 2
 3...
 4
 5let sockets = [];
 6
 7server.on('connection', function(sock) {
 8    console.log('CONNECTED: ' + sock.remoteAddress + ':' + sock.remotePort);
 9    sockets.push(sock);
10
11    sock.on('data', function(data) {
12        console.log('DATA ' + sock.remoteAddress + ': ' + data);
13        // Write the data back to all the connected, the client will receive it as data from the server
14        sockets.forEach(function(sock, index, array) {
15            sock.write(sock.remoteAddress + ':' + sock.remotePort + " said " + data + '\n');
16        });
17    });
18});

这告诉服务器听取连接客户端发送的数据事件.当连接客户端发送任何数据到服务器时,我们通过插槽数组迭代将其回响给所有连接客户端。

然后添加一个关闭事件的处理器,当一个连接的客户端终止连接时将触发。 每当一个客户端断开连接时,我们希望将客户端从插槽阵列中移除,以便我们不再向其播放。

 1[label server.js]
 2
 3let sockets = [];
 4server.on('connection', function(sock) {
 5
 6    ...
 7
 8    // Add a 'close' event handler to this instance of socket
 9    sock.on('close', function(data) {
10        let index = sockets.findIndex(function(o) {
11            return o.remoteAddress === sock.remoteAddress && o.remotePort === sock.remotePort;
12        })
13        if (index !== -1) sockets.splice(index, 1);
14        console.log('CLOSED: ' + sock.remoteAddress + ' ' + sock.remotePort);
15    });
16});

以下是server.js的完整代码:

 1[label server.js]
 2const net = require('net');
 3const port = 7070;
 4const host = '127.0.0.1';
 5
 6const server = net.createServer();
 7server.listen(port, host, () => {
 8    console.log('TCP Server is running on port ' + port + '.');
 9});
10
11let sockets = [];
12
13server.on('connection', function(sock) {
14    console.log('CONNECTED: ' + sock.remoteAddress + ':' + sock.remotePort);
15    sockets.push(sock);
16
17    sock.on('data', function(data) {
18        console.log('DATA ' + sock.remoteAddress + ': ' + data);
19        // Write the data back to all the connected, the client will receive it as data from the server
20        sockets.forEach(function(sock, index, array) {
21            sock.write(sock.remoteAddress + ':' + sock.remotePort + " said " + data + '\n');
22        });
23    });
24
25    // Add a 'close' event handler to this instance of socket
26    sock.on('close', function(data) {
27        let index = sockets.findIndex(function(o) {
28            return o.remoteAddress === sock.remoteAddress && o.remotePort === sock.remotePort;
29        })
30        if (index !== -1) sockets.splice(index, 1);
31        console.log('CLOSED: ' + sock.remoteAddress + ' ' + sock.remotePort);
32    });
33});

保存文件,然后重新启动服务器:

1npm start

我们有一个功能齐全的TCP服务器在我们的机器上运行,接下来我们将写一个客户端连接到我们的服务器。

第2步:创建一个 Node.js TCP 客户端

我们的 Node.js TCP 服务器正在运行,所以让我们创建一个 TCP 客户端连接到服务器并测试服务器。

您刚刚写的 Node.js 服务器仍在运行,阻止您当前的终端会话. 我们希望在开发客户端时继续运行,所以打开一个新的终端窗口或卡。

1ssh sammy@your_server_ip

一旦连接,导航到tcp-nodejs-app目录:

1[environment second]
2cd tcp-nodejs-app

在相同的目录中,创建一个名为client.js的新文件:

1[environment second]
2nano client.js

客户端将使用在 server.js 文件中使用的相同的 net 库连接到 TCP 服务器. 将此代码添加到文件中以在端口 7070 上使用 IP 地址 127.0.0.1` 连接到服务器:

 1[label client.js]
 2const net = require('net');
 3const client = new net.Socket();
 4const port = 7070;
 5const host = '127.0.0.1';
 6
 7client.connect(port, host, function() {
 8    console.log('Connected');
 9    client.write("Hello From Client " + client.address().address);
10});

此代码将首先尝试连接到TCP服务器,以确保我们创建的服务器正在运行.一旦连接建立,客户端将通过client.write函数将Hello From Client + client.address().address`发送到服务器。

一旦客户端从服务器接收数据,我们希望它打印服务器的响应. 添加此代码来捕捉数据事件,并打印服务器对命令行的响应:

1[label client.js]
2client.on('data', function(data) {
3    console.log('Server Says : ' + data);
4});

最后,通过添加以下代码,优雅地处理从服务器的断开连接:

1[label client.js]
2client.on('close', function() {
3    console.log('Connection closed');
4});

保存「client.js」檔案。

运行以下命令来启动客户端:

1[environment second]
2node client.js

连接将建立,服务器将接收数据,回应它回到客户端:

1[secondary_label client.js Output]
2[environment second]
3Connected
4Server Says : 127.0.0.1:34548 said Hello From Client 127.0.0.1

返回服务器正在运行的终端,您将看到以下输出:

1[secondary_label server.js Output]
2CONNECTED: 127.0.0.1:34550
3DATA 127.0.0.1: Hello From Client 127.0.0.1

您已经验证了您可以在服务器和客户端应用程序之间建立 TCP 连接。

CTRL+C停止服务器,然后切换到另一个终端会话,然后按CTRL+C停止客户端。

在下一步中,我们将使用PM2启动服务器,并在后台运行。

步骤 3 – 使用 PM2 运行服务器

你有一个工作服务器,它接受客户端连接,但它在前沿运行. 让我们使用PM2运行服务器,以便它在后台运行,并可以顺利重新启动。

首先,在您的全球服务器上安装 PM2 使用npm:

1sudo npm install pm2 -g

PM2 安装后,使用它来运行您的服务器,而不是运行npm start来启动服务器,您将使用pm2命令。

1pm2 start server.js

你会看到这样的输出:

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

但是,如果我们重新启动机器,它将不再运行,所以让我们为它创建一个 systemd 服务。

运行以下命令来生成和安装 PM2 的 systemd 启动脚本. 请确保使用sudo执行此操作,以便系统d 文件自动安装。

1sudo pm2 startup

你会看到这个输出:

 1[secondary_label Output]
 2[PM2] Init System found: systemd
 3Platform systemd
 4
 5...
 6
 7[PM2] Writing init configuration in /etc/systemd/system/pm2-root.service
 8[PM2] Making script booting at startup...
 9[PM2] [-] Executing: systemctl enable pm2-root...
10Created symlink from /etc/systemd/system/multi-user.target.wants/pm2-root.service to /etc/systemd/system/pm2-root.service.
11[PM2] [v] Command successfully executed.
12+---------------------------------------+
13[PM2] Freeze a process list on reboot via:
14$ pm2 save
15
16[PM2] Remove init script via:
17$ pm2 unstartup systemd

PM2 现在作为系统d 服务运行。

您可以使用pm2 列表命令列出 PM2 正在管理的所有流程:

1pm2 list

您将在列表中看到您的申请,标识为0:

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

在之前的输出中,你会注意到观看已禁用,这是当你对任何应用程序文件进行更改时重新加载服务器的功能。

要获取有关任何运行过程的更多信息,请使用pm2 show命令,然后是其ID。

1pm2 show 0

此输出显示了运行时间、状态、日志文件路径以及有关运行应用程序的其他信息:

 1[secondary_label Output]
 2Describing process with id 0 - name server
 3┌───────────────────┬──────────────────────────────────────────┐
 4 status             online                                   
 5 name               server                                   
 6 restarts           0                                        
 7 uptime             7m                                       
 8 script path        /home/sammy/tcp-nodejs-app/server.js     
 9 script args        N/A                                      
10 error log path     /home/sammy/.pm2/logs/server-error-0.log 
11 out log path       /home/sammy/.pm2/logs/server-out-0.log   
12 pid path           /home/sammy/.pm2/pids/server-0.pid       
13 interpreter        node                                     
14 interpreter args   N/A                                      
15 script id          0                                        
16 exec cwd           /home/sammy/tcp-nodejs-app               
17 exec mode          fork_mode                                
18 node.js version    8.11.2                                   
19 watch & reload                                             
20 unstable restarts  0                                        
21 created at         2018-05-30T19:29:45.765Z                 
22└───────────────────┴──────────────────────────────────────────┘
23Code metrics value
24┌─────────────────┬────────┐
25 Loop delay       1.12ms 
26 Active requests  0      
27 Active handles   3      
28└─────────────────┴────────┘
29Add your own code metrics: http://bit.ly/code-metrics
30Use `pm2 logs server [--lines 1000]` to display logs
31Use `pm2 monit` to monitor CPU and Memory usage server

如果应用程序状态显示错误,您可以使用 error log path 打开并检查错误日志来纠正错误:

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

如果您对服务器代码进行了更改,则需要重新启动应用程序的流程以应用这些更改,如下:

1pm2 restart 0

PM2 现在正在管理应用程序,现在我们将使用 Nginx 来向服务器发送代理请求。

步骤4:将 Nginx 设置为反向代理服务器

您的应用程序正在运行并听到127.0.0.1,这意味着它只会接受来自本地机器的连接,我们将设置 Nginx 作为一个反向代理,以处理流量并将其导向到我们的服务器。

为了做到这一点,我们将修改 Nginx 配置以使用 Nginx 的 stream {}stream_proxy 功能,将 TCP 连接传输到我们的 Node.js 服务器。

我们必须将主要的 Nginx 配置文件编辑为配置 TCP 连接转发的块,仅作为顶级块工作。

在您的编辑器中打开文件 /etc/nginx/nginx.conf:

1sudo nano /etc/nginx/nginx.conf

在配置文件的末尾添加以下行:

 1[label /etc/nginx/nginx.conf]
 2
 3...
 4
 5stream {
 6    server {
 7      listen 3000;
 8      proxy_pass 127.0.0.1:7070;        
 9      proxy_protocol on;
10    }
11}

它会听取 TCP 连接的端口 3000,并向您的 Node.js 服务器发送请求,在端口 7070 上运行. 如果您的应用程序设置为在不同的端口上收听,请将代理传输 URL 端口更新到正确的端口号。

保存文件并离开编辑器。

检查您的 Nginx 配置,以确保您没有输入任何语法错误:

1sudo nginx -t

接下来,重新启动 Nginx 以启用 TCP 和 UDP 代理功能:

1sudo systemctl restart nginx

接下来,允许 TCP 连接到我们的服务器在该端口. 使用ufw允许在端口3000上的连接:

1sudo sudo ufw allow 3000

假设您的 Node.js 应用程序正在运行,您的应用程序和 Nginx 配置是正确的,您现在应该能够通过 Nginx 反向代理程序访问您的应用程序。

步骤5:测试客户端服务器连接

让我们通过使用client.js脚本连接到本地机器的TCP服务器来测试服务器,因此您需要下载您为本地机器开发的client.js文件,并在脚本中更改端口和IP地址。

首先,在本地计算机上,下载使用scpclient.js文件:

1[environment local
2scp sammy@your_server_ip:~/tcp-nodejs-app/client.js client.js

在您的编辑器中打开client.js文件:

1[environment local
2nano client.js

端口更改为3000,并将主机更改为您的服务器的IP地址:

1[label client.js]
2// A Client Example to connect to the Node.js TCP Server
3const net = require('net');
4const client = new net.Socket();
5const port = 3000;
6const host = 'your_server_ip';
7...

保存文件,离开编辑器,并通过运行客户端来测试事物:

1node client.js

您将看到您之前运行时看到的相同的输出,表明您的客户端机器通过 Nginx 连接并到达您的服务器:

1[environment local]
2[secondary_label client.js Output]
3Connected
4Server Says : 127.0.0.1:34584 said PROXY TCP4 your_local_ip_address your_server_ip 52920 3000
5Hello From Client your_local_ip_address

由于Nginx正在代理客户端连接您的服务器,您的Node.js服务器不会看到客户端的真实IP地址;它只会看到Nginx的IP地址. Nginx不支持直接将真实的IP地址发送到后端而不对您的系统进行一些可能会影响安全的变化,但是由于我们启用了Nginx中的PROXY协议,Node.js服务器现在收到了额外"PROXY"消息,包含真实的IP. 如果您需要 IP 地址, 您可以调整您的服务器, 以处理 ` PROXY' 请求并解析您需要的数据 .

您现在有 Node.js TCP 应用程序运行在 Nginx 反向代理后面,并可以继续开发您的服务器。

结论

在本教程中,您使用 Node.js 创建了一个 TCP 应用程序,使用 PM2 运行,然后在 Nginx 后面服务,您还创建了一个客户端应用程序,可以从其他机器连接到它。

Published At
Categories with 技术
comments powered by Disqus