著者は、 Write forDOnationsプログラムの一環として寄付を受け取るためにOSMIを選択しました。

序章

Node.js は、ChromeのV8Javascriptエンジン上に構築された人気のあるオープンソースのJavaScriptランタイム環境です。 Node.jsは、サーバー側およびネットワークアプリケーションの構築に使用されます。 TCP(伝送制御プロトコル)は、アプリケーション間でデータストリームの信頼性が高く、順序付けられ、エラーチェックされた配信を提供するネットワークプロトコルです。 TCPサーバーはTCP接続要求を受け入れることができ、接続が確立されると、両側でデータストリームを交換できます。

このチュートリアルでは、サーバーをテストするためのクライアントとともに、基本的なNode.jsTCPサーバーを構築します。 PM2 と呼ばれる強力なNode.jsプロセスマネージャーを使用して、サーバーをバックグラウンドプロセスとして実行します。 次に、 Nginx をTCPアプリケーションのリバースプロキシとして構成し、ローカルマシンからのクライアント/サーバー接続をテストします。

前提条件

このチュートリアルを完了するには、次のものが必要です。

ステップ1—Node.jsTCPアプリケーションを作成する

TCPソケットを使用してNode.jsアプリケーションを作成します。 これは、Node.jsの Net ライブラリを理解するのに役立つサンプルアプリケーションであり、生のTCPサーバーおよびクライアントアプリケーションを作成できます。

まず、Node.jsアプリケーションを配置するディレクトリをサーバー上に作成します。 このチュートリアルでは、~/tcp-nodejs-appディレクトリにアプリケーションを作成します。

  1. mkdir ~/tcp-nodejs-app

次に、新しいディレクトリに切り替えます。

  1. cd ~/tcp-nodejs-app

プロジェクト用にpackage.jsonという名前の新しいファイルを作成します。 このファイルには、アプリケーションが依存するパッケージがリストされています。 このファイルを作成すると、この依存関係のリストを他の開発者と共有しやすくなるため、ビルドの再現性が高まります。

  1. nano package.json

npm initコマンドを使用してpackage.jsonを生成することもできます。これにより、アプリケーションの詳細を入力するよう求められますが、ファイルを手動で変更して、起動コマンド。 したがって、このチュートリアルではファイルを手動で作成します。

次のJSONをファイルに追加します。これは、アプリケーションの名前、バージョン、メインファイル、アプリケーションを起動するコマンド、およびソフトウェアライセンスを指定します。

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

scriptsフィールドでは、アプリケーションのコマンドを定義できます。 ここで指定した設定では、node server.jsを実行する代わりに、npm startを実行してアプリを実行できます。

package.jsonファイルには、ランタイムと開発の依存関係のリストを含めることもできますが、このアプリケーションのサードパーティの依存関係はありません。

プロジェクトディレクトリとpackage.jsonの設定が完了したので、サーバーを作成しましょう。

アプリケーションディレクトリに、server.jsファイルを作成します。

  1. nano server.js

Node.jsは、TCPサーバーとクライアントの通信を可能にするnetと呼ばれるモジュールを提供します。 netモジュールにrequire()をロードし、サーバーのポートとホストを保持する変数を定義します。

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

このアプリではポート7070を使用しますが、使用可能な任意のポートを使用できます。 HOST127.0.0.1を使用しているため、サーバーはローカルネットワークインターフェイスでのみリッスンしています。 後で、リバースプロキシとしてこのアプリの前にNginxを配置します。 Nginxは、複数の接続と水平スケーリングの処理に精通しています。

次に、このコードを追加して、net モジュールのcreateServer()関数を使用してTCPサーバーを生成します。 次に、net モジュールのlisten()機能を使用して、定義したポートとホストで接続のリッスンを開始します。

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

server.jsを保存し、サーバーを起動します。

  1. npm start

次の出力が表示されます。

Output
TCP Server is running on port 7070

