fuelphpとsocket.ioでのチャットシステムの作り方まとめ

はじめに

何度かまとめようと思ってたんですけど、なかなかちゃんとまとめられなかったので今回再度まとめてみようと思います。

socket.io単体での実装方法はよく見かけるのですが、LAMP環境で構築されたシステムと一緒に構築されるサンプルがあまり見かけられなかったので今回はこれの実装方法をまとめてみようと思います。

また、僕は普段LAMP環境でのwebサービスをメインに扱っているので極力node.jsの使用を最小限にすることを心がけて作業を進めました(node.js得意でないだけです)

システム構成

基本的にwebサイトと同じですが、チャットルームではページ遷移なしでの画面を更新するためにnode.jsを使用します。

通常のwebサイトとの1番の違いはjavascriptでクライアントからサーバへのデータ送信以外に、サーバからクライアントへのデータ送信も使用することでしょうか。

node.js(socket.io)を利用するとこれが簡単に実装できるので今回はこれを利用します

サーバ名 ソフトウェア 概要
webサーバ apache サーバプログラムはphpで実装します
dbサーバ mysql  
チャットサーバ nodel.js(socket.io)  

db設計

テーブル名 名前 概要
ユーザ users ユーザ情報を管理します
チャットルーム rooms チャットルームの情報を管理します
入室中ユーザ room_user チャットルームに入室中のユーザを管理します
チャットコメント comments チャット内でのコメントを管理します

今回しようするテーブルはこんなかんじでシンプルなものを想定します。
また、データベースの操作はwebサーバ(php)から行います。

実装方法(事前準備まで)

一気にすべて書くとごちゃごちゃしてわかりずらくなりそうなので、まずはhtmlとチャットサーバの接続の確立までを実装します。

今回の登場人物としては、ブラウザ(html)、チャットサーバ(node.js + socket.io)とwebサーバ(php)の3つとなります。
ざっくり行う処理は以下のようなことになります。

  1. チャットサーバとの接続処理(HTML)
  2. チャット開始をチャットサーバへ通知(HTML)
  3. チャット開始通知を受けたらtokenをHTMLへかえす(チャットサーバ)
  4. チャットサーバからtokenがかえってきたらwebサーバへ送信して共有する(HTML)
  5. チャットルームに入室中のユーザ情報として保存(webサーバ)

html

// 1. チャットサーバとの接続処理
var socket = io.connect("http://example.com:3000");
socket.on("connect", function() {

    // 2. チャット開始をnode.jsサーバへ通知
    socket.emit("onServerRecvStart", 1);

    // 4. node.jsからtokenがかえってきたらwebサーバへ送信して共有します
    socket.on("onClientRecvToken", function(_token) {
        $.ajax({
            type: "post",
            url: "http://example.com/start?id=" + "<?php echo $id; ?>",
            data: {
                "token": _token
            },
            success: function(res) {
                if (res == "OK") {
                    token = _token;
                }
            }
        });
    });
});

チャットサーバの実装(node.js)

var io = require("socket.io").listen(3000);
io.sockets.on("connection",function(socket) {

    // 3. チャット開始通知を受けたらtokenをhtmlにかえします
    socket.on("onServerRecvStart",function(data) {
        socket.emit("onClientRecvToken",socket.id);
    });
});

webサーバの実装(php

// 5. チャットルームに入室中のユーザ情報を保存
start.php
<?php
$token = Input::post("token");
$room_id = Input::get("id");
$user_id = Session::get("user_id");

// ログインチェックや$room_idの検証など・・・

// room_userテーブルに保存

ここまでの処理が完了すると、準備が完了で以下の状態になっています

  • HTMLとチャットサーバでの接続が確立している
  • webサーバでチャットルームにどのユーザが入室しているのかがわかる

全体イメージ
f:id:yoppy0066:20150524163442p:plain

実装方法(メイン処理)

ここからがメイン処理となります。やることは以下になります

  • 送信ボタンが押されたらコメントをサーバへ送信する
  • 他人が送信したコメントを受信して画面に表示する

また、流れとしては以下のような形になります

  1. 送信ボタンでwebサーバへコメントを送信(HTML)
  2. 送信されたコメントをdbに保存(webサーバ)
  3. 送信されたコメントと入室中のユーザのtokenをチャットサーバへ送信(webサーバ)
  4. 受け取ったtokenのユーザへコメントを送信します(チャットサーバ)
  5. 受信したコメントを画面に追加します(HTML)

今回、webサーバからチャットサーバへのメッセージ送信ではelephant.ioというライブラリを使用しました。
https://github.com/Wisembly/elephant.io

html

// 1. 送信ボタンでwebサーバへ送信
$("#submit").function() {
    $.ajax({
        type: 'post',
        url: "http://example.com/send?id=" + "<?php echo $id; ?>",
        data: {
          'message': $('#message').val(),
        },
        success: function(res) {
          if (res == 'OK') {
              // 送信したコメントを画面に追加します
          }
        }
    });
});

socket.on('onServerRecvMessage', function(data) {
    // 5. 受信したコメントを画面に追加します
}

webサーバの実装(php

// 2. コメントをdbに保存
message.php
<?php
$message = Input::post("message");
$room_id = Input::get("id");
$user_id = Session::get("user_id");

// ログインチェックや$room_idの検証など・・・
// commentsに保存

// 3. コメントと入室中のユーザtokenをnode.jsへ送信します
$elephant = new ElephantIO\Client("http://example.com:3000",'socket.io',1,false,true,true);

// $list_tokensは、room_usersから入室中のユーザのtokenを取得したもの
foreach ($list_token as $token) {
    $data = array(
        'message' => $message,
        'token' => $token,
    );
    $json = json_encode($data);
        
    $elephant->init();
    $elephant->emit('onServerRecvMessage',$json);
    $elephant->close();
}

チャットサーバの実装(node.js)

var io = require("socket.io").listen(3000);

io.sockets.on("connection",function(socket) {

    // ・・・

    // 4. 受け取ったtokenのユーザへコメントを送信します
    socket.on('onServerRecvMessage',function(data) {
       var json = JSON.parse(data);
       var json2 = {"type":json.type,"message":json.message};
       io.sockets.socket(json.token).emit('onClientRecvMessage',json2);
    });
});

全体イメージ
f:id:yoppy0066:20150524165246p:plain

まとめ

普段使っているwebサービスにnode.jsサーバを追加することによって簡単にチャットシステムが構築できました。
node.jsがバリバリかける人だったらわざわざphp使ったりしたりしないのでしょうが、、、
今回は最低限の実装です。
アクセス増大などにより、チャットサーバの負荷分散が必要になった場合はstoreタイプをredisに変更することで負荷分散も可能になるかと思います。
機会があればこれのサンプルものせて見たいと思います

あと、以前に以下のページでnode.jsとelephant.ioの導入方法をまとめたので参考になれば幸いです
phpでsocket.ioを使うときのインストール方法をメモ - とりあえずphpとか

以上です