NodeJS 速習チュートリアル

Node.js Net モジュール

1. Net モジュール入門

Net モジュールは Node.js のコアネットワークモジュールの 1 つであり、TCP サーバーとクライアントの作成を可能にします。TCP(Transmission Control Protocol)は、ネットワーク上のデバイス間で実行されるアプリケーション間のバイトストリームを、信頼性が高く、順序が保証され、エラーチェックが行われた状態で配信するプロトコルです。

HTTP モジュールが Net モジュールの上に構築されているのに対し、Net モジュールはより低レイヤーなネットワーク機能を提供し、通信プロトコルをより詳細に制御できます。

       注意: Net モジュールは、独自の TCP プロトコルが必要な場合や、TCP 上に独自のアプリケーションレイヤープロトコルを実装したい場合に最適です。

1.1 Net モジュールのインポート

Net モジュールを使用するには、Node.js アプリケーションで以下のようにインポートします。

const net = require('net');

2. TCP サーバーの作成

Net モジュールを使用すると、接続をリッスンする TCP サーバーを簡単に作成できます。

const net = require('net');

// TCP サーバーを作成
const server = net.createServer((socket) => {
  console.log('クライアントが接続されました');
  
  // Buffer オブジェクトの代わりに文字列を受け取るようエンコーディングを設定
  socket.setEncoding('utf8');
  
  // クライアントからのデータを処理
  socket.on('data', (data) => {
    console.log(`クライアントからの受信: ${data}`);
    
    // クライアントにデータをエコーバック(送り返す)
    socket.write(`エコー: ${data}`);
  });
  
  // クライアントの切断を処理
  socket.on('end', () => {
    console.log('クライアントが切断されました');
  });
  
  // エラー処理
  socket.on('error', (err) => {
    console.error('ソケットエラー:', err);
  });
  
  // クライアントにウェルカムメッセージを送信
  socket.write('TCP サーバーへようこそ!\r\n');
});

// サーバーを起動し、ポート 8080 でリッスン
server.listen(8080, () => {
  console.log('TCP サーバーがポート 8080 で稼働中です');
});

この例のポイント:

  • net.createServer() が新しい TCP サーバーを作成します。
  • クライアントが接続されると、コールバック関数が実行されます。
  • socket オブジェクトがクライアントへの接続を表します。
  • dataenderror イベントのイベントハンドラを設定しています。
  • server.listen(8080) でポート 8080 でサーバーを開始します。

3. TCP クライアントの作成

TCP サーバーに接続するための TCP クライアントも作成可能です。

const net = require('net');

// TCP クライアントを作成
const client = net.createConnection({ port: 8080 }, () => {
  console.log('サーバーに接続しました');
  
  // サーバーにメッセージを送信
  client.write('クライアントからのこんにちは!');
});

// エンコーディングを設定
client.setEncoding('utf8');

// サーバーからのデータを処理
client.on('data', (data) => {
  console.log(`サーバーからの受信: ${data}`);
  
  // さらにメッセージを送信
  client.write('クライアントからの追加データ');
});

// 接続の終了を処理
client.on('end', () => {
  console.log('サーバーから切断されました');
});

// エラー処理
client.on('error', (err) => {
  console.error('接続エラー:', err);
});

       注意: クライアントとサーバーを一緒にテストするには、一方のターミナルでサーバーのスクリプトを実行し、別のターミナルでクライアントのスクリプトを実行してください。

4. Socket オブジェクトのプロパティとメソッド

サーバーの接続コールバックに渡される、または createConnection() によって返される Socket オブジェクトには、便利なプロパティとメソッドが多数あります。

プロパティ/メソッド説明
socket.write(data[, encoding][, callback])指定したエンコーディングでデータを書き込む
socket.end([data][, encoding][, callback])すべてのデータを書き込んだ後、ソケットを閉じる
socket.setEncoding(encoding)受信データのエンコーディングを設定する
socket.setTimeout(timeout[, callback])指定ミリ秒の無操作でタイムアウトするように設定する
socket.setKeepAlive([enable][, initialDelay])Keep-alive 機能を有効/無効にする
socket.address()接続のアドレス、ファミリー、ポートを含むオブジェクトを返す
socket.remoteAddressクライアントの IP アドレス(文字列)
socket.remotePortクライアントのポート(数値)
socket.localAddressサーバーがリッスンしているローカル IP アドレス
socket.localPortサーバーがリッスンしているローカルポート
socket.bytesRead受信したバイト数
socket.bytesWritten送信したバイト数

