new net.Socket()
new net.Socket()
とは?
net.Socket
クラスは、Node.js の net
モジュールが提供する、TCP またはローカルソケット(Unixドメインソケットなど)の抽象化されたオブジェクトです。簡単に言えば、ネットワーク通信を行うための「接続の片側」を表します。
new net.Socket()
コンストラクタを使って net.Socket
のインスタンスを直接作成することは可能ですが、通常はあまり一般的ではありません。なぜなら、net
モジュールには、より便利なファクトリメソッドが用意されているからです。
-
サーバーが接続を受け入れる場合
net.createServer()
このメソッドは TCP サーバーを作成し、クライアントからの新しい接続があるたびに、その接続を表すnet.Socket
インスタンスをconnection
イベントのリスナーに渡します。
-
net.connect()
net.createConnection()
これらのメソッドは、新しいnet.Socket
インスタンスを作成し、指定されたアドレスとポートに接続を開始します。
new net.Socket()
の主な用途 (直接インスタンス化する場合)
new net.Socket([options])
を直接使用するケースは、例えば以下のような特殊な状況が考えられます。
-
既存のファイルディスクリプタ (fd) やハンドルをラップする場合
- すでに存在する低レベルのネットワークハンドル(ファイルディスクリプタなど)を Node.js の
net.Socket
オブジェクトとして扱いたい場合に利用できます。これは、システムコールレベルでの操作が必要な低レベルのネットワークプログラミングや、C++アドオンなどと連携する際に役立ちます。
<!-- end list -->
const net = require('net'); // 例: 既存のファイルディスクリプタをラップする(実際の fd は環境依存です) // const socket = new net.Socket({ fd: someExistingFd });
- すでに存在する低レベルのネットワークハンドル(ファイルディスクリプタなど)を Node.js の
-
遅延接続
net.Socket
インスタンスを作成してから、後でsocket.connect()
メソッドを使って接続を開始したい場合。これは、接続ロジックをより細かく制御したい場合に便利です。
const net = require('net'); const socket = new net.Socket(); // 後で接続する socket.connect(8080, 'localhost', () => { console.log('Connected to server!'); socket.write('Hello from new net.Socket()!'); }); socket.on('data', (data) => { console.log(`Received: ${data}`); socket.end(); }); socket.on('end', () => { console.log('Disconnected from server.'); }); socket.on('error', (err) => { console.error('Socket error:', err.message); });
net.Socket
インスタンスは、Node.js の インターフェースを実装しています。これは、読み書きの両方が可能なストリームであることを意味します。
net.Socket
オブジェクトは、以下のような様々なイベントを発行し、プロパティやメソッドを提供します。
主なイベント
'timeout'
: ソケットが非アクティブ状態のためにタイムアウトした場合に発行されます(socket.setTimeout()
で設定した場合)。'error'
: エラーが発生したときに発行されます。'close'
: ソケットが完全に閉じられたときに発行されます。'end'
: ソケットの相手側が FIN パケットを送信して接続の終了を合図したときに発行されます。'data'
: データを受信したときに発行されます。引数としてBuffer
またはString
形式のデータが渡されます。'connect'
: ソケット接続が正常に確立されたときに発行されます。
主なメソッドとプロパティ
socket.bytesWritten
: 送信したバイト数。socket.bytesRead
: 受信したバイト数。socket.remotePort
: リモート側のポート番号。socket.remoteAddress
: リモート側の IP アドレス。socket.address()
: ソケットのアドレス情報(ポート、アドレス、ファミリーなど)を含むオブジェクトを返します。socket.setTimeout(timeout[, callback])
: ソケットがアイドル状態になったときに'timeout'
イベントを発行するまでの時間を設定します。socket.setEncoding([encoding])
: 受信データを指定されたエンコーディング(例:'utf8'
)で文字列として扱うように設定します。設定しない場合、Buffer
オブジェクトとしてデータが渡されます。socket.resume()
: 一時停止されたデータイベントの受信を再開します。socket.pause()
: データイベントの受信を一時停止します。socket.destroy([error])
: ソケットを完全に閉じます。socket.end([data[, encoding]][, callback])
: ソケットの書き込み側を終了します。これにより、相手側に FIN パケットが送信されます。socket.write(data[, encoding][, callback])
: ソケットにデータを送信します。socket.connect(port[, host][, connectListener])
: 指定されたポートとホストに接続を開きます。
net.Socket
に関連する一般的なエラーとトラブルシューティング
net.Socket
はネットワーク通信の基盤となるため、さまざまな要因でエラーが発生する可能性があります。ここでは、よくあるエラーとその原因、そしてトラブルシューティングのポイントを解説します。
'error' イベント
net.Socket
を使用する際に最も重要なのは、常に 'error'
イベントをリッスンすることです。これを怠ると、エラーが発生しても Node.js プロセスがクラッシュする可能性があります。
const net = require('net');
const socket = new net.Socket();
socket.on('error', (err) => {
console.error('Socket error:', err.message);
// エラーの種類に応じて適切な処理を行う
});
// ... その他のロジック
トラブルシューティングのポイント
- エラーコードを確認する
多くのネットワークエラーには、err.code
にエラーコード(例:'ECONNREFUSED'
,'EADDRINUSE'
など)が含まれています。これは問題の種類を特定するのに非常に役立ちます。 - エラーメッセージを読み込む
err.message
にはエラーの内容が記述されています。これを注意深く読むことで、問題の原因を特定する手がかりが得られます。
ECONNREFUSED (Connection Refused)
原因
- IPアドレスが間違っている
クライアントが間違った IP アドレス(またはホスト名)に接続しようとしている。 - ファイアウォール
クライアントまたはサーバーのファイアウォールが、接続をブロックしている。 - ポートが間違っている
クライアントが接続しようとしているポート番号が、サーバーがリッスンしているポート番号と異なる。 - サーバーが起動していない
クライアントが接続しようとしているポートで、サーバーアプリケーションが起動していないか、クラッシュしている可能性があります。
トラブルシューティング
- IPアドレス/ホスト名
- サーバーが
'0.0.0.0'
や'::'
(IPv6) でリッスンしていることを確認します。これにより、すべてのネットワークインターフェースからの接続を受け入れられます。 - クライアントが
localhost
を使用している場合、サーバーもlocalhost
でリッスンしているか、または適切な IP アドレスでリッスンしていることを確認します。
- サーバーが
- ファイアウォールの設定
- クライアント側のファイアウォールがアウトバウンド接続をブロックしていないか確認します。
- サーバー側のファイアウォールがインバウンド接続をブロックしていないか確認します。必要に応じて、対象のポートを開放します。
- ポート番号の一致を確認
クライアントとサーバーのコードで、使用しているポート番号が完全に一致しているか再確認します。 - サーバーの起動状況を確認
サーバーアプリケーションが正しく起動しており、指定されたポートでリッスンしているか確認します。- Node.js サーバーの場合、
net.createServer()
がエラーなく起動しているかログを確認。 netstat -an | grep <ポート番号>
(Linux/macOS) やnetstat -an | findstr <ポート番号>
(Windows) コマンドで、そのポートがLISTENING
状態になっているか確認します。
- Node.js サーバーの場合、
EADDRINUSE (Address already in use)
原因
- ポートがすでに使用中
サーバーが起動しようとしているポートが、すでに別のアプリケーション(Node.js アプリケーションの別のインスタンス、以前のクラッシュしたプロセス、他のサービスなど)によって使用されている。
トラブルシューティング
- kill -9 の問題
以前のプロセスをkill -9
で強制終了した場合、ソケットがすぐに解放されないことがあります。少し待ってから再起動を試すか、別のポートを使用します。 - ソケットの再利用 (開発環境のみ推奨)
server.unref()
を使用して、サーバーが最後に参照されるとイベントループから解放されるようにします。server.listen({ port: 8080, exclusive: false })
のようにexclusive: false
を設定することで、複数のサーバーが同じポートでリッスンすることを許可しますが、これは特別なケースであり、通常は推奨されません。
- ポート番号の変更
開発中は、異なるポート番号を使用するようにサーバーを設定することを検討します。 - 競合するプロセスを終了
特定されたプロセスが不要であれば、終了させます。 - ポート使用状況の確認
netstat -anb | findstr <ポート番号>
(Windows)lsof -i :<ポート番号>
(Linux/macOS)netstat -an | grep <ポート番号>
(Linux/macOS) これらのコマンドを使用して、どのプロセスがそのポートを使用しているか特定します。
ETIMEDOUT (Operation timed out)
原因
- DNS解決の失敗/遅延
ホスト名をIPアドレスに解決するのに時間がかかりすぎている。 - サーバーの応答遅延
サーバーが接続要求に対してすぐに応答できない。 - ファイアウォール
サーバー側で接続は受け入れられているが、応答がファイアウォールによってブロックされている。 - ネットワーク遅延/混雑
接続試行中にパケットが失われたり、応答が返ってこなかったりする。
トラブルシューティング
- socket.connect() のタイムアウト設定
connect
メソッドにはタイムアウトオプションがないため、Node.js の内部的なネットワークスタックのタイムアウトに依存します。タイムアウトが発生した場合、上記を確認し、必要に応じてリトライロジックを実装します。 - DNS解決の確認
ホスト名を使用している場合、DNS サーバーが正しく機能しているか、DNS キャッシュが古くなっていないか確認します。 - サーバーの負荷
サーバーが高い負荷状態にあり、新しい接続を受け入れるのに時間がかかっている可能性があります。サーバーのパフォーマンスを監視します。 - ファイアウォールの再確認
ECONNREFUSED
の場合と同様に、双方向のトラフィックが許可されているか確認します。 - ネットワーク接続の確認
クライアントとサーバー間のネットワーク接続が安定しているか確認します。Ping コマンドなどで疎通を確認します。
ENOTFOUND (DNS lookup failed) / EAI_AGAIN (Temporary failure in name resolution)
原因
- インターネット接続がない
インターネットに接続されていない環境で外部のホスト名に接続しようとしている。 - DNS解決の失敗
DNS サーバーがダウンしている、またはネットワーク設定に問題がある。 - ホスト名が間違っている
存在しないホスト名に接続しようとしている。
トラブルシューティング
- 一時的な問題
EAI_AGAIN
は一時的な DNS 解決の失敗を示すことが多いです。少し待ってから再試行するか、リトライロジックを実装することを検討します。 - ネットワーク接続
インターネットへの接続が確立されているか確認します。 - DNSサーバーの確認
ローカルの DNS 設定が正しいか、設定されている DNS サーバーが機能しているか確認します。 - ホスト名のスペルミス
接続先のホスト名にスペルミスがないか確認します。
EPIPE (Broken pipe)
原因
- リモートエンドが接続を突然クローズした
データを書き込もうとしたときに、相手側がすでにソケットを閉じている(またはクラッシュした)場合。これは通常、サーバーがクライアントからの予期しない切断を検出した後にデータを書き込もうとしたときに発生します。
トラブルシューティング
- ハートビート/キープアライブ
長時間接続が維持される場合、定期的にハートビートメッセージを交換することで、接続がまだアクティブであることを確認できます。 - 'end' イベントまたは 'close' イベントのハンドリング
データ書き込み前に、相手側が接続を閉じていないか確認するために、'end'
や'close'
イベントを適切に処理しているか確認します。これらのイベントが発生した後に書き込みを試みないようにロジックを調整します。 - 相手側の状態を確認
サーバーがクライアントからの接続を切断した理由や、クライアントがサーバーとの接続を切断した理由を特定します。
EADDRNOTAVAIL (Cannot assign requested address)
原因
- ネットワークインターフェースがダウンしている
指定されたローカルIPアドレスが属するネットワークインターフェースがアクティブでない場合。 - 指定されたローカルIPアドレスが存在しない
socket.connect({ localAddress: '...' })
を使用して、存在しないローカルIPアドレスから接続しようとした場合。
- ネットワークインターフェースの状態
関連するネットワークインターフェースが有効になっているか確認します。 - ローカルIPアドレスの確認
システムに割り当てられているローカルIPアドレスを正確に指定しているか確認します(例:ipconfig
(Windows) /ifconfig
(Linux/macOS))。
- タイムアウト
socket.setTimeout()
を設定している場合、一定時間データが送受信されないと'timeout'
イベントが発生します。これはエラーではありませんが、接続がアイドル状態であることを示し、接続をクローズするトリガーとなり得ます。 - バッファリングとフロー制御
大量のデータを送受信する際に、バッファがいっぱいになったり、フロー制御がうまくいかなかったりすると問題が発生することがあります。socket.write()
の戻り値(true
またはfalse
)をチェックし、'drain'
イベントを利用して書き込みを一時停止/再開するフロー制御を実装することが重要です。
- 依存関係
プロジェクトが他のライブラリやフレームワークに依存している場合、それらがnet.Socket
の動作に影響を与えていないか確認します。 - OS と Node.js のバージョン
使用している OS のバージョンや Node.js のバージョンが、既知の問題やバグに関連していないか確認します。 - ネットワークツール
ping
: 接続先のホストとの基本的なネットワーク疎通を確認。telnet
/nc
(netcat): 特定のポートへの接続を試み、ポートが開いているか確認。Wireshark
/tcpdump
: ネットワークパケットをキャプチャし、通信の詳細を分析。これにより、データが正しく送受信されているか、どの段階で問題が発生しているかなどを深く掘り下げることができます。
- 最小限の再現コード
問題を切り分けるために、最小限のコードでエラーを再現できるか試します。 - コードのデバッグ
Node.js のデバッガーやconsole.log
を活用して、コードの実行フローや変数の状態を追跡します。 - ログの活用
クライアントとサーバーの両方で、接続、データ送受信、切断、エラーなどの重要なイベントを詳細にログに出力するようにします。これにより、問題発生時の状況を把握しやすくなります。
net.Socket
は Node.js の net
モジュールの中心的なクラスで、TCP (Transmission Control Protocol) やローカルソケット(Unixドメインソケット)通信を扱うために使用されます。通常、net.connect()
(クライアント側) や net.createServer()
(サーバー側) といったファクトリメソッドを通じて間接的に利用されますが、ここではそれぞれのコンテキストでどのように net.Socket
インスタンスが扱われるかを示します。
例 1: シンプルな TCP サーバー
この例では、net.createServer()
を使用して TCP サーバーを作成し、クライアントからの接続を受け入れます。新しい接続ごとに net.Socket
インスタンスが生成され、'connection'
イベントリスナーに渡されます。
サーバー (server.js
)
const net = require('net');
const PORT = 3000;
const HOST = '127.0.0.1'; // ループバックアドレス (localhost)
// TCP サーバーを作成
const server = net.createServer((socket) => {
// 'socket' は新しく接続されたクライアントを表す net.Socket インスタンスです。
console.log(`クライアントが接続しました: ${socket.remoteAddress}:${socket.remotePort}`);
// クライアントからデータを受信した時
socket.on('data', (data) => {
const receivedMessage = data.toString().trim();
console.log(`クライアント (${socket.remoteAddress}:${socket.remotePort}) からデータを受信: ${receivedMessage}`);
// 受信したデータをエコーバック
socket.write(`サーバーからの返信: あなたは "${receivedMessage}" と言いました。\n`);
});
// クライアントが接続を終了した時
socket.on('end', () => {
console.log(`クライアントが切断しました: ${socket.remoteAddress}:${socket.remotePort}`);
});
// ソケットでエラーが発生した時
socket.on('error', (err) => {
console.error(`ソケットエラー (${socket.remoteAddress}:${socket.remotePort}): ${err.message}`);
});
// ソケットが完全にクローズされた時 (end, error, destroy などの後)
socket.on('close', () => {
console.log(`ソケットがクローズされました: ${socket.remoteAddress}:${socket.remotePort}`);
});
});
// サーバーをリスン開始
server.listen(PORT, HOST, () => {
console.log(`サーバーが ${HOST}:${PORT} でリスンを開始しました`);
});
// サーバーエラーのハンドリング
server.on('error', (err) => {
if (err.code === 'EADDRINUSE') {
console.error(`ポート ${PORT} は既に使用されています。別のポートを使用するか、既存のプロセスを終了してください。`);
} else {
console.error('サーバーエラー:', err.message);
}
});
サーバーコードの説明
server.on('error', (err) => { ... })
: サーバー自体がエラーを起こした場合(例: ポートが既に使用されているEADDRINUSE
など)に発生します。server.listen(PORT, HOST, () => { ... })
: 指定されたポートとホストでサーバーが接続を待機し始めます。socket.on('close', () => { ... })
: ソケットが完全に閉じられたときに発生します。'end'
や'error'
の後に発生することが多いです。socket.on('error', (err) => { ... })
: ソケットレベルでエラーが発生したときに発生します(例: 接続中にネットワークが切断された、相手が強制終了したなど)。これは必ずハンドリングするべきです。socket.on('end', () => { ... })
: クライアントが自発的に接続を終了(socket.end()
を呼び出すなど)したときに発生します。socket.write(message)
: クライアントにデータを送信します。socket.on('data', (data) => { ... })
: クライアントからデータが送信されると発生するイベントです。data
はBuffer
オブジェクトで渡されるため、toString()
で文字列に変換します。socket.remoteAddress
,socket.remotePort
: 接続してきたクライアントのリモートIPアドレスとポート番号を取得できます。net.createServer((socket) => { ... })
: 新しい TCP サーバーを作成します。引数のコールバック関数は、新しいクライアントが接続するたびに実行され、その接続を表すnet.Socket
オブジェクトがsocket
引数として渡されます。
例 2: シンプルな TCP クライアント
この例では、net.connect()
を使用して上記サーバーに接続するクライアントを作成します。net.connect()
も内部で net.Socket
インスタンスを生成して返します。
クライアント (client.js
)
const net = require('net');
const PORT = 3000;
const HOST = '127.0.0.1'; // サーバーと同じホスト
// サーバーに接続
// net.connect() は net.Socket のインスタンスを返します。
const client = net.connect(PORT, HOST, () => {
console.log(`サーバー (${HOST}:${PORT}) に接続しました`);
// 接続後、サーバーにメッセージを送信
client.write('こんにちは、サーバーさん!\n');
client.write('Node.js クライアントです。\n');
});
// サーバーからデータを受信した時
client.on('data', (data) => {
console.log(`サーバーからデータを受信: ${data.toString().trim()}`);
// 受信後、接続を終了
client.end(); // クライアント側からFINパケットを送信し、書き込みを終了
});
// サーバーが接続を終了した時 (FINパケットを受信)
client.on('end', () => {
console.log('サーバーから切断されました');
});
// ソケットでエラーが発生した時
client.on('error', (err) => {
console.error('ソケットエラー:', err.message);
});
// ソケットが完全にクローズされた時
client.on('close', () => {
console.log('ソケットがクローズされました');
});
クライアントコードの説明
client.end()
: クライアント側から接続の書き込み側を終了します。これにより、相手(サーバー)に FIN (Finish) パケットが送信され、サーバーの'end'
イベントがトリガーされます。end()
を呼び出してもすぐにソケットが閉じられるわけではなく、すべてのデータが送信され、相手側が FIN を返した後に'close'
イベントが発生します。client.on('data', (data) => { ... })
: サーバーからデータを受信すると発生します。client.write(message)
: サーバーにデータを送信します。net.connect(PORT, HOST, () => { ... })
: 指定されたポートとホストに接続を開始します。接続が確立されると、コールバック関数が実行され、'connect'
イベントも発行されます。
例 3: new net.Socket()
を直接使用する(高度なケース)
この例では、net.Socket
を直接インスタンス化し、後から connect()
メソッドを呼び出して接続を開始します。これは前述の net.connect()
と機能的には似ていますが、インスタンスの生成と接続の開始を分離したい場合に利用されます。
クライアント (client_direct.js
)
const net = require('net');
const PORT = 3000;
const HOST = '127.0.0.1';
// net.Socket インスタンスを直接作成
const client = new net.Socket();
// 接続イベントリスナーを設定(connect() のコールバックとほぼ同じタイミングで発火)
client.on('connect', () => {
console.log(`直接インスタンス化したクライアントがサーバー (${HOST}:${PORT}) に接続しました`);
client.write('これは直接インスタンス化したクライアントからのメッセージです。\n');
});
// データ受信イベント
client.on('data', (data) => {
console.log(`直接インスタンス化したクライアントがサーバーから受信: ${data.toString().trim()}`);
client.end();
});
// 切断イベント
client.on('end', () => {
console.log('直接インスタンス化したクライアントがサーバーから切断されました');
});
// エラーイベント
client.on('error', (err) => {
console.error('直接インスタンス化したクライアントのソケットエラー:', err.message);
});
// クローズイベント
client.on('close', () => {
console.log('直接インスタンス化したクライアントのソケットがクローズされました');
});
// ここで接続を開始
client.connect(PORT, HOST);
client.connect(PORT, HOST);
: 作成したソケットインスタンスに対して、実際にサーバーへの接続を開始するメソッドを呼び出します。client.on('connect', () => { ... })
:client.connect()
が成功すると、このイベントが発行されます。const client = new net.Socket();
:net.Socket
クラスの新しいインスタンスを直接作成します。この時点ではまだ接続は開始されていません。
実行方法
-
server.js
ファイルを保存し、ターミナルで実行します。node server.js
サーバーが 127.0.0.1:3000 でリスンを開始しました
と表示されるはずです。 -
別のターミナルを開き、
client.js
を実行します。node client.js
クライアントがサーバーに接続し、メッセージを送信・受信して切断する様子が両方のターミナルで確認できます。
-
同様に、
client_direct.js
を実行して、new net.Socket()
の動作を確認できます。node client_direct.js
net.Socket
は Node.js における低レベルのネットワーク通信の基盤ですが、多くの場合、開発者はより抽象化された、特定のユースケースに特化したプロトコルやライブラリを使用します。これらは内部的に net.Socket
を利用していることが多いですが、直接 net.Socket
を扱う必要がなく、開発を効率化できます。
主な代替手段としては以下のものが挙げられます。
- HTTP/HTTPS モジュール
- WebSocket (ws ライブラリなど)
- UDP (User Datagram Protocol) (dgram モジュール)
- RPC (Remote Procedure Call) フレームワーク (gRPC など)
- メッセージキューシステム (Redis, RabbitMQ など)
- サードパーティ製ソケット通信ライブラリ
それぞれの方法について詳しく見ていきましょう。
HTTP/HTTPS モジュール (http, https)
ウェブアプリケーションや RESTful API の開発では、net.Socket
を直接扱うことはほとんどなく、Node.js 標準の http
および https
モジュールを使用します。これらは TCP の上に HTTP プロトコルを実装しており、リクエスト/レスポンスモデルに最適化されています。
特徴
- HTTPS
暗号化通信 (TLS/SSL) が容易に実装できる。 - RESTful API
近年のウェブサービス開発の主流。 - 普遍性
ウェブブラウザや多くのクライアントが標準で対応しており、どこからでもアクセスしやすい。 - ステートレス
基本的に各リクエストは独立しており、以前のリクエストの状態を保持しない(セッション管理は別のメカニズムで行う)。 - リクエスト/レスポンスモデル
クライアントがリクエストを送り、サーバーがレスポンスを返すという明確なサイクル。
利点
- ミドルウェアやフレームワーク (Express.js, Koa.js など) と組み合わせて強力なウェブサーバーを構築できる。
- ウェブ開発に特化しており、開発効率が高い。
- 使い慣れたプロトコルで、ドキュメントやツールが豊富。
欠点
- オーバーヘッドが比較的大きい。
- 双方向通信(プッシュ通知など)には向かない(ロングポーリングや WebSocket が必要)。
使用例 (サーバー)
const http = require('http');
const server = http.createServer((req, res) => {
console.log(`リクエスト受信: ${req.method} ${req.url}`);
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end('こんにちは、HTTPサーバーです!');
});
server.listen(8080, () => {
console.log('HTTPサーバーが http://localhost:8080 で起動しました');
});
WebSocket (ws ライブラリなど)
リアルタイムな双方向通信が必要な場合(チャットアプリケーション、オンラインゲーム、ライブデータストリーミングなど)は、WebSocket が適しています。WebSocket は HTTP のハンドシェイクから始まり、その後は永続的な TCP 接続上で全二重通信を行います。
特徴
- イベント駆動
データ受信や接続状態の変化をイベントとして扱える。 - HTTPとの互換性
ポート80/443を使用できるため、ファイアウォールを通過しやすい。 - 永続的な接続
一度確立されると接続が維持されるため、低レイテンシー。 - 全二重通信
クライアントとサーバーが同時にデータを送受信できる。
利点
- HTTPのオーバーヘッドなしに高速な双方向通信が可能。
- リアルタイムアプリケーション開発に最適。
欠点
net.Socket
よりは高レベルだが、複雑なプロトコル設計が必要になる場合もある。- 非リアルタイムなリクエスト/レスポンスには向かない。
使用例 (サーバー - ws ライブラリを使用)
// まずは 'ws' ライブラリをインストール: npm install ws
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', ws => {
console.log('クライアントがWebSocket接続しました');
ws.on('message', message => {
console.log(`受信: ${message}`);
// 受け取ったメッセージをすべてのクライアントにブロードキャスト
wss.clients.forEach(client => {
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
ws.send(`サーバーからの返信: あなたは "${message}" と言いました`);
});
ws.on('close', () => {
console.log('クライアントがWebSocket切断しました');
});
ws.on('error', error => {
console.error('WebSocketエラー:', error);
});
});
console.log('WebSocketサーバーが ws://localhost:8080 で起動しました');
UDP (User Datagram Protocol) (dgram モジュール)
TCP が信頼性の高いコネクション指向のプロトコルであるのに対し、UDP はコネクションレスで、信頼性を保証しないデータグラム指向のプロトコルです。高速性が求められるが、パケット損失が許容される場合(例: ストリーミング、オンラインゲームの一部、DNSクエリなど)に使用されます。
特徴
- データグラム指向
メッセージの単位がデータグラム。 - 高速
TCPのような接続確立やエラーチェックのオーバーヘッドがないため高速。 - 非信頼性
パケットの到達順序や到達そのものを保証しない。 - コネクションレス
事前に接続を確立する必要がない。
利点
- 多数のクライアントからの短いメッセージを処理するのに向いている。
- 低レイテンシーが最重要視されるアプリケーション。
欠点
- TCPほど汎用性がない。
- パケット損失や順序の保証が必要な場合は、アプリケーションレベルで信頼性メカニズムを実装する必要がある。
使用例 (サーバー)
const dgram = require('dgram');
const server = dgram.createSocket('udp4');
server.on('error', (err) => {
console.error(`サーバーエラー:\n${err.stack}`);
server.close();
});
server.on('message', (msg, rinfo) => {
console.log(`サーバーが ${rinfo.address}:${rinfo.port} から ${msg} を受信しました`);
// 受信したデータをそのままクライアントに返信(エコー)
server.send(`サーバーからの返信: ${msg}`, rinfo.port, rinfo.address, (err) => {
if (err) console.error('UDP送信エラー:', err);
});
});
server.on('listening', () => {
const address = server.address();
console.log(`サーバーが ${address.address}:${address.port} でUDPリスンを開始しました`);
});
server.bind(41234); // ポートをバインド
RPC (Remote Procedure Call) フレームワーク (gRPC, Thrift など)
異なるプロセスやネットワーク上のマシンで実行されている関数を、ローカルで実行されているかのように呼び出すための技術です。特にマイクロサービスアーキテクチャで、サービス間の通信によく利用されます。
特徴
- 高パフォーマンス
バイナリプロトコルを使用するため、JSONなどのテキストベースプロトコルより効率的。 - 型安全
コード生成により、メソッド呼び出しが型安全になる。 - スキーマ駆動
データ構造やサービスインターフェースが定義ファイル(例: Protocol Buffers for gRPC)で厳密に定義される。 - 言語非依存
様々な言語で実装されたサービス間で通信が可能。
利点
- 厳密なインターフェース定義により、開発の整合性が保たれる。
- 大規模な分散システムやマイクロサービスアーキテクチャに適している。
欠点
- HTTP/RESTful API ほど普遍的ではない。
- 学習コストが高い。
メッセージキューシステム (Redis Pub/Sub, RabbitMQ, Kafka など)
リアルタイムな通知、タスクキュー、複雑なイベント駆動型アーキテクチャでは、メッセージキューシステムが効果的です。直接的なソケット通信ではなく、メッセージブローカーを介して非同期的にメッセージを送受信します。
特徴
- 信頼性
メッセージの永続化、アック(確認応答)により、メッセージ損失を防ぐことができる。 - スケーラビリティ
メッセージの処理を分散・並列化しやすい。 - 疎結合
送信者と受信者が直接通信する必要がなく、互いに依存しない。 - 非同期通信
送信側はメッセージをキューに送信し、受信側は後でそれを取り出す。
利点
- イベント駆動型アーキテクチャの構築を容易にする。
- 複雑な分散システムでの信頼性とスケーラビリティを向上させる。
欠点
- 専用のメッセージブローカーが必要。
- システムの複雑性が増す。
サードパーティ製ソケット通信ライブラリ (Socket.IO など)
net.Socket
を直接使用するよりも、さらに高レベルで便利な抽象化を提供するライブラリも存在します。Socket.IO
はその代表例で、WebSocket を利用しつつ、接続の再接続、フォールバック(WebSocketが使えない場合にHTTPロングポーリングなど)、ルーム機能などを自動的に処理してくれます。
特徴
- 簡素化されたAPI
イベントベースのAPIで、リアルタイム通信の実装が容易。 - 名前空間/ルーム
複数のチャネルやグループにメッセージを送信する機能。 - 再接続ロジック
ネットワークの一時的な切断時に自動的に再接続を試みる。 - クロスブラウザ対応
WebSocketがサポートされていない環境でも動作するようフォールバック機構を持つ。
利点
- 低レベルのネットワーク問題を気にせず開発できる。
- リアルタイムウェブアプリケーション開発を劇的に簡素化。
欠点
- 独自のプロトコルを使用するため、他のWebSocketクライアントとの直接的な互換性はない場合がある(ただし、WebSocket自体は標準プロトコル)。
net.Socket
やws
より抽象化されているため、低レベルな制御は難しい。
// まずは 'socket.io' と 'express' をインストール: npm install socket.io express
const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const app = express();
const server = http.createServer(app);
const io = new Server(server); // Socket.IOサーバーをHTTPサーバーにアタッチ
app.get('/', (req, res) => {
res.sendFile(__dirname + '/index.html'); // HTMLファイルを提供する
});
io.on('connection', (socket) => {
console.log('Socket.IO クライアントが接続しました');
socket.on('chat message', (msg) => {
console.log('メッセージ: ' + msg);
io.emit('chat message', msg); // 全ての接続されたクライアントにメッセージをブロードキャスト
});
socket.on('disconnect', () => {
console.log('Socket.IO クライアントが切断しました');
});
});
server.listen(3000, () => {
console.log('Socket.IO サーバーが http://localhost:3000 で起動しました');
});
// index.html (クライアント側のHTMLファイル)
/*
<!DOCTYPE html>
<html>
<head>
<title>Socket.IO Chat</title>
<script src="/socket.io/socket.io.js"></script>
</head>
<body>
<ul id="messages"></ul>
<form id="form" action="">
<input id="input" autocomplete="off" /><button>送信</button>
</form>
<script>
var socket = io();
var form = document.getElementById('form');
var input = document.getElementById('input');
var messages = document.getElementById('messages');
form.addEventListener('submit', function(e) {
e.preventDefault();
if (input.value) {
socket.emit('chat message', input.value);
input.value = '';
}
});
socket.on('chat message', function(msg) {
var item = document.createElement('li');
item.textContent = msg;
messages.appendChild(item);
window.scrollTo(0, document.body.scrollHeight);
});
</script>
</body>
</html>
*/