Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?

リアルタイム通信について調べてみた

はじめに

この記事は OUCC アドベントカレンダー 2015の10日目の記事です.

昨日は@E_Rubikさんによる診療内科へ行こうでした.明日は@spring_rainingさんです.

空いていたので埋めたは良いものの,特に書けるようなことがなかったので,最近調べていたことをまとめました. Djangoでリアルタイムにアンケート集計をするアプリの機能改善をしていて,Python+Ajaxではどうしても速度がでないので,node.jsでWebSocketを使って一部を高速化をしようといった内容です.

間違っている,こうした方が良い等ありましたら,私(@okwrtdsh)までお願いします.

現状

  • Django+Ajaxで0.5秒に1回jsonを取得してflotr2.jsで描画
  • 数が多くなると遅延が無視できないレベル
  • その他いろいろな改善点(この記事では触れない)

やりたいこと

  • AjaxをやめてWebSocketを使う
  • 0.5秒毎ではなくDBに変更のある毎に
  • node.js触ってみたい

ということで,WebSocketとPostgreSQLのNOTIFY/LISTENについて調べてみました.

WebSocket

WebSocketとは

HTTPはリアルタイムに適したプロトコルではなく,HTTPにリアルタイム性を持たせるための多様な迂回方法が開発され,サーバ・クライアント間の双方向で途切れのない連続した通信が実現しました. WebScketはこれら既存の仕様を利用,迂回して双方向の通信を実現するのではなく,双方向通信を提供することを目的に新たに策定されたプロタコルです. WebScketはHTTPを取り去り永続的にTCPのような通信を行うことによって,HTTPよりもオーバーヘッドや機能制限を取り除いています.