5. Server オブジェクトのプロパティとメソッド

createServer() によって返される Server オブジェクトには、以下の便利な機能があります。

プロパティ/メソッド説明
server.listen(port[, hostname][, backlog][, callback])接続のリッスンを開始する
server.close([callback])新しい接続の受け付けを停止する
server.address()サーバーのアドレス情報を返す
server.maxConnections接続数がこれを超えた場合に接続を拒否する設定
server.connections現在の同時接続数
server.listeningサーバーがリッスン中かどうかを示す真偽値

6. チャットサーバーの構築

接続されているすべてのクライアントにメッセージをブロードキャストする、シンプルなチャットサーバーを作成してみましょう。

const net = require('net');

// すべてのクライアント接続を保持
const clients = [];

// チャットサーバーを作成
const server = net.createServer((socket) => {
  // クライアント ID を生成
  const clientId = `${socket.remoteAddress}:${socket.remotePort}`;
  console.log(`クライアントが接続されました: ${clientId}`);
  
  socket.setEncoding('utf8');
  
  // クライアントをリストに追加
  clients.push(socket);
  
  // ウェルカムメッセージを送信
  socket.write(`チャットサーバーへようこそ! 現在 ${clients.length} 人のユーザーがオンラインです。\r\n`);
  
  // 送信者以外のすべてのクライアントにメッセージをブロードキャスト
  function broadcast(message, sender) {
    clients.forEach(client => {
      if (client !== sender) {
        client.write(message);
      }
    });
  }
  
  // 新しい接続を全員に通知
  broadcast(`ユーザー ${clientId} がチャットに参加しました。\r\n`, socket);
  
  // クライアントのメッセージを処理
  socket.on('data', (data) => {
    console.log(`${clientId}: ${data.trim()}`);
    
    // 他のすべてのクライアントにメッセージを配信
    broadcast(`${clientId}: ${data}`, socket);
  });
  
  // クライアントの切断を処理
  socket.on('end', () => {
    console.log(`クライアントが切断されました: ${clientId}`);
    
    // リストからクライアントを削除
    const index = clients.indexOf(socket);
    if (index !== -1) {
      clients.splice(index, 1);
    }
    
    // 切断を全員に通知
    broadcast(`ユーザー ${clientId} がチャットを退出しました。\r\n`, null);
  });
  
  // エラー処理
  socket.on('error', (err) => {
    console.error(`${clientId} からのソケットエラー:`, err);
  });
});

// サーバーを起動
const PORT = 8080;
server.listen(PORT, () => {
  console.log(`チャットサーバーがポート ${PORT} で稼働中です`);
});

server.on('error', (err) => {
  console.error('サーバーエラー:', err);
});

このチャットサーバーに接続するには、TCP クライアントまたは telnet のようなツールを使用できます。
telnet localhost 8080

7. 独自のプロトコルを構築する

Net モジュールを使用する利点の 1 つは、独自のアプリケーションプロトコルを作成できることです。シンプルな JSON ベースのプロトコルを作成してみましょう。

const net = require('net');

// JSON ベースのプロトコルをサポートするサーバー
const server = net.createServer((socket) => {
  console.log('クライアントが接続されました');
  
  // 受信データ用のバッファ
  let buffer = '';
  
  socket.on('data', (data) => {
    // 新しいデータをバッファに追加
    buffer += data.toString();
    
    // 完全なメッセージ(改行区切り)を処理
    let boundary = buffer.indexOf('\n');
    while (boundary !== -1) {
      // メッセージを抽出
      const message = buffer.substring(0, boundary);
      buffer = buffer.substring(boundary + 1);
      
      try {
        const parsedMessage = JSON.parse(message);
        console.log('受信したメッセージ:', parsedMessage);
        
        // メッセージタイプに応じた処理
        switch (parsedMessage.type) {
          case 'greeting':
            socket.write(JSON.stringify({
              type: 'welcome',
              message: `こんにちは、${parsedMessage.name}さん!`,
              timestamp: Date.now()
            }) + '\n');
            break;
            
          case 'query':
            socket.write(JSON.stringify({
              type: 'response',
              queryId: parsedMessage.queryId,
              result: handleQuery(parsedMessage.query),
              timestamp: Date.now()
            }) + '\n');
            break;
            
          default:
            socket.write(JSON.stringify({
              type: 'error',
              message: '不明なメッセージタイプです',
              timestamp: Date.now()
            }) + '\n');
        }
      } catch (err) {
        console.error('メッセージ処理エラー:', err);
        socket.write(JSON.stringify({
          type: 'error',
          message: '無効な JSON 形式です',
          timestamp: Date.now()
        }) + '\n');
      }
      
      boundary = buffer.indexOf('\n');
    }
  });
  
  socket.on('end', () => console.log('クライアントが切断されました'));
  socket.on('error', (err) => console.error('ソケットエラー:', err));
});

