[Node.js] Socket.ioで双方向通信チャットアプリを構築 〜 JSおくのほそ道 #005 

こんにちはほそ道です。
今回はここまで学んだ技術、コールバック・イベント処理/モジュールの利用/HTTP通信を活用しつつ、
WebSocketによる双方向リアルタイム通信アプリケーションを作ってみます。

[目次はこちら](http://qiita.com/setzz/items/ad9a9c99a4fda041a563)

WebSocketについては[Wikipedia](http://ja.wikipedia.org/wiki/WebSocket)にいい感じにまとまってますが
ほそ道が感じた良さを一言でいうと
「ユーザアクション無しにサーバーからクライアントへのpush通信が可能となる」という事ですね。
指定時間にサーバからお知らせ等のアクションも可能となります。

# チャットアプリケーションの実装

というわけで作ってみました。
非常にベタなんですが。。
チャットアプリケーションです。
今回はサーバ側とクライアント側の両方に実装が必要となります。
また、新しくWebSocketを実現する為の **socket.ioモジュール** が登場します。
アプリケーションの骨格が見えるよう、なるべく基本機能のみを用い、jQueryなども使わずに組みました。

## 構成

構成は下記の様になってます。
socket.ioのインストールは
`npm install socket.io`
でおこないます。
app.jsがサーバ側実装、index.htmlがユーザにレスポンスするhtmlです。

```
.
├── app.js
├── index.html
└── node_modules
```

## サーバサイドの実装

### 実装

```js:app.js
// 1.モジュールオブジェクトの初期化
var fs = require("fs");
var server = require("http").createServer(function(req, res) {
     res.writeHead(200, {"Content-Type":"text/html"});
     var output = fs.readFileSync("./index.html", "utf-8");
     res.end(output);
}).listen(8080);
var io = require("socket.io").listen(server);

// ユーザ管理ハッシュ
var userHash = {};
 
// 2.イベントの定義
io.sockets.on("connection", function (socket) {
 
  // 接続開始カスタムイベント(接続元ユーザを保存し、他ユーザへ通知)
  socket.on("connected", function (name) {
    var msg = name + "が入室しました";
    userHash[socket.id] = name;
    io.sockets.emit("publish", {value: msg});
  });

  // メッセージ送信カスタムイベント
  socket.on("publish", function (data) {
    io.sockets.emit("publish", {value:data.value});
  });

  // 接続終了組み込みイベント(接続元ユーザを削除し、他ユーザへ通知)
  socket.on("disconnect", function () {
    if (userHash[socket.id]) {
      var msg = userHash[socket.id] + "が退出しました";
      delete userHash[socket.id];
      io.sockets.emit("publish", {value: msg});
    }
  });
});
```

### 解説

コメント付けした1.がオブジェクトの初期化で、2.がアプリの機能実装を行っています。

#### オブジェクトの初期化
前回やった`require("http").createServer`でhttpサーバを立てています。
レスポンスは`fs`モジュールで読み込んだindex.htmlです。
`var io = require("socket.io").listen(server);`の部分でWebSocketの機能を追加しています。  

#### アプリの機能実装
大外の`io.sockets.on("connection", 〜`で通信開始イベントが発生し、
内側にコールバックとして接続開始、メッセージ送信、接続終了の３つのイベントを設定しています。
コールバックの引数`socket`に色々な接続情報が格納されています。

今回、ユーザの入室と退出を他のユーザにも伝える為に`userHash `というユーザを管理する入れ物を作りました。
socket.io自体にもkey-valueで値を保存する仕組みがあるのですが、
コード量の削減と、骨格が見えやするなるようにする為に自分で仕組みを用意しました。
Keyがsocket.id, Valueがクライアント側で生成するチャットユーザ名です。

接続開始はAさん入室時に他ユーザにAさん入室を通知します。
メッセージ送信は全ユーザ向け、自分以外全員、特定ユーザなど出来ますが今回実装では全ユーザ向けに配信します。
接続終了に関しては組込イベント`disconnect`で、Aさん退出時に他ユーザにAさん退出を通知します。


## クライアントサイドの実装

### 実装
```html:index.html
<html>
<head>
  <meta charset="UTF-8">
  <title>ホソミチチャット</title>
</head>
<body>
  <input type="text" id="msg_input" style="width:200px;" />
  <button onclick="publishMessage();">語る</button>
  <div id="msg"></div>
  <script src="/socket.io/socket.io.js"></script>
  <script type="text/javascript">
    // 1.イベントとコールバックの定義
    var socketio = io.connect('http://localhost:8080');
   
    socketio.on("connected", function(name) {});
    socketio.on("publish", function (data) { addMessage(data.value); });
    socketio.on("disconnect", function () {});

    // 2.イベントに絡ませる関数の定義
    function start(name) {
      socketio.emit("connected", name);
    }

    function publishMessage() {
      var textInput = document.getElementById('msg_input');
      var msg = "[" + myName + "] " + textInput.value;
      socketio.emit("publish", {value: msg});
      textInput.value = '';
    }
    
    function addMessage (msg) {
      var domMeg = document.createElement('div');
      domMeg.innerHTML = new Date().toLocaleTimeString() + ' ' + msg;
      msgArea.appendChild(domMeg);
    }

    // 3.開始処理
    var msgArea = document.getElementById("msg");
    var myName = Math.floor(Math.random()*100) + "さん";
    addMessage("貴方は" + myName + "として入室しました");
    start(myName);
  </script>
</body>
</html>
```

### 解説

主にscriptタグの中について見ていきます。
1.でイベントの準備、2.で機能実装、3.が入室された時の開始処理となります。

#### イベントの準備
`connected`が開始イベントで、サーバにユーザ名を伝え、ユーザ入室情報が配信されます。
`publish`メッセージ配信イベントです。コールバックとして画面にメッセージを表示させます。
`disconnect`はブラウザを閉じたときなので何もせずサーバに処理を任せます。

### 開始処理
ユーザ名をランダムな数字でテキトーに生成してみました。


# チャットアプリケーションを動かしてみる

## 入室イベントの確認

それでは使ってみましょう。３画面で連動性を確認します。

![スクリーンショット 2014-05-18 14.00.18.png](https://qiita-image-store.s3.amazonaws.com/0/39330/bd84d6cc-3908-c88f-b970-5ec7d42165ce.png)

入室したユーザにはランダムに生成された名前が振られます。
先に入室したユーザに後から入室したユーザの入室イベントが配信されています。

## メッセージの送信確認
![スクリーンショット 2014-05-18 14.02.19.png](https://qiita-image-store.s3.amazonaws.com/0/39330/14c16c6b-37c2-154b-7831-380ede61e08f.png)

メッセージを送信すると全ユーザに配信され、チャットが出来ています。

## 退出通知の配信
![スクリーンショット 2014-05-18 14.03.12.png](https://qiita-image-store.s3.amazonaws.com/0/39330/0dc41929-7098-bd06-cbdf-65109bef518b.png)

一番上のブラウザを閉じてみました。
残されたユーザには退出イベントがメッセージで通知されます。

## WebSocket通信の確認
という訳でもっとちゃんとしたアプリケーションにするには詰める部分は色々ありますが
簡易的なアプリケーションが実装できました！
ちなみにWebSocketとして動いている事も確認しておきます。

![スクリーンショット 2014-05-18 14.15.17.png](https://qiita-image-store.s3.amazonaws.com/0/39330/6e8637c2-0b88-e7f0-cf01-df16c1138a51.png)

４番目のリクエスト応答が101:SwitchingProtocolのステータスコードを返し、
各種ヘッダ情報もWebsocketの条件を満たした通信内容となっています。

# Node.jsまとめ
というわけでここまでNode.jsをやってきましたが、次回より別のテーマに移ろうと思います。
ほそ道的Node.jsの雑感をまとめます。

- Nodeの良いと思った点
    - 小規模で簡素なツール等を作るには相当強力かつ生産性が高い
    - 簡素なWebアプリケーションを作る分には記述量は少なく、色々なミドルウェアの準備も不要
    - アプリケーション構築の為に覚える言語が少なくて良い。サーバサイドもクライアントサイドもJsが出来れば良い。
- Nodeの難しそうな点
    - 複雑な処理を組もうと思った場合に非同期の処理順番制御と高速性の担保等、設計時に考えるべき要素が多い。
    - コールバックが多くなり過ぎコードの可読性が下がる可能性がある。これも設計をしっかりやらないと破綻しそう。

まあちゃんとしたものを作るならそれなりの学習は必要ってことですね。
シンプルなものを作るときの爆速感は気に入りました。

今回は以上です。

次回はビルドツールなんぞを紹介しようと思います。