![seqdiag](http://utils.uci-sys.jp/seqdiag?seqdiag{ クライアント -> サーバ [label = "HTTP GET Upgrade Request"]; クライアント <- サーバ [label = "HTTP 101 Switching Protocols Response"]; クライアント <- サーバ [diagonal, label = "WebSocket"]; クライアント <- サーバ [diagonal, label = "WebSocket"]; クライアント -> サーバ [diagonal, label = "WebSocket"]; クライアント <- サーバ [diagonal, label = "WebSocket"]; })

クライアントから,WebSocketへのアップグレードを求めるGETリクエストをサーバに送り,サーバがWebSocketに対応しているか判断し,両方ともに対応している場合はHTTPは取り去られWebSocketでの通信に切り替わります.以降,双方向から任意のデータを送ることができるようになります.

詳しくはこちらをご覧下さい.

node.jsのsocket.ioについて

古いブラウザはWebSocketをサポートしていません.socket.ioは,実行中のブラウザが対応している中で最も適切なリアルタイム通信技術を使って,WebSocketもしくはWebSocketライクな双方向通信APIをサーバとクライアントに提供しています.古いブラウザ(IE5.5以降)やモバイルブラウザ(iOS Safari, Android)にも対応しています.これに加えて,socket.ioは切断検出,自動再接続や,カスタムイベント,ネームスペース,などなど便利な機能がそろっています.

詳しくはこちらをご覧下さい.

PostgreSQL NOTIFY/LISTEN

NOTIFY/LISTEN

LISTEN channel_name;で監視を開始し,NOTIFY channel_name;で監視しているセッションに配信を行います.また,通知を送信する際にはpg_notify(channel_name, payload)関数を利用することもできます.pg_notify関数は、NOTIFY文が関数になったもので、チャンネル名やペイロード(通知の際に使用できる任意の文字列)が決まっていない場合に便利とされています.

詳しくはこちらをご覧下さい.

とりあえず作ってみる

イメージ??

![seqdiag](http://utils.uci-sys.jp/actdiag?actdiag{ 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 ->10 lane browser { label = "browser" 1 [label = "登録・変更"]; 9 [label = "データを受け取る"]; 10 [label = "描画"]; } lane django { label = "django server" 2 [label = "保存処理"]; } lane postgresql { label = "postgresql server" 3 [label = "INSERT OR UPDATE"]; 4 [label = "NOTIFY"]; 7 [label = "SQL VIEW"]; } lane nodejs { label = "node.js server" 5 [label = "LISTEN"]; 6 [label = "viewに問い合わせ"]; 8 [label = "結果をwebsocketでデータを送る"]; } })

postgresql server から通知を受け取る

通知を送るためにトリガ関数を作ります.listen_notificationチャンネルに通知を送るようにします.

CREATE OR REPLACE FUNCTION notify_trigger()
  RETURNS trigger AS
$BODY$
DECLARE
BEGIN
  PERFORM pg_notify('listen_notification', TG_TABLE_NAME || ',id,' || NEW.id );
  RETURN new;
END;
$BODY$
  LANGUAGE plpgsql;

トリガを作ります.staff_scheduleのテーブルに挿入,変更,削除があった後に先ほど作成したnotify_trigger関数を呼びます.※アンケートのプロジェクトが手元になかったので適当なプロジェクトを使っています.

CREATE TRIGGER staff_schedule_trigger
  AFTER INSERT OR UPDATE OR DELETE
  ON staff_schedule
  FOR EACH ROW
  EXECUTE PROCEDURE notify_trigger();

これで通知を送る側の設定は終わりです.node.js側でlisten_notificationチャンネルを監視するようにします.通知があると通知内容をconsoleに表示します.

var pg = require('pg');
var pgConString = "protocol://user:password@host:port/dbname";
pg.connect(pgConString, function(err, client){
  if(err){
    console.log(err);
    process.exit();
  }
  client.on('notification', function(msg){
    console.log(msg);
  });
  client.query("LISTEN listen_notification");
});

socket.ioでデータを送る

サーバ側の作成をします.カスタムイベントを作成してデータを送るようにします.クライアント(index.html)は後で説明します.

var fs = require("fs");
var http = require("http");
var socketio = require("socket.io");
var io;
var server = 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);
io = socketio.listen(server);
io.sockets.on("connection", function(socket){
  socket.on("change", function(dict_list){
    io.sockets.emit("change", dict_list);
  });
});

集計結果を返すためにviewを作ります.※集計するようなものが無かった為,適当にstaff毎の登録数を返しています.

create view staff_attend_days as
select staff_id as id, count(*) as total from staff_schedule
group by staff_id;

viewに問い合わせて結果を返す処理を追加します.

var pg = require('pg');
var pgConString = "protocol://user:password@host:port/dbname";
pg.connect(pgConString, function(err, client){
  if(err){
    console.log(err);
    process.exit();
  }
  client.on('notification', function(msg){
    console.log(msg);
    //追加 ここから
    client.query("SELECT * FROM staff_attend_days", function(err, result){
      if(err){
        console.log(err);
      }
      else{
        console.log(result.rows);
        io.sockets.emit("change", result.rows);
      }
    });
    //ここまで
  });
  client.query("LISTEN listen_notification");
});

これでサーバ側の設定は終わりです.クライアントで結果を受け取れるようにします.※とりあえず結果を表示するだけです.

<html>
<head>
  <meta charset="UTF-8">
  <title>log</title>
  <script src="/socket.io/socket.io.js"></script>
  <script type="text/javascript">
    var socketio = io.connect('http://localhost:8080');
    socketio.on("change", function (dict_list){
      showLog(dict_list);
    });
    function showLog(dict_list) {
      var logArea = document.getElementById('log');
      var newDiv = document.createElement('div');
      newDiv.innerHTML = ++i + " : " + JSON.stringify(dict_list);
      logArea.appendChild(newDiv);
    }
    i = 0;
  </script>
</head>
<body>
  <h1>Log</h1>
  <div id="log"></div>
</body>
</html>

これでDBに変更がある毎に,クライアント側でデータを受け取れるようになりました.

結果

  • consoleへの出力
{ name: 'notification',
  length: 49,
  processId: 83770,
  channel: 'listen_notification',
  payload: 'staff_schedule,id,88' }
[ { id: 2, total: '24' },
  { id: 1, total: '30' },
  { id: 3, total: '6' },
  { id: 4, total: '2' },
  { id: 5, total: '1' } ]
  • index.htmlのスクリーンショット index.html

まとめ

一人で試しているので実際のところ,速くなったとか全く分かりませんでした... オーバーヘッドが減ったことによりサーバへの負荷が減るのではないかと思います.(そもそもサーバ分けてるし...)

かなり雑ですが,以上です.今回作ったものはgithubにあげています.node.jsに関連は「Nodeクックブック(オライリージャパン)」を参考にしました.

宣伝

OUCCではバイトが楽しくて仕方がないという奇特なバイト畜部員を募集しています.

以下,ホームページより抜粋.

OUCCは、複数のIT企業とつながりを持ち、プログラマのアルバイトなどをさせていただいています。
OUCCに入れば、プログラマのアルバイトを紹介してもらえるでしょう。
最近では、C#を用いたソフトの保守・修正やPHPによるWebアプリ製作などを行っています。
詳しくはこちら

最後に

Web系が得意な2016年度新入生にはオススメのバイト先があります!!

興味がある方は私(@okwrtdsh)までご連絡下さい!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment