Cách phát triển ứng dụng Node.js TCP Server bằng PM2 và Nginx trên Ubuntu 16.04
Node.js là một môi trường chạy JavaScript open-souce phổ biến được xây dựng trên công cụ Javascript V8 của Chrome. Node.js được sử dụng để xây dựng các ứng dụng mạng và phía server . TCP (Transmission Control Protocol) là một giao thức mạng cung cấp khả năng phân phối stream dữ liệu giữa các ứng dụng một cách tin cậy , có thứ tự và được kiểm tra lỗi. Server TCP có thể chấp nhận yêu cầu kết nối TCP và khi kết nối được cài đặt , cả hai bên có thể trao đổi stream dữ liệu.Trong hướng dẫn này, bạn sẽ xây dựng một server TCP Node.js cơ bản, cùng với một client để kiểm tra server . Bạn sẽ chạy server của bạn dưới dạng một quy trình bằng cách sử dụng trình quản lý tiến trình Node.js mạnh mẽ được gọi là PM2 . Sau đó, bạn sẽ cấu hình Nginx làm Reverse Proxy cho ứng dụng TCP và kiểm tra kết nối client - server từ máy local của bạn .
Yêu cầu
Để hoàn thành hướng dẫn này, bạn cần :
- Một server Ubuntu 16.04 được cài đặt theo hướng dẫn cài đặt server ban đầu Ubuntu 16.04 , bao gồm user không phải root có quyền sudo và firewall .
- Nginx đã được cài đặt trên server của bạn, như trong Cách cài đặt Nginx trên Ubuntu 16.04 . Nginx phải được biên dịch với tùy chọn
--with-stream
, là tùy chọn mặc định khi cài đặt mới Nginx thông qua trình quản lý góiapt
trên Ubuntu 16.04. - Node.js được cài đặt bằng PPA chính thức, như được giải thích trong Cách cài đặt Node.js trên Ubuntu 16.04 .
Bước 1 - Tạo ứng dụng TCP Node.js
Ta sẽ viết một ứng dụng Node.js bằng TCP Sockets. Đây là một ứng dụng mẫu sẽ giúp bạn hiểu thư viện Net trong Node.js, cho phép ta tạo các ứng dụng server và client TCP thô.
Để bắt đầu, hãy tạo một folder trên server của bạn mà bạn muốn đặt ứng dụng Node.js của bạn . Đối với hướng dẫn này, ta sẽ tạo ứng dụng của bạn trong folder ~/tcp-nodejs-app
:
- mkdir ~/tcp-nodejs-app
Sau đó chuyển sang folder mới:
- cd ~/tcp-nodejs-app
Tạo một file mới có tên package.json
cho dự án của bạn. Tệp này liệt kê các gói mà ứng dụng phụ thuộc vào. Việc tạo file này sẽ làm cho bản dựng có thể tái tạo được vì sẽ dễ dàng chia sẻ danh sách các phần phụ thuộc này với các nhà phát triển khác:
- nano package.json
Bạn cũng có thể tạo package.json
bằng cách sử dụng lệnh npm init
, lệnh này sẽ nhắc bạn về các chi tiết của ứng dụng, nhưng ta vẫn phải thay đổi file theo cách thủ công để thêm các phần bổ sung, bao gồm cả lệnh khởi động. Do đó, ta sẽ tạo file theo cách thủ công trong hướng dẫn này.
Thêm JSON sau vào file , chỉ định tên ứng dụng, version , file chính, lệnh khởi động ứng dụng và giấy phép phần mềm:
{ "name": "tcp-nodejs-app", "version": "1.0.0", "main": "server.js", "scripts": { "start": "node server.js" }, "license": "MIT" }
Trường scripts
cho phép bạn xác định các lệnh cho ứng dụng của bạn . Cài đặt bạn đã chỉ định ở đây cho phép bạn chạy ứng dụng bằng cách chạy npm start
thay vì chạy node server.js
.
Tệp package.json
cũng có thể chứa danh sách các phụ thuộc thời gian chạy và phát triển, nhưng ta sẽ không có bất kỳ phụ thuộc bên thứ ba nào cho ứng dụng này.
Đến đây bạn đã cài đặt folder dự án và package.json
, hãy tạo server .
Trong folder ứng dụng của bạn, hãy tạo file server.js
:
- nano server.js
Node.js cung cấp một module gọi là net
cho phép giao tiếp giữa server và client TCP. Tải module net
bằng require()
, sau đó xác định các biến để giữ cổng và server cho server :
const net = require('net'); const port = 7070; const host = '127.0.0.1';
Ta sẽ sử dụng cổng 7070
cho ứng dụng này, nhưng bạn có thể sử dụng bất kỳ cổng nào có sẵn mà bạn muốn. Ta đang sử dụng 127.0.0.1
cho HOST
, đảm bảo server của ta chỉ lắng nghe trên network interface local của ta . Sau đó, ta sẽ đặt Nginx trước ứng dụng này như một Reverse Proxy . Nginx rất thành thạo trong việc xử lý nhiều kết nối và mở rộng quy mô theo chiều ngang.
Sau đó, thêm mã này để tạo ra một server TCP bằng cách sử dụng hàm createServer()
từ module net
. Sau đó, bắt đầu lắng nghe các kết nối trên cổng và server lưu trữ mà bạn đã xác định bằng cách sử dụng hàm listen()
của module net
:
... const server = net.createServer(); server.listen(port, host, () => { console.log('TCP Server is running on port ' + port +'.'); });
Lưu server.js
và khởi động server :
- npm start
Bạn sẽ thấy kết quả này:
OutputTCP Server is running on port 7070
Server TCP đang chạy trên cổng 7070
. Nhấn CTRL+C
để dừng server .
Bây giờ ta biết server đang lắng nghe, hãy viết mã để xử lý các kết nối client .
Khi một client kết nối với server , server sẽ kích hoạt sự kiện connection
mà ta sẽ quan sát. Ta sẽ xác định một mảng các client được kết nối, mà ta sẽ gọi là sockets
và thêm từng cá thể client vào mảng này khi client kết nối.
Ta sẽ sử dụng sự kiện data
để xử lý stream dữ liệu từ các client được kết nối, sử dụng mảng sockets
để truyền dữ liệu tới tất cả các client được kết nối.
Thêm mã này vào file server.js
để triển khai các tính năng sau:
... 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'); }); }); });
Điều này cho server lắng nghe các sự kiện data
được gửi bởi các client được kết nối. Khi các client được kết nối gửi bất kỳ dữ liệu nào đến server , ta gửi dữ liệu đó trở lại tất cả các client được kết nối bằng cách lặp lại qua mảng sockets
.
Sau đó, thêm một trình xử lý cho các sự kiện close
sẽ được chia nhỏ khi một client được kết nối chấm dứt kết nối. Khi nào client ngắt kết nối, ta muốn xóa client khỏi mảng sockets
để ta không phát sóng tới nó nữa. Thêm mã này vào cuối khối kết nối:
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); }); });
Đây là mã hoàn chỉnh cho 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); }); });
Lưu file và sau đó khởi động lại server :
- npm start
Ta có một Server TCP đầy đủ chức năng đang chạy trên máy của ta . Tiếp theo ta sẽ viết một ứng dụng client để kết nối với server của ta .
Bước 2 - Tạo một Node.js TCP Client
Server TCP Node.js của ta đang chạy, vì vậy hãy tạo một Máy khách TCP để kết nối với server và kiểm tra server .
Server Node.js bạn vừa viết vẫn đang chạy, chặn phiên terminal hiện tại của bạn. Ta muốn giữ cho nó hoạt động khi ta phát triển ứng dụng client , vì vậy hãy mở một cửa sổ hoặc tab Terminal mới. Sau đó kết nối lại vào server từ tab mới.
- ssh sammy@your_server_ip
Sau khi kết nối, chuyển đến folder tcp-nodejs-app
:
- cd tcp-nodejs-app
Trong cùng một folder , hãy tạo một file mới có tên là client.js
:
- nano client.js
Máy khách sẽ sử dụng cùng một thư viện net
được sử dụng trong file server.js
để kết nối với server TCP. Thêm mã này vào file để kết nối với server bằng địa chỉ IP 127.0.0.1
trên cổng 7070
:
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); });
Mã này trước tiên sẽ cố gắng kết nối với server TCP đảm bảo rằng server mà ta đã tạo đang chạy. Khi kết nối được cài đặt , client sẽ gửi địa chỉ "Hello From Client " + client.address().address
Đến server bằng cách sử dụng hàm client.write
. Server của ta sẽ nhận dữ liệu này và gửi lại cho client .
Khi client nhận lại dữ liệu từ server , ta muốn nó in phản hồi của server . Thêm mã này để bắt sự kiện data
và in phản hồi của server cho dòng lệnh:
client.on('data', function(data) { console.log('Server Says : ' + data); });
Cuối cùng, xử lý các ngắt kết nối khỏi server một cách duyên dáng bằng cách thêm mã này:
client.on('close', function() { console.log('Connection closed'); });
Lưu file client.js
.
Chạy lệnh sau để khởi động client :
- node client.js
Kết nối sẽ cài đặt và server sẽ nhận dữ liệu, gửi dữ liệu trở lại client :
client.js OutputConnected Server Says : 127.0.0.1:34548 said Hello From Client 127.0.0.1
Quay lại terminal nơi server đang chạy và bạn sẽ thấy kết quả sau:
server.js OutputCONNECTED: 127.0.0.1:34550 DATA 127.0.0.1: Hello From Client 127.0.0.1
Bạn đã xác minh bạn có thể cài đặt kết nối TCP giữa server và ứng dụng client .
Nhấn CTRL+C
để dừng server . Sau đó chuyển sang phiên terminal khác và nhấn CTRL+C
để dừng client . Đến đây bạn có thể ngắt kết nối phiên terminal này khỏi server của bạn và quay lại phiên terminal ban đầu của bạn.
Trong bước tiếp theo, ta sẽ chạy server với PM2 và chạy nó trong nền.
Bước 3 - Chạy Server với PM2
Bạn có một server đang hoạt động chấp nhận các kết nối client , nhưng nó chạy ở chế độ nền trước. Hãy chạy server bằng PM2 để nó chạy trong backgrand và có thể khởi động lại một cách duyên dáng.
Trước tiên, hãy cài đặt PM2 trên server của bạn trên phạm vi global bằng cách sử dụng npm
:
- sudo npm install pm2 -g
Sau khi PM2 được cài đặt, hãy sử dụng nó để chạy server của bạn. Thay vì chạy npm start
để khởi động server , bạn sẽ sử dụng lệnh pm2
. Khởi động server :
- pm2 start server.js
Bạn sẽ thấy kết quả như thế này:
[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
Server hiện đang chạy ở chế độ nền. Tuy nhiên, nếu ta khởi động lại máy, nó sẽ không chạy nữa, vì vậy hãy tạo một dịch vụ systemd cho nó.
Chạy lệnh sau để tạo và cài đặt các tập lệnh khởi động systemd của PM2. Đảm bảo chạy điều này với sudo
để các file systemd tự động cài đặt.
- sudo pm2 startup
Bạn sẽ thấy kết quả này:
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 hiện đang chạy như một dịch vụ systemd.
Bạn có thể liệt kê tất cả các quy trình mà PM2 đang quản lý bằng lệnh pm2 list
:
- pm2 list
Bạn sẽ thấy ứng dụng của bạn trong danh sách, với ID là 0
:
Output┌──────────┬────┬──────┬──────┬────────┬─────────┬────────┬─────┬───────────┬───────┬──────────┐ │ App name │ id │ mode │ pid │ status │ restart │ uptime │ cpu │ mem │ user │ watching │ ├──────────┼────┼──────┼──────┼────────┼─────────┼────────┼─────┼───────────┼───────┼──────────┤ │ server │ 0 │ fork │ 9075 │ online │ 0 │ 4m │ 0% │ 30.5 MB │ sammy │ disabled │ └──────────┴────┴──────┴──────┴────────┴─────────┴────────┴─────┴───────────┴───────┴──────────┘
Trong kết quả trước đó, bạn sẽ nhận thấy rằng chế độ watching
bị tắt. Đây là tính năng reload server khi bạn thực hiện thay đổi đối với các file ứng dụng nào. Nó hữu ích trong quá trình phát triển, nhưng ta không cần tính năng đó trong production .
Để biết thêm thông tin về bất kỳ quá trình nào đang chạy, hãy sử dụng lệnh pm2 show
, theo sau là ID của nó. Trong trường hợp này, ID là 0
:
- pm2 show 0
Đầu ra này hiển thị thời gian hoạt động, trạng thái, đường dẫn file log và thông tin khác về ứng dụng đang chạy:
OutputDescribing process with id 0 - 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
Nếu trạng thái ứng dụng hiển thị lỗi, bạn có thể sử dụng đường dẫn log lỗi để mở và xem lại log lỗi để gỡ lỗi:
- cat /home/tcp/.pm2/logs/server-error-0.log
Nếu bạn thực hiện thay đổi đối với mã server , bạn cần khởi động lại quy trình của ứng dụng để áp dụng các thay đổi, như sau:
- pm2 restart 0
PM2 hiện đang quản lý ứng dụng. Bây giờ ta sẽ sử dụng Nginx để gửi các yêu cầu proxy tới server .
Bước 4 - Cài đặt Nginx làm server Reverse Proxy
Ứng dụng của bạn đang chạy và nghe trên 127.0.0.1
, nghĩa là nó sẽ chỉ chấp nhận các kết nối từ máy local . Ta sẽ cài đặt Nginx làm Reverse Proxy để xử lý lưu lượng đến và chuyển trực tiếp đến server của ta .
Để thực hiện việc này, ta sẽ sửa đổi cấu hình Nginx để sử dụng các tính năng stream {}
và stream_proxy
của Nginx để chuyển tiếp các kết nối TCP tới server Node.js của ta .
Ta phải chỉnh sửa file cấu hình Nginx chính vì khối stream
cấu hình chuyển tiếp kết nối TCP chỉ hoạt động như một khối cấp cao nhất. Cấu hình Nginx mặc định trên Ubuntu tải các khối server trong khối http
của file và không thể đặt khối stream
trong khối đó.
Mở file /etc/nginx/nginx.conf
trong editor :
- sudo nano /etc/nginx/nginx.conf
Thêm các dòng sau vào cuối file cấu hình của bạn:
... stream { server { listen 3000; proxy_pass 127.0.0.1:7070; proxy_protocol on; } }
Điều này sẽ lắng nghe các kết nối TCP trên cổng 3000
và ủy quyền các yêu cầu tới server Node.js của bạn đang chạy trên cổng 7070
. Nếu ứng dụng của bạn được đặt để lắng nghe trên một cổng khác, hãy cập nhật cổng URL chuyển proxy thành số cổng chính xác. Chỉ thị proxy_protocol
yêu cầu Nginx sử dụng giao thức PROXY để gửi thông tin client đến các server backend , sau đó có thể xử lý thông tin đó nếu cần.
Lưu file và thoát khỏi editor .
Kiểm tra cấu hình Nginx của bạn đảm bảo bạn không mắc phải bất kỳ lỗi cú pháp nào:
- sudo nginx -t
Tiếp theo, khởi động lại Nginx để bật chức năng proxy TCP và UDP:
- sudo systemctl restart nginx
Tiếp theo, cho phép kết nối TCP với server của ta trên cổng đó. Sử dụng ufw
để cho phép kết nối trên cổng 3000
:
- sudo sudo ufw allow 3000
Giả sử rằng ứng dụng Node.js của bạn đang chạy và ứng dụng cũng như cấu hình Nginx của bạn là chính xác, bây giờ bạn có thể truy cập ứng dụng của bạn thông qua Reverse Proxy Nginx.
Bước 5 - Kiểm tra kết nối Máy khách- Server
Hãy kiểm tra server bằng cách kết nối với server TCP từ máy local của ta bằng tập lệnh client.js
. Để làm như vậy, bạn cần download file client.js
mà bạn đã phát triển xuống máy local của bạn và thay đổi cổng và địa chỉ IP trong tập lệnh.
Đầu tiên, trên máy local của bạn, hãy download file client.js
bằng cách sử dụng scp
:
- [environment local
- scp sammy@your_server_ip:~/tcp-nodejs-app/client.js client.js
Mở file client.js
trong editor :
- [environment local
- nano client.js
Thay đổi port
thành 3000
và thay đổi host
thành địa chỉ IP của server của bạn:
// A Client Example to connect to the Node.js TCP Server const net = require('net'); const client = new net.Socket(); const port = 3000; const host = 'your_server_ip'; ...
Lưu file , thoát khỏi editor và kiểm tra mọi thứ bằng cách chạy ứng dụng client :
- node client.js
Bạn sẽ thấy kết quả kết quả giống như khi bạn chạy nó trước đó, cho biết rằng client của bạn đã kết nối qua Nginx và đến server của bạn:
client.js OutputConnected Server Says : 127.0.0.1:34584 said PROXY TCP4 your_local_ip_address your_server_ip 52920 3000 Hello From Client your_local_ip_address
Vì Nginx đang ủy quyền các kết nối client tới server của bạn, server Node.js của bạn sẽ không thấy địa chỉ IP thực của các client ; nó sẽ chỉ thấy địa chỉ IP của Nginx. Nginx không hỗ trợ gửi địa chỉ IP thực đến chương trình backend trực tiếp mà không thực hiện một số thay đổi đối với hệ thống của bạn có thể ảnh hưởng đến bảo mật, nhưng vì ta đã bật giao thức PROXY trong Nginx, server Node.js hiện đang nhận được thông báo PROXY
bổ sung có chứa IP thực. Nếu bạn cần địa chỉ IP đó, bạn có thể điều chỉnh server của bạn để xử lý các yêu cầu PROXY
và phân tích dữ liệu bạn cần.
Như vậy, bạn có ứng dụng Node.js TCP của bạn chạy sau Reverse Proxy Nginx và có thể tiếp tục phát triển server của bạn hơn nữa.
Kết luận
Trong hướng dẫn này, bạn đã tạo một ứng dụng TCP với Node.js, chạy nó với PM2 và phục vụ nó sau Nginx. Bạn cũng đã tạo một ứng dụng client để kết nối với nó từ các máy khác. Bạn có thể sử dụng ứng dụng này để xử lý các stream dữ liệu lớn hoặc để xây dựng các ứng dụng nhắn tin thời gian thực.
Các tin liên quan
Cách thiết lập server VPN IKEv2 với StrongSwan trên Ubuntu 18.042018-07-16
Cách cấu hình BIND làm server DNS Mạng riêng trên Ubuntu 18.04
2018-07-06
Sử dụng Bộ định tuyến React 4 với Kết xuất phía server
2018-06-04
Cách cài đặt Linux, Nginx, MySQL, PHP ( LEMP) trên Ubuntu 18.04
2018-05-23
server Express cơ bản trong Node.js
2018-05-04
Thiết lập server ban đầu với Ubuntu 18.04
2018-04-27
Tự động thiết lập server ban đầu với Ubuntu 18.04
2018-04-27
Bắt đầu với kết xuất phía server bằng Nuxt.js
2018-04-16
Cách bảo vệ server của bạn trước lỗ hổng Meltdown và Spectre
2018-01-10
Sơ lược về lịch sử Linux
2017-10-27