Node.js net.Serverで学ぶネットワークプログラミング:基礎から応用まで

2025-04-26

net.Serverの主な役割

  • 接続管理
    接続されたクライアントの管理や、接続の終了処理を行います。
  • イベント処理
    接続、データ受信、エラーなどのイベントを処理するためのイベントリスナーを登録できます。
  • 接続の受付
    クライアントからの接続要求を待ち受け、接続が確立されると、新しいnet.Socketオブジェクトを生成します。

new net.Server() の使い方

new net.Server()は、net.Serverクラスのインスタンスを生成します。このインスタンスは、サーバーの動作を制御するためのメソッドやプロパティを持っています。

基本的な使用方法は以下の通りです。

  1. const net = require('net');
    
  2. net.Serverインスタンスを生成する

    const server = net.createServer();
    
  3. イベントリスナーを登録する

    • 'connection'イベント: クライアントが接続されたときに発生します。
    • 'listening'イベント: サーバーが指定されたポートで接続を待ち受け始めたときに発生します。
    • 'error'イベント: サーバーでエラーが発生したときに発生します。
    • 'close'イベント: サーバーが閉じた時に発生します。
    server.on('connection', (socket) => {
      console.log('クライアントが接続しました。');
    
      socket.on('data', (data) => {
        console.log('クライアントからデータを受信しました:', data.toString());
        socket.write('サーバーからの応答: ' + data.toString());
      });
    
      socket.on('end', () => {
        console.log('クライアントが切断しました。');
      });
    });
    
    server.on('listening', () => {
      console.log('サーバーがポート3000で接続を待ち受け始めました。');
    });
    
    server.on('error', (err) => {
      console.error('サーバーエラー:', err);
    });
    
  4. サーバーを起動する

    server.listen(3000, () => {
      console.log('サーバー起動');
    });
    

例の解説

  • server.listen(3000, ...)で、サーバーをポート3000で起動します。
  • socket.on('end', ...)で、クライアントが切断したときの処理を定義します。
  • socket.on('data', ...)で、クライアントからデータを受信したときの処理を定義します。
  • server.on('connection', ...)で、クライアントが接続されたときの処理を定義します。接続されたクライアントとの通信は、socketオブジェクトを通じて行います。
  • net.createServer()でサーバーインスタンスを作成します。


一般的なエラーとトラブルシューティング

    • エラー
      Error: listen EADDRINUSE: address already in use :::3000 のように、EADDRINUSE エラーが発生する。
    • 原因
      指定したポートがすでに別のプロセスで使用されている。
    • 解決策
      • 別のポートを使用する。
      • netstat (Windows) または lsof (macOS/Linux) コマンドを使用して、どのプロセスがポートを使用しているかを確認し、必要に応じてそのプロセスを終了する。
      • サーバーを終了した後に、すぐに再起動しようとすると、OSがポートを解放するのに時間がかかる場合があります。少し待ってから再起動してみてください。
  1. ファイアウォールによる接続拒否 (Firewall Blocking)

    • エラー
      クライアントがサーバーに接続できない。
    • 原因
      ファイアウォールがサーバーのポートへの接続をブロックしている。
    • 解決策
      • ファイアウォール設定で、サーバーのポートへの接続を許可するルールを追加する。
      • 一時的にファイアウォールを無効にして、問題がファイアウォールに関連しているかどうかを確認する。
  2. ネットワークインターフェースの問題 (Network Interface Issues)

    • エラー
      Error: listen EADDRNOTAVAIL のように、EADDRNOTAVAIL エラーが発生する。
    • 原因
      指定したIPアドレスが利用できない、またはネットワークインターフェースがダウンしている。
    • 解決策
      • server.listen() の第二引数に、利用可能なIPアドレスを指定する。server.listen(3000, '127.0.0.1') のように。
      • 0.0.0.0 を使用して、すべての利用可能なインターフェースでリッスンする。
      • ネットワーク接続を確認し、必要に応じて再起動する。
  3. connection イベントが発生しない

    • エラー
      クライアントが接続しても、connection イベントが発火しない。
    • 原因
      • サーバーが正常に起動していない。
      • クライアントが間違ったポートまたはIPアドレスに接続しようとしている。
      • ネットワークの問題で接続がタイムアウトしている。
    • 解決策
      • listening イベントが正常に発火していることを確認する。
      • クライアントの接続先を確認する。
      • ネットワーク接続を確認する。
      • サーバーのログを確認し、エラーメッセージがないか確認する。
  4. data イベントでデータが受信されない

    • エラー
      クライアントからデータを送信しても、data イベントが発火しない。
    • 原因
      • クライアントがデータを送信していない。
      • データの送信方法が間違っている。
      • ネットワークの問題でデータが損失している。
    • 解決策
      • クライアントがデータを送信していることを確認する。
      • クライアントとサーバーのデータ形式が一致していることを確認する。
      • ネットワーク接続を確認する。
  5. サーバーの終了処理

    • エラー
      サーバーが正常に終了しない。
    • 原因
      • server.close() を呼び出していない。
      • 接続中のクライアントが残っている。
    • 解決策
      • server.close() を呼び出してサーバーを終了する。
      • 接続中のクライアントをすべて切断してからサーバーを終了する。
      • server.unref() を使用して、Node.js のイベントループがサーバーの終了を待たないようにする。
  6. エラーハンドリングの欠如

    • エラー
      サーバーが予期しないエラーでクラッシュする。
    • 原因
      error イベントのハンドリングが不十分。
    • 解決策
      error イベントのリスナーを追加し、エラーを適切に処理する。