TCPサーバーはポート7070で実行されています。 CTRL+Cを押してサーバーを停止します。

サーバーがリッスンしていることがわかったので、クライアント接続を処理するコードを記述しましょう。

クライアントがサーバーに接続すると、サーバーはconnectionイベントをトリガーします。これを確認します。 接続されたクライアントの配列を定義します。これをsocketsと呼び、クライアントが接続したときに各クライアントインスタンスをこの配列に追加します。

dataイベントを使用して、接続されたクライアントからのデータストリームを処理し、socketsアレイを使用して、接続されているすべてのクライアントにデータをブロードキャストします。

次の機能を実装するには、次のコードをserver.jsファイルに追加します。

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アレイからクライアントを削除して、ブロードキャストしないようにします。 接続ブロックの最後に次のコードを追加します。

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

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

ファイルを保存してから、サーバーを再起動します。

  1. npm start

マシン上で完全に機能するTCPサーバーが実行されています。 次に、サーバーに接続するクライアントを作成します。

ステップ2—Node.jsTCPクライアントを作成する

Node.js TCPサーバーが実行されているので、サーバーに接続してサーバーをテストするためのTCPクライアントを作成しましょう。

作成したNode.jsサーバーはまだ実行中であり、現在のターミナルセッションをブロックしています。 クライアントを開発する間、それを実行し続けたいので、新しいターミナルウィンドウまたはタブを開きます。 次に、新しいタブからサーバーに再度接続します。

  1. ssh sammy@your_server_ip

接続したら、tcp-nodejs-appディレクトリに移動します。

  1. cd tcp-nodejs-app

同じディレクトリに、client.jsという名前の新しいファイルを作成します。

  1. nano client.js

クライアントは、server.jsファイルで使用されているのと同じnetライブラリを使用してTCPサーバーに接続します。 このコードをファイルに追加して、ポート7070のIPアドレス127.0.0.1を使用してサーバーに接続します。

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

このコードは、最初にTCPサーバーへの接続を試みて、作成したサーバーが実行されていることを確認します。 接続が確立されると、クライアントはclient.write機能を使用して "Hello From Client " + client.address().addressをサーバーに送信します。 サーバーはこのデータを受信し、クライアントにエコーバックします。

クライアントがサーバーからデータを受信したら、サーバーの応答を出力する必要があります。 次のコードを追加して、dataイベントをキャッチし、コマンドラインに対するサーバーの応答を出力します。

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

最後に、次のコードを追加して、サーバーからの切断を適切に処理します。

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

client.jsファイルを保存します。

次のコマンドを実行して、クライアントを起動します。

  1. node client.js

接続が確立され、サーバーがデータを受信して、クライアントにエコーバックします。

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

サーバーが実行されているターミナルに戻ると、次の出力が表示されます。

server.js Output
CONNECTED: 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を使用してサーバーを実行し、バックグランドで実行され、正常に再起動できるようにします。

まず、npmを使用してサーバーにPM2をグローバルにインストールします。

  1. sudo npm install pm2 -g

PM2をインストールしたら、それを使用してサーバーを実行します。 npm startを実行してサーバーを起動する代わりに、pm2コマンドを使用します。 サーバーを起動します。

  1. 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ファイルが自動的にインストールされるようにしてください。

  1. 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 listコマンドを使用して、PM2が管理しているすべてのプロセスを一覧表示できます。

  1. 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です。

  1. pm2 show 0

この出力には、稼働時間、ステータス、ログファイルパス、および実行中のアプリケーションに関するその他の情報が表示されます。

Output
Describing 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

アプリケーションのステータスにエラーが表示された場合は、エラーログパスを使用してエラーログを開き、確認してエラーをデバッグできます。

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

サーバーコードに変更を加えた場合は、次のように、アプリケーションのプロセスを再起動して変更を適用する必要があります。

  1. pm2 restart 0

