Ubuntu16.04でPM2とNginxを使用してNode.jsTCPサーバーアプリケーションを開発する方法
序章
Node.js は、ChromeのV8Javascriptエンジン上に構築された人気のあるオープンソースのJavaScriptランタイム環境です。 Node.jsは、サーバー側およびネットワーキングアプリケーションの構築に使用されます。 TCP(伝送制御プロトコル)は、アプリケーション間でデータストリームの信頼性が高く、順序付けられ、エラーチェックされた配信を提供するネットワーキングプロトコルです。 TCPサーバーはTCP接続要求を受け入れることができ、接続が確立されると、両側でデータストリームを交換できます。
このチュートリアルでは、サーバーをテストするためのクライアントとともに、基本的なNode.jsTCPサーバーを構築します。 PM2 と呼ばれる強力なNode.jsプロセスマネージャーを使用して、サーバーをバックグラウンドプロセスとして実行します。 次に、 Nginx をTCPアプリケーションのリバースプロキシとして構成し、ローカルマシンからのクライアント/サーバー接続をテストします。
前提条件
このチュートリアルを完了するには、次のものが必要です。
- Ubuntu16.04初期サーバーセットアップガイドに従ってセットアップされた1つのUbuntu16.04サーバー。これには、sudo非rootユーザーとファイアウォールが含まれます。
- Ubuntu 16.04にNginxをインストールする方法に示すように、サーバーにNginxがインストールされています。 Nginxは次のコマンドでコンパイルする必要があります
--with-stream
オプション。これは、Nginxの新規インストールのデフォルトです。apt
Ubuntu16.04のパッケージマネージャー。 - Ubuntu 16.04にNode.jsをインストールする方法で説明されているように、Node.jsは公式PPAを使用してインストールされます。
ステップ1—Node.jsTCPアプリケーションを作成する
TCPソケットを使用してNode.jsアプリケーションを作成します。 これは、Node.jsの Net ライブラリを理解するのに役立つサンプルアプリケーションであり、生のTCPサーバーおよびクライアントアプリケーションを作成できます。
まず、Node.jsアプリケーションを配置するディレクトリをサーバー上に作成します。 このチュートリアルでは、アプリケーションを作成します ~/tcp-nodejs-app
ディレクトリ:
- mkdir ~/tcp-nodejs-app
次に、新しいディレクトリに切り替えます。
- cd ~/tcp-nodejs-app
名前の付いた新しいファイルを作成します package.json
あなたのプロジェクトのために。 このファイルには、アプリケーションが依存するパッケージがリストされています。 このファイルを作成すると、この依存関係のリストを他の開発者と共有しやすくなるため、ビルドの再現性が高まります。
- nano package.json
を生成することもできます package.json
を使用して npm init
コマンド。アプリケーションの詳細を入力するように求められますが、スタートアップコマンドなど、ファイルを手動で変更して追加する必要があります。 したがって、このチュートリアルではファイルを手動で作成します。
次のJSONをファイルに追加します。これは、アプリケーションの名前、バージョン、メインファイル、アプリケーションを起動するコマンド、およびソフトウェアライセンスを指定します。
{
"name": "tcp-nodejs-app",
"version": "1.0.0",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"license": "MIT"
}
The scripts
フィールドでは、アプリケーションのコマンドを定義できます。 ここで指定した設定により、を実行してアプリを実行できます npm start
走る代わりに node server.js
.
The package.json
ファイルには、ランタイムと開発の依存関係のリストを含めることもできますが、このアプリケーションのサードパーティの依存関係はありません。
これで、プロジェクトディレクトリと package.json
セットアップして、サーバーを作成しましょう。
アプリケーションディレクトリで、 server.js
ファイル:
- nano server.js
Node.jsはと呼ばれるモジュールを提供します net
これにより、TCPサーバーとクライアントの通信が可能になります。 をロードします net
モジュール付き require()
次に、サーバーのポートとホストを保持する変数を定義します。
const net = require('net');
const port = 7070;
const host = '127.0.0.1';
ポートを使用します 7070
このアプリの場合ですが、使用可能な任意のポートを使用できます。 使用しています 127.0.0.1
のために HOST
これにより、サーバーがローカルネットワークインターフェイスでのみリッスンするようになります。 後で、リバースプロキシとしてこのアプリの前にNginxを配置します。 Nginxは、複数の接続と水平スケーリングの処理に精通しています。
次に、このコードを追加して、 createServer()
からの機能 net
モジュール。 次に、を使用して定義したポートとホストで接続のリッスンを開始します。 listen()
の機能 net
モジュール:
...
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
これらの機能を実装するためのファイル:
...
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
アレイなので、ブロードキャストしなくなります。 接続ブロックの最後に次のコードを追加します。
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
:
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—Node.jsTCPクライアントを作成する
Node.js TCPサーバーが実行されているので、サーバーに接続してサーバーをテストするためのTCPクライアントを作成しましょう。
作成したNode.jsサーバーはまだ実行中であり、現在のターミナルセッションをブロックしています。 クライアントを開発する間、それを実行し続けたいので、新しいターミナルウィンドウまたはタブを開きます。 次に、新しいタブからサーバーに再度接続します。
- ssh sammy@your_server_ip
接続したら、に移動します tcp-nodejs-app
ディレクトリ:
- cd tcp-nodejs-app
同じディレクトリに、という新しいファイルを作成します client.js
:
- nano client.js
クライアントは同じものを使用します net
で使用されるライブラリ server.js
TCPサーバーに接続するファイル。 このコードをファイルに追加して、IPアドレスを使用してサーバーに接続します 127.0.0.1
ポートで 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);
});
このコードは、最初にTCPサーバーへの接続を試みて、作成したサーバーが実行されていることを確認します。 接続が確立されると、クライアントは送信します "Hello From Client " + client.address().address
を使用してサーバーに client.write
関数。 サーバーはこのデータを受信し、クライアントにエコーバックします。
クライアントがサーバーからデータを受信したら、サーバーの応答を出力する必要があります。 このコードを追加して、 data
イベントを実行し、コマンドラインに対するサーバーの応答を出力します。
client.on('data', function(data) {
console.log('Server Says : ' + data);
});
最後に、次のコードを追加して、サーバーからの切断を適切に処理します。
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を使用してサーバーを実行し、バックグランドで実行され、正常に再起動できるようにします。
まず、を使用してサーバーに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のsystemdスタートアップスクリプトを生成してインストールします。 必ずこれを実行してください sudo
そのため、systemdファイルは自動的にインストールされます。
- 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
リストにアプリケーションが表示され、IDは次のようになります。 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 │
└──────────┴────┴──────┴──────┴────────┴─────────┴────────┴─────┴───────────┴───────┴──────────┘
前の出力では、次のことに気付くでしょう。 watching
無効になっています。 これは、アプリケーションファイルに変更を加えたときにサーバーをリロードする機能です。 開発には役立ちますが、本番環境ではその機能は必要ありません。
実行中のプロセスに関する詳細情報を取得するには、 pm2 show
コマンドの後にIDが続きます。 この場合、IDは 0
:
- pm2 show 0
この出力には、稼働時間、ステータス、ログファイルパス、および実行中のアプリケーションに関するその他の情報が表示されます。
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
アプリケーションのステータスにエラーが表示された場合は、エラーログパスを使用してエラーログを開き、確認してエラーをデバッグできます。
- cat /home/tcp/.pm2/logs/server-error-0.log
サーバーコードに変更を加えた場合は、次のように、アプリケーションのプロセスを再起動して変更を適用する必要があります。
- pm2 restart 0
PM2は現在アプリケーションを管理しています。 次に、Nginxを使用してリクエストをサーバーにプロキシします。
ステップ4—Nginxをリバースプロキシサーバーとして設定する
アプリケーションが実行され、リッスンしています 127.0.0.1
、これは、ローカルマシンからの接続のみを受け入れることを意味します。 Nginxをリバースプロキシとして設定し、着信トラフィックを処理してサーバーに転送します。
これを行うには、Nginxの stream{}およびstream_proxy機能を使用してTCP接続をNode.jsサーバーに転送するように、Nginx構成を変更します。
メインのNginx構成ファイルを次のように編集する必要があります stream
TCP接続転送を構成するブロックは、最上位ブロックとしてのみ機能します。 UbuntuのデフォルトのNginx構成は、サーバーブロックを http
ファイルのブロック、および stream
ブロックをそのブロック内に配置することはできません。
ファイルを開く /etc/nginx/nginx.conf
エディターで:
- sudo nano /etc/nginx/nginx.conf
構成ファイルの最後に次の行を追加します。
...
stream {
server {
listen 3000;
proxy_pass 127.0.0.1:7070;
proxy_protocol on;
}
}
これは、ポートでTCP接続をリッスンします 3000
ポートで実行されているNode.jsサーバーにリクエストをプロキシします 7070
. アプリケーションが別のポートでリッスンするように設定されている場合は、プロキシパスのURLポートを正しいポート番号に更新します。 The proxy_protocol
ディレクティブは、 PROXYプロトコルを使用してクライアント情報をバックエンドサーバーに送信するようにNginxに指示します。バックエンドサーバーは、必要に応じてその情報を処理できます。
ファイルを保存して、エディターを終了します。
Nginxの構成をチェックして、構文エラーが発生していないことを確認します。
- sudo nginx -t
次に、Nginxを再起動して、TCPおよびUDPプロキシ機能を有効にします。
- sudo systemctl restart nginx
次に、そのポートでサーバーへのTCP接続を許可します。 使用する ufw
ポートでの接続を許可する 3000
:
- sudo sudo ufw allow 3000
Node.jsアプリケーションが実行されていて、アプリケーションとNginxの構成が正しいと仮定すると、Nginxリバースプロキシを介してアプリケーションにアクセスできるようになります。
ステップ5—クライアント/サーバー接続のテスト
ローカルマシンからTCPサーバーに接続して、サーバーをテストしてみましょう。 client.js
脚本。 そのためには、をダウンロードする必要があります client.js
ローカルマシンに開発したファイルを作成し、スクリプトのポートとIPアドレスを変更します。
まず、ローカルマシンで、 client.js
を使用してファイル scp
:
- [environment local
- scp sammy@your_server_ip:~/tcp-nodejs-app/client.js client.js
を開きます client.js
エディター内のファイル:
- [environment local
- nano client.js
変更 port
に 3000
を変更します host
サーバーのIPアドレスに:
// 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';
...
ファイルを保存し、エディターを終了し、クライアントを実行してテストします。
- node client.js
以前に実行したときと同じ出力が表示されます。これは、クライアントマシンがNginxを介して接続し、サーバーに到達したことを示しています。
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
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の背後で提供しました。 また、他のマシンから接続するためのクライアントアプリケーションを作成しました。 このアプリケーションを使用して、データストリームの大きなチャンクを処理したり、リアルタイムメッセージングアプリケーションを構築したりできます。