デバッグのヒント

  • ネットワークモニタリングツール (Wireshark など) を使用して、ネットワークトラフィックを監視する。
  • Node.js のデバッガーを使用する。
  • try...catch ブロックを使用して、エラーを捕捉する。
  • console.log() を使用して、サーバーの状態や変数の値を出力する。


基本的なTCPサーバーの例

const net = require('net');

const server = net.createServer((socket) => {
  console.log('クライアントが接続しました。');

  // クライアントからのデータを受信する
  socket.on('data', (data) => {
    console.log('クライアントからのデータ:', data.toString());

    // クライアントに応答を送信する
    socket.write('サーバーからの応答: ' + data.toString());
  });

  // クライアントが切断したときの処理
  socket.on('end', () => {
    console.log('クライアントが切断しました。');
  });

  // エラーハンドリング
  socket.on('error', (err) => {
    console.error('ソケットエラー:', err);
  });
});

// サーバーをポート3000で起動する
server.listen(3000, () => {
  console.log('サーバーがポート3000で起動しました。');
});

// サーバーのエラーハンドリング
server.on('error', (err) => {
  console.error('サーバーエラー:', err);
});

説明

  1. net.createServer() でサーバーインスタンスを作成し、接続されたクライアントのソケットを引数とするコールバック関数を渡します。
  2. socket.on('data', ...) で、クライアントからデータを受信したときの処理を定義します。受信したデータを toString() で文字列に変換し、コンソールに出力します。
  3. socket.write(...) で、クライアントに応答を送信します。
  4. socket.on('end', ...) で、クライアントが切断したときの処理を定義します。
  5. socket.on('error', ...) で、ソケットエラーの処理を定義します。
  6. server.listen(3000, ...) で、サーバーをポート3000で起動します。
  7. server.on('error', ...) で、サーバー全体のエラーハンドリングを定義します。

複数のクライアントを扱う例

const net = require('net');

const server = net.createServer((socket) => {
  console.log('クライアントが接続しました。');

  socket.on('data', (data) => {
    console.log('クライアントからのデータ:', data.toString());
    socket.write('サーバーからの応答: ' + data.toString());
  });

  socket.on('end', () => {
    console.log('クライアントが切断しました。');
  });

  socket.on('error', (err) => {
    console.error('ソケットエラー:', err);
  });
});

server.listen(3000, () => {
  console.log('サーバーがポート3000で起動しました。');
});

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

この例は、複数のクライアントが同時に接続しても、それぞれのクライアントとの通信を処理できます。Node.jsは非同期処理が得意なので、複数のクライアントの接続を効率的に処理できます。

特定のIPアドレスでリッスンする例

const net = require('net');

const server = net.createServer((socket) => {
  console.log('クライアントが接続しました。');

  socket.on('data', (data) => {
    console.log('クライアントからのデータ:', data.toString());
    socket.write('サーバーからの応答: ' + data.toString());
  });

  socket.on('end', () => {
    console.log('クライアントが切断しました。');
  });

  socket.on('error', (err) => {
    console.error('ソケットエラー:', err);
  });
});

server.listen(3000, '127.0.0.1', () => { // ローカルホストでのみリッスン
  console.log('サーバーがポート3000で起動しました。');
});

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

この例では、server.listen() の第二引数に '127.0.0.1' を指定することで、ローカルホストからの接続のみを受け付けるようにしています。

const net = require('net');

const server = net.createServer((socket) => {
  // ... クライアントとの通信処理 ...
});

server.listen(3000, () => {
  console.log('サーバーがポート3000で起動しました。');
});

// サーバーを終了する
server.close(() => {
  console.log('サーバーを終了しました。');
});


Express.js + Socket.IO (WebSocket)

  • 利点
    • 双方向通信が可能。
    • リアルタイム性が高い。
    • ブラウザとの互換性が高い。
    • HTTPサーバーと統合しやすい。
const express = require('express');
const http = require('http');
const socketIO = require('socket.io');

const app = express();
const server = http.createServer(app);
const io = socketIO(server);

io.on('connection', (socket) => {
  console.log('クライアントが接続しました。');

  socket.on('chat message', (msg) => {
    io.emit('chat message', msg); // 全てのクライアントに送信
  });

  socket.on('disconnect', () => {
    console.log('クライアントが切断しました。');
  });
});

server.listen(3000, () => {
  console.log('サーバーがポート3000で起動しました。');
});

UDP (dgramモジュール)

  • 利点
    • 高速な通信が可能。
    • ブロードキャストやマルチキャストが可能。
const dgram = require('dgram');

const server = dgram.createSocket('udp4');

server.on('message', (msg, rinfo) => {
  console.log(`サーバーが ${rinfo.address}:${rinfo.port} からメッセージを受信しました: ${msg}`);
  server.send(`サーバーからの応答: ${msg}`, rinfo.port, rinfo.address, (err) => {
    if (err) {
      console.error(err);
    }
  });
});

server.on('listening', () => {
  const address = server.address();
  console.log(`サーバーが ${address.address}:${address.port} でリッスンしています。`);
});

server.bind(3000);

gRPC

  • 注意
    gRPCを使うには、プロトコルバッファの定義が必要であり、少し複雑になる場合があります。
  • 利点
    • 高性能な通信。
    • 言語中立性。
    • ストリーミング通信が可能。
  • 利点
    • パフォーマンスの向上。
    • 多重化により、複数のリクエストを同時に処理できる。
const http2 = require('http2');
const fs = require('fs');

const server = http2.createSecureServer({
  key: fs.readFileSync('./server.key'),
  cert: fs.readFileSync('./server.crt')
}, (stream, headers) => {
    stream.respond({
      'content-type': 'text/html',
      ':status': 200
    });
    stream.end('<h1>Hello World</h1>');
});

server.listen(3000, () => {
  console.log('HTTP/2サーバーがポート3000で起動しました。');
});