Node.jsとWebSocketでリアルタイムチャットをやってみた

Pocket

Node.jsでWebサーバを構築するメリット

Node.jsでは、リクエストを処理するときに、イベントループというモデルを採用しており、シングルスレッドで対応します。
イベントが発生するとイベントキューに積まれていき、イベントループで積まれているイベントを処理していきます。Node.jsでWebサーバを構築すると、多重プロセスモデルに比べて、大量のクライアントからの接続を維持できるというメリットがあります。
その反面、CPUを大量に消費するような重たい処理を行うとイベントループが止まってしまうので、そのような処理をするときにはNode.jsは向いていません。

event-model

そんな感じで、Node.jsの大量のクライアントからの接続を維持できるというメリットをいかしWebSocketを使って、リアルタイムチャットをつくってみた。

その前に、WebSocketって? リアルタイムチャットって?

WebSocketは、Webにおいて双方向通信を実現するプロトコルのことです。
WebSocketの詳しい説明の前に、経緯みたいなものをたどるため、HTTPを使って双方向通信を実現する方法を説明します。
HTTPを簡単に説明すると、クライアントからサーバにリクエストを送り、サーバからクライアントにレスポンスを返すという仕組みです。しかし、これだとサーバからクライアントに任意のタイミングで通信を行うことができませんでした。
そこで、擬似的にサーバから情報を送るためにロングポーリングと呼ばれる方法がしばしば使われていました。
ロングポーリングは、まずクライアントから擬似的にリクエストを送りサーバからレスポンスをもらって画面を更新するということを繰り返して、無理矢理力技で双方向通信(厳密には双方向ではないが、、、)をしていました。

ロングポーリング

ロングポーリングでは、クライアントがサーバへ定期的にアクセスして画面を再描画するので、リアルタイム性はアクセスの間隔に依存するため不安定でした。また、変更分の差分だけでなく、毎回すべてレンダリングするため、かなり効率の悪いやり方でした。

そうしたものを補う形で実現できるのが、WebSocketです!
Webでサーバから任意のタイミングでクライアントに情報を送信したいときに、使えるのがWebSocketです。
WebSocketでは、Facebookのメッセージのように多数のクライアントが一つのページにアクセスしていて誰かがメッセージを送信すると、そのページにつながっているクライアントにリロードなしそのメッセージが送信され更新されるといったことができます。

WebSocketの仕組みは、コネクションを通じてクライアントとサーバ間の双方向通信します。
まずWebSocketのコネクションを確立することから始まり、そのコネクションの確立にHTTPが使われます。
そうして確立したコネクションを使って、TCP上で低コストで双方向通信が実現できるになっています。

WebSocket

Node.js + WebSocketでリアルタイムチャットを作る

これから作るものは以下の環境で試しています。

node      : 6.2.2
express   : 4.14.0
socket.io : 1.5.0

nodeが入ってないという方は、こちらを参考に。
expressとsocketは、npmでインストールしておいてください

 $ npm init -y
 $ npm install express socket.io --save

これから実際にコードを書いてみましょう。
といっても、コード量は、わずか数行です。

まずは、サーバ側のコードから

var app = require('express')();
var server = require('http').Server(app);
var io = require('socket.io')(server);

app.get('/', (req, res) => {
  res.sendFile(__dirname + '/index.html');
});

// chatでメッセージを受信したらクライアント全体にキャスト
io.on('connection', (socket) => {
  socket.on('chat', (msg) => {
    io.emit('chat', msg);
  });
});

server.on('listening', () => {
  console.log('listening on 3000');
});

server.listen(3000);

次に、クライアント側のコードです。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>WebSocketを使おう</title>
</head>
<body>
<h1>Chatアプリ</h1>
<form id='form'>
  <input id="chat" />
  <button>送信</button>
</form>
<ul id="messages"></ul>
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io();
var form = document.getElementById('form');
var chat = document.getElementById('chat');
var messages = document.getElementById('messages');

form.addEventListener('submit', function (e) {
  // websocketを使うのでフォームの送信をキャンセル
  e.preventDefault();
  
  // イベントを発火しデータを受け渡す
  socket.emit('chat', chat.value);
  chat.value = '';
});

// サーバ側からchatイベントを待ち受ける
socket.on('chat', function (msg) {
  var li = document.createElement('li');
  li.textContent = msg;
  messages.appendChild(li);
});
</script>
</body>
</html>

アプリケーション部分のコードはこれだけです。

プロジェクトのファイルの構成は以下の通りです。

./
├── app.js
├── index.html
├── package.json
└── node_modules/
   └── nodeのライブラリがたくさん

そして下記のコマンドを実行してください

 $ node app.js

画像でのせても、わかりづらいのでお持ちの環境でブラウザのウインドウを複数立ち上げて確認してみてください。
送信ボタンを押すと、別で開いている方も更新されるはずです。

Pocket

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

次のHTML タグと属性が使えます: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>