function handleQuery(query) {
  if (query === 'time') return { time: new Date().toISOString() };
  if (query === 'stats') return { uptime: process.uptime(), platform: process.platform };
  return { error: '不明なクエリです' };
}

server.listen(8080, () => console.log('JSON プロトコルサーバーがポート 8080 で稼働中'));

8. Socket タイムアウトの処理

非アクティブな接続を処理するために、ソケットにタイムアウトを設定できます。

const net = require('net');

const server = net.createServer((socket) => {
  console.log('クライアントが接続されました');
  
  // 10秒のタイムアウトを設定
  socket.setTimeout(10000);
  
  // タイムアウト発生時の処理
  socket.on('timeout', () => {
    console.log('ソケットタイムアウト');
    socket.write('長時間操作がなかったため、切断します...\r\n');
    socket.end();
  });
  
  socket.on('data', (data) => {
    console.log(`受信: ${data.toString().trim()}`);
    socket.write(`エコー: ${data}`);
  });
});

server.listen(8080, () => console.log('タイムアウト付きサーバーがポート 8080 で稼働中'));

9. IPC (プロセス間通信) の活用

Net モジュールは、Unix ドメインソケットや Windows の名前付きパイプを使用して、IPC(Inter-Process Communication)サーバーとクライアントを作成することもできます。

const net = require('net');
const path = require('path');
const fs = require('fs');

// IPC ソケットのパスを定義
const socketPath = path.join(__dirname, 'ipc-socket');

// IPC サーバーを作成
const server = net.createServer((socket) => {
  console.log('IPC サーバーに接続されました');
  
  socket.on('data', (data) => {
    console.log(`IPC 経由で受信: ${data.toString().trim()}`);
    socket.write(`エコー: ${data}`);
  });
});

// IPC サーバーを起動
server.listen(socketPath, () => {
  console.log(`IPC サーバーが ${socketPath} で稼働中です`);
});

// サーバー終了時にソケットファイルをクリーンアップ
server.on('close', () => {
  if (fs.existsSync(socketPath)) fs.unlinkSync(socketPath);
});

// プロセス終了シグナルの処理
process.on('SIGINT', () => {
  server.close(() => process.exit(0));
});

       注意: IPC 接続はネットワークスタックを使用せず、ローカルマシン内に制限されるため、一般的に TCP 接続よりも高速で安全です。

10. ベストプラクティス

  • エラーハンドリング: アプリケーションのクラッシュを防ぐため、常にソケットエラーを処理してください。
  • タイムアウト: リソースリークを防ぐため、非アクティブな接続にはタイムアウトを実装してください。
  • Keep-Alive: 長時間の接続では Keep-alive を使用して、切断を検知できるようにします。
  • バッファリング: メッセージの一部のみが届く場合に備え、適切なフレーミングとバッファリングをプロトコルに実装してください。
  • 接続制限: サーバーの過負荷を防ぐため、server.maxConnections を設定してください。
  • バックプレッシャー: クライアントの処理が追いつかない場合に備え、socket.write() の戻り値を確認してバックプレッシャーを制御してください。

11. Net モジュール vs HTTP モジュール

機能Net モジュールHTTP モジュール
プロトコル生の TCP/IPHTTP プロトコル
メッセージ形式任意(開発者が定義)HTTP リクエスト/レスポンス
抽象化レベル低レイヤー(制御性が高い)高レイヤー(使いやすい)
ユースケースカスタムプロトコル、パフォーマンス重視Web アプリ、REST API

12. まとめ

Node.js の Net モジュールは、TCP サーバーとクライアントを作成するための強力なツールを提供します。これには以下の機能が含まれます:

  • カスタムプロトコルのための低レベルソケットアクセス
  • 非同期かつイベント駆動の API
  • TCP/IP と IPC 通信の両方をサポート
  • 高レイヤープロトコルの構成要素

HTTP モジュールに比べて手動での作業が増えますが、Net モジュールはアプリケーションに最適なプロトコルを正確に実装できる柔軟性を提供します。