作者选择了 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地址。
首先,在本地计算机上,下载使用scp
的client.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 后面服务,您还创建了一个客户端应用程序,可以从其他机器连接到它。