この用語をシェア
WebSocketとは
WebSocketは、Webブラウザとサーバー間でリアルタイムな双方向通信を実現するプロトコルです。RFC 6455として標準化されており、HTTPベースのリクエスト・レスポンスモデルとは異なり、一度接続を確立すると、クライアントとサーバーの両方がいつでも自由にデータを送信できます。
WebSocketの主要な特徴
- 双方向通信:クライアントとサーバーが同時にデータを送信可能
- リアルタイム:低遅延でのデータ交換が可能
- 持続的接続:一度接続すると接続が維持される
- 軽量:HTTPヘッダーのオーバーヘッドがない
- ファイアウォール対応:HTTPポート(80/443)を使用
WebSocket vs HTTP
特徴 | WebSocket | HTTP |
---|---|---|
通信方式 | 双方向 | リクエスト・レスポンス |
接続 | 持続的 | ステートレス |
オーバーヘッド | 低い | 高い |
リアルタイム性 | 高い | 低い |
キャッシュ | 不可 | 可能 |
WebSocketの接続プロセス
1. ハンドシェイク(Handshake)
WebSocketは、HTTPアップグレードリクエストから始まります。
クライアントからのリクエスト
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: https://example.com
サーバーからのレスポンス
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
2. データ交換フェーズ
接続が確立されると、フレームベースでデータを交換します。
WebSocketフレーム構造
0 1 2 3
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+
WebSocket Opcodeの種類
Opcode | 意味 | 説明 |
---|---|---|
0x0 | 継続フレーム | 分割されたメッセージの続き |
0x1 | テキストフレーム | UTF-8テキストデータ |
0x2 | バイナリフレーム | バイナリデータ |
0x8 | 接続終了 | 接続を終了する |
0x9 | Ping | 接続確認 |
0xA | Pong | Pingへの応答 |
WebSocketの実装例
クライアント(JavaScript)
// WebSocket接続の確立
const socket = new WebSocket('ws://localhost:8080');
// 接続が開かれたときの処理
socket.onopen = function(event) {
console.log('WebSocket接続が開かれました');
// メッセージを送信
socket.send('Hello Server!');
};
// メッセージを受信したときの処理
socket.onmessage = function(event) {
console.log('受信したメッセージ:', event.data);
// JSONメッセージの解析
try {
const data = JSON.parse(event.data);
console.log('パースされたデータ:', data);
} catch (e) {
console.log('テキストメッセージ:', event.data);
}
};
// エラーが発生したときの処理
socket.onerror = function(error) {
console.error('WebSocketエラー:', error);
};
// 接続が閉じられたときの処理
socket.onclose = function(event) {
console.log('WebSocket接続が閉じられました');
console.log('コード:', event.code);
console.log('理由:', event.reason);
// 再接続の試行
setTimeout(() => {
console.log('再接続を試行します...');
// 再接続のロジック
}, 5000);
};
// JSONメッセージの送信
function sendJSON(data) {
if (socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify(data));
}
}
// 接続を閉じる
function closeConnection() {
socket.close(1000, 'ユーザーによる切断');
}
サーバー(Node.js + ws)
const WebSocket = require('ws');
// WebSocketサーバーの作成
const wss = new WebSocket.Server({ port: 8080 });
// 接続されたクライアントの管理
const clients = new Set();
console.log('WebSocketサーバーがポート8080で起動しました');
// クライアントが接続したときの処理
wss.on('connection', function connection(ws, req) {
console.log('新しいクライアントが接続しました');
// クライアントリストに追加
clients.add(ws);
// 接続確認メッセージを送信
ws.send(JSON.stringify({
type: 'welcome',
message: 'WebSocketサーバーへようこそ!',
timestamp: new Date().toISOString()
}));
// メッセージを受信したときの処理
ws.on('message', function incoming(data) {
console.log('受信したメッセージ:', data.toString());
try {
const message = JSON.parse(data);
// すべてのクライアントにブロードキャスト
broadcastMessage({
type: 'broadcast',
data: message,
timestamp: new Date().toISOString()
});
} catch (e) {
// テキストメッセージの場合
broadcastMessage({
type: 'text',
message: data.toString(),
timestamp: new Date().toISOString()
});
}
});
// 接続が閉じられたときの処理
ws.on('close', function close() {
console.log('クライアントが切断しました');
clients.delete(ws);
});
// エラーが発生したときの処理
ws.on('error', function error(err) {
console.error('WebSocketエラー:', err);
clients.delete(ws);
});
// 定期的なPing送信
const pingInterval = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.ping();
} else {
clearInterval(pingInterval);
}
}, 30000);
});
// 全クライアントにメッセージをブロードキャスト
function broadcastMessage(message) {
const data = JSON.stringify(message);
clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(data);
}
});
}
サーバー(Python + websockets)
import asyncio
import websockets
import json
import logging
# ログ設定
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# 接続されたクライアントの管理
connected_clients = set()
async def handle_client(websocket, path):
"""クライアント接続の処理"""
logger.info(f"新しいクライアントが接続しました: {websocket.remote_address}")
# クライアントリストに追加
connected_clients.add(websocket)
try:
# 接続確認メッセージを送信
await websocket.send(json.dumps({
'type': 'welcome',
'message': 'WebSocketサーバーへようこそ!',
'clients_count': len(connected_clients)
}))
# メッセージを受信し続ける
async for message in websocket:
logger.info(f"受信したメッセージ: {message}")
try:
# JSONメッセージの解析
data = json.loads(message)
# すべてのクライアントにブロードキャスト
await broadcast_message({
'type': 'broadcast',
'data': data,
'from': str(websocket.remote_address),
'clients_count': len(connected_clients)
})
except json.JSONDecodeError:
# テキストメッセージの場合
await broadcast_message({
'type': 'text',
'message': message,
'from': str(websocket.remote_address),
'clients_count': len(connected_clients)
})
except websockets.exceptions.ConnectionClosed:
logger.info(f"クライアントが切断しました: {websocket.remote_address}")
except Exception as e:
logger.error(f"エラーが発生しました: {e}")
finally:
# クライアントリストから削除
connected_clients.discard(websocket)
async def broadcast_message(message):
"""全クライアントにメッセージをブロードキャスト"""
if not connected_clients:
return
data = json.dumps(message)
await asyncio.gather(
*[client.send(data) for client in connected_clients],
return_exceptions=True
)
# WebSocketサーバーの起動
async def main():
logger.info("WebSocketサーバーをポート8080で起動します...")
async with websockets.serve(handle_client, "localhost", 8080):
logger.info("WebSocketサーバーが起動しました")
await asyncio.Future() # 永続的に実行
if __name__ == "__main__":
asyncio.run(main())
WebSocketの活用例
1. リアルタイムチャットアプリ
- 用途:インスタントメッセージング
- 特徴:即座のメッセージ配信
- 実装:Socket.IO, WebSocket API
2. オンラインゲーム
- 用途:マルチプレイヤーゲーム
- 特徴:低遅延でのゲーム状態同期
- 実装:カスタムプロトコル
3. ライブ配信・動画通話
- 用途:ストリーミング、ビデオ会議
- 特徴:リアルタイムメディア配信
- 実装:WebRTC + WebSocket
4. 金融取引システム
- 用途:株価、為替レートの配信
- 特徴:高頻度でのデータ更新
- 実装:専用プロトコル
WebSocketライブラリ・フレームワーク
JavaScript(クライアント)
- WebSocket API:ブラウザ標準API
- Socket.IO:フォールバック機能付きライブラリ
- ws:Node.js用WebSocketライブラリ
サーバーサイド
- Node.js:ws、Socket.IO
- Python:websockets、tornado
- Java:Java WebSocket API、Netty
- Go:gorilla/websocket
- C#:SignalR
WebSocketのセキュリティ
セキュリティ考慮事項
- Origin検証:適切なOriginからの接続のみ許可
- 認証・認可:トークンベースの認証実装
- 入力検証:受信データの検証
- レート制限:メッセージ頻度の制限
- WSS使用:SSL/TLS暗号化
WSS(WebSocket Secure)
// 暗号化されたWebSocket接続
const socket = new WebSocket('wss://example.com/socket');
// 証明書の検証
socket.onopen = function(event) {
console.log('セキュアな接続が確立されました');
};
WebSocketの制限事項
- ファイアウォール:一部のプロキシやファイアウォールでブロック
- 接続数制限:サーバーリソースによる制限
- メッセージサイズ:大きなメッセージの処理負荷
- ネットワーク切断:接続の予期せぬ切断
- メモリ使用:持続的接続によるメモリ消費
WebSocketの代替技術
Server-Sent Events (SSE)
- 用途:サーバーからクライアントへの一方向通信
- 特徴:HTTPベース、自動再接続
- 制限:クライアントからサーバーへの通信不可
Long Polling
- 用途:疑似リアルタイム通信
- 特徴:HTTPベース、フォールバック対応
- 制限:オーバーヘッドが大きい
WebRTC
- 用途:P2P通信、メディアストリーミング
- 特徴:ブラウザ間直接通信
- 制限:設定が複雑
WebSocketのベストプラクティス
- 再接続機能:ネットワーク切断時の自動再接続
- ハートビート:定期的なPing/Pongで接続確認
- エラーハンドリング:適切なエラー処理
- メッセージキューイング:送信失敗時のメッセージ保持
- リソース管理:適切な接続数制限
WebSocketの今後
- HTTP/3対応:QUIC プロトコルベースの改良
- WebTransport:新しいリアルタイム通信API
- WebAssembly統合:高性能アプリケーション
- エッジコンピューティング:CDN でのWebSocket対応
関連技術
- Socket.IO:WebSocketライブラリ
- SignalR:Microsoft のリアルタイム通信ライブラリ
- WebRTC:リアルタイム通信API
- Server-Sent Events:一方向リアルタイム通信