PM2は現在アプリケーションを管理しています。 次に、Nginxを使用してリクエストをサーバーにプロキシします。

ステップ4—Nginxをリバースプロキシサーバーとして設定する

アプリケーションは127.0.0.1で実行およびリッスンしています。つまり、ローカルマシンからの接続のみを受け入れます。 Nginxをリバースプロキシとして設定し、着信トラフィックを処理してサーバーに転送します。

これを行うには、Nginxの stream{}およびstream_proxy機能を使用してTCP接続をNode.jsサーバーに転送するように、Nginx構成を変更します。

TCP接続転送を構成するstreamブロックはトップレベルのブロックとしてのみ機能するため、メインのNginx構成ファイルを編集する必要があります。 UbuntuのデフォルトのNginx構成は、ファイルのhttpブロック内にサーバーブロックをロードし、streamブロックをそのブロック内に配置することはできません。

エディターでファイル/etc/nginx/nginx.confを開きます。

  1. sudo nano /etc/nginx/nginx.conf

構成ファイルの最後に次の行を追加します。

/etc/nginx/nginx.conf

...

stream {
    server {
      listen 3000;
      proxy_pass 127.0.0.1:7070;        
      proxy_protocol on;
    }
}

これは、ポート3000でTCP接続をリッスンし、ポート7070で実行されているNode.jsサーバーにリクエストをプロキシします。 アプリケーションが別のポートでリッスンするように設定されている場合は、プロキシパスのURLポートを正しいポート番号に更新します。 proxy_protocolディレクティブは、 PROXYプロトコルを使用してクライアント情報をバックエンドサーバーに送信するようにNginxに指示します。バックエンドサーバーは、必要に応じてその情報を処理できます。

ファイルを保存して、エディターを終了します。

Nginx構成をチェックして、構文エラーが発生していないことを確認します。

  1. sudo nginx -t

次に、Nginxを再起動して、TCPおよびUDPプロキシ機能を有効にします。

  1. sudo systemctl restart nginx

次に、そのポートでサーバーへのTCP接続を許可します。 ufwを使用して、ポート3000での接続を許可します。

  1. sudo sudo ufw allow 3000

Node.jsアプリケーションが実行されていて、アプリケーションとNginxの構成が正しいと仮定すると、Nginxリバースプロキシを介してアプリケーションにアクセスできるようになります。

ステップ5—クライアント/サーバー接続のテスト

client.jsスクリプトを使用して、ローカルマシンからTCPサーバーに接続して、サーバーをテストしてみましょう。 そのためには、開発したclient.jsファイルをローカルマシンにダウンロードし、スクリプトのポートとIPアドレスを変更する必要があります。

まず、ローカルマシンで、scpを使用してclient.jsファイルをダウンロードします。

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

エディタでclient.jsファイルを開きます。

  1. [environment local
  2. nano client.js

port3000に変更し、hostをサーバーのIPアドレスに変更します。

client.js
// 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';
...

ファイルを保存し、エディターを終了し、クライアントを実行してテストします。

  1. node client.js

以前に実行したときと同じ出力が表示されます。これは、クライアントマシンがNginxを介して接続し、サーバーに到達したことを示しています。

client.js Output
Connected 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サーバーは追加の[を受信しています。 X249X]実際のIPを含むメッセージ。 そのIPアドレスが必要な場合は、PROXYリクエストを処理し、必要なデータを解析するようにサーバーを調整できます。

これで、Node.js TCPアプリケーションがNginxリバースプロキシの背後で実行され、サーバーの開発をさらに進めることができます。

結論

このチュートリアルでは、Node.jsを使用してTCPアプリケーションを作成し、PM2を使用して実行し、Nginxの背後で提供しました。 また、他のマシンから接続するためのクライアントアプリケーションを作成しました。 このアプリケーションを使用して、データストリームの大きなチャンクを処理したり、リアルタイムメッセージングアプリケーションを構築したりできます。