SlideShare a Scribd company logo
WebSocketで
リアルタイム処理をする
@mapserver2007 / Ryuichi TANAKA
WebSocketといえば
リアルタイムなチャットがすぐ思いつく
が、ありふれてる上にチャットなんて別にブラウザで使わ
ない
LINE
もっと実用的なアプリを作る
WebSocket+位置情報でリアルタイムトラッキングWebSocket+位置情報でリアルタイムトラッキング
自転車、自動車、人間、なんでも監視できる
地図を使えばブラウザ開いているだけでリアルタイム監視
今やっていること
Aphrael(アフラエル)というシステムを開発中
その過程でWebSocketの勉強をしているので報告
Aphrael
自転車盗難防止システム
自転車に端末を設置(Android)し、監視するシステム
Android端末では、侵入検知(Bluetooth)、盗難検知(加速
度センサー)、移動検知(GPS)を行い、情報をサーバへ送信
する。
サーバはデータを保存、ソーシャルアプリ(Twiter、SMSサーバはデータを保存、ソーシャルアプリ(Twiter、SMS
等)、ブラウザ(Chrome)へ送信し監視者へ通知する。
システム概要
Android端末を自転車のサドルバッグに設置し、GPS、セ
ンサを利用して自転車を監視する
自転車の不正な動き(自転車を動かしたり)を検知したら
サーバへ送信し、管理者へ通知
自転車の盗難を検知したらトラッキング開始
自転車の盗難状況はChromeでリアルタイム監視自転車の盗難状況はChromeでリアルタイム監視
開発体制
共同開発になった。
https://github.com/yuanying
Android、Rails、インフラ担当
Android4.x
Rails4/Ruby2.0
https://github.com/mapserver2007
Chrome、node.js担当
Chrome27(2013年7月最新版)でのExtension
node.js v0.11.3(2013年7月でのnpm利用の最新版)
本当はv0.12.xでyield使いたいのでアップデートするかも
Androidもやる予定
ソースコード
https://github.com/aphrael
子プロジェクトが4つ(Rails/Android/Chrome/node.js)
システム構成
システム構成(俺担当)
ここを作ってる
急遽変更した点
Chromeとリアルタイム通信する箇所は当初は
GCM(Google Cloud Messaging for Chrome)を使って
いたが、node.jsに完全移行した。
変更した理由は、リクエスト回数制限(10000req/day)と
APIの機能不足
複数ユーザで連続通信した場合越える可能性が高い
機能が少ない。例えばGCMは接続する全てのクライアントに同機能が少ない。例えばGCMは接続する全てのクライアントに同
じデータを送りつけるためユーザ単位の制御ができない。クライ
アントの特定はChromeExtensionのChannelIdだが、インス
トールしたクライアントですべて同じIDになってしまうため個別
の制御ができない
外部サービスに頼るのは基本的によくない(仕様変更、勉強の観点
から)
Google Cloud Messaging for Chromeの基本について
はhttp://www.slideshare.net/mapserver2007/web-
20130525を参照してください。
Chrome+node.jsでやりたいこと
ChromeExtensionから画面に表示されている内容をDOM
で書き換えられるか。
Webページに設置した地図の位置をExtensionから変更できるか
WebSocketで受け取ったデータ(経緯度)の位置を表示する。
これにより、サーバが送信した位置情報をリアルタイムに
画面で描画することができるか。画面で描画することができるか。
Webページの地図をずっと開いておくだけ。リロードの必要も
ポーリングも必要ない。常にリアルタイムの位置情報をフリーハ
ンドで確認できる。
片付けるべき課題
WebSocket接続中に緯度経度情報を受け取って、表示中
の地図を更新できるか(正常系)
クライアント側で接続が切断された場合、すぐに再接続が
できるか(タイムアウトの検知=デフォルト不可)
サーバ側で接続が切断された場合またはサーバが再起動し
た場合、クライアントと再接続できるかた場合、クライアントと再接続できるか
基本的に利便性を優先するため、地図表示中に接続が切れ
てもすぐに再接続+更新処理の続行を可能にする(利用者に
は切断・再接続を意識させない作りにする)
コネクションがゾンビになったりしないような作りにする
(きちんと切断して開放する)
node.js選択の理由
実装が圧倒的に楽
WebSocketサーバを10行程度で書ける
HTTPサーバとWebSocketサーバを一つのソースで書け
る
WebSocketのサーバ、HTTP(REST API)サーバを同時に動かせ
るる
ライブラリが豊富
WebSocket絡みなら大抵ある。便利なものから基本的な
Socket.ioなど。
インストールが楽
npmとnvmで管理すればバージョンごとの動作も簡単
WebSocketとは
永続的な通信を行えることで、HTTPでは難しかったサー
バクライアントの双方向通信が簡単に実現できる
HTTPでは不可能ではないが、オーバヘッドが大きい
HTTPの様にクライアント→サーバの一方向ではなく、
サーバ→クライアントの通信が可能
接続はクライアントから行うが、確立してしまえば双方向通信が接続はクライアントから行うが、確立してしまえば双方向通信が
可能
GCMはChannelIdで特定するのか、サーバ→クライアントの接
続が可能
プロトコルは「ws://, wss://」
大抵のモダンブラウザ(Chrome14~、Firefox6~、Safari5~、
IE10~)なら対応している
WebSocketを使う意味
HTTPでポーリング、ロングポールを使ったら負けだと思
う
ポーリングは論外(無駄通信が多すぎる。コストが高すぎ)
ポーリングが許されるのは小学生まで
ロングポール(Comet)はオーバヘッドが多すぎる(大量の人数が同
時にコネクションを長時間保持することになる)。しかたなく使っ
ていた技術という感がある。時代遅れ。ていた技術という感がある。時代遅れ。
ロングポールが許されるのはLingrだけ
現代人ならWebSocket使いましょうよ
つまりWebSocketも使えないようなブラウザは捨てろ
ChromeExtensionの構成(manifest.json)
{
"manifest_version": 2,
"name": "Aphrael",
"description": "Aphrael for chrome",
"version": "0.0.1",
"permissions": [
"pushMessaging",
"notifications",
"tabs"
],],
"background": {
"scripts": ["notify.js"]
},
"content_scripts": [
{
"matches": [
"http://*/*"
],
"js": ["content_script.js", "jquery.min.js"],
"run_at": "document_end"
}
]
}
ChromeExtensionの構成(manifest.json)
{
"manifest_version": 2,
"name": "Aphrael",
"description": "Aphrael for chrome",
"version": "0.0.1",
"permissions": [
"pushMessaging",
"notifications",
"tabs"
],
初期状態から使用可能な
JavaScript
],
"background": {
"scripts": ["notify.js"]
},
"content_scripts": [
{
"matches": [
"http://*/*"
],
"js": ["content_script.js", "jquery.min.js"],
"run_at": "document_end"
}
]
}
JavaScript
読み込ませて使用する
JavaScript
background
D
D
backgroundに指定したスクリプトは「ビューを調査」で開くウインドウ
でデバッグ可能。ChromeExtension内のサンドボックスで実行される
スクリプトなので、表示してるページのJavaScriptと干渉することはない
(干渉できない)
content_script
backgroundスクリプトから呼び出す
chrome.tabs.executeScript(
tabId,
{file: "content_script.js"},
function(response) {
// callback
executeScriptで呼び出す
Chromeのサンドボックスを超えて表示しているページで
DOMアクセス可能
ただし表示しているページ内のJavaScriptへのアクセスは
できない(関数実行は不可)
// callback
}
);
ExtensionからWebSocketを使う
// WebSocket通信開始
var webSocket = new WebSocket(“ws://localhost:9222”);
// サーバからのメッセージを受信
webSocket.onmessage = function() {
// content_scriptを呼び出して画面を書き換える
};
// クローズ処理
webScoket.onclose = function() {…};webScoket.onclose = function() {…};
// エラー処理
webSocket.onerror = function() {…};
// クライアントからサーバへのメッセージ送信
webScoket.send(“message”);
// クローズ
webSocket.close();
ExtensionからWebSocketを使う
newした段階で接続される
接続状況はwebSocket.readyStateで確認できる
oncloseメソッドでサーバからの切断処理に対応
サーバからの切断を検知してオブジェクトのクリア処理などを書
// WebSocket通信開始
var webSocket = new WebSocket(“ws://localhost:9222”);
サーバからの切断を検知してオブジェクトのクリア処理などを書
く
ExtensionからWebSocketを使う
// WebSocket通信開始
var webSocket = new WebSocket(“ws://localhost:9222”);
// サーバからのメッセージを受信
webSocket.onmessage = function() {
// content_scriptを呼び出して画面を書き換える
}
// クローズ処理
webScoket.onclose = function() {…}webScoket.onclose = function() {…}
Aphraelでの実装(接続関係)
WebSocketオブジェクトを一つ保持して使いまわす
WebSocketオブジェクトを定期監視し、サーバとの接続
が切れたら再接続するようにする
AphraelのWebページをアクティブにしたときのみ監視。
タイムアウトを検知できないので、setIntervalで監視。
readyStateプロパティを確認して3(=切断済み)ならオブジェクreadyStateプロパティを確認して3(=切断済み)ならオブジェク
トをクリアし、再接続処理を実行する。
サーバから切断(サーバが落ちた場合)された場合、クライ
アントのWebSocketオブジェクトをクリアする(ゾンビに
ならないように)
onerrorメソッドで検知する。
Aphraelでの実装(タブ・ウィンドウ関係)
ChromeExtensionからはchrome.*を使ってAPIへアクセ
ス可能。タブ、ウィンドウ制御が可能
タブがアクティブになったとき
タブが削除されたとき
タブが更新されたとき
ウィンドウが作成されたときウィンドウが作成されたとき
ウィンドウが削除されたとき
ウィンドウを合体させたとき など
これらのイベントを検知し、Extensionを実行するタブを
取得し、Aphraelのページでのみ実行する。
複数のタブから同時にアクセス可能なので、開いているタ
ブ全てにWebSocket処理を実行する。
WebSocketサーバの実装
var WebSocketServer = require('ws').Server,
httpServer = require('http').createServer();
var server = new WebSocketServer({
server: httpServer
});
var connection = null;
server.on('connection', function(ws) {server.on('connection', function(ws) {
connection = ws;
ws.on('close', function(code) {
console.log(code);
});
});
httpServer.listen("9222");
WebSocketサーバの実装
10行程度で実装
wsモジュールを使用することでさらに簡単に実装
WebSocket関係のモジュールによってはChrome最新版との接
続がうまくいかない場合がある(例:websocket-serverモジュー
ル)。現時点ではwsモジュールで接続可能。
HTTPサーバの実装
var express = require('express');
var app = express();
app.get('/rest/position', function(req, res) {
var data = {
lat: req.query.lat,
lng: req.query.lng
};
try {try {
connection.send(JSON.stringify(data));
res.send(200);
}
catch (e) {
logger.error(e.message);
throw e;
}
});
app.listen("9223");
HTTPサーバの実装
HTTPでGETリクエストを受けてリクエストを
WebSocketプロトコルで送信する。
HTTPレスポンスはBodyなしで返すだけ
expressモジュールを使うと簡単にURLルーティングが可
能
WebSocket、HTTPサーバ両方動かす
一つのソース(server.js)にWebSocket、HTTPサーバを記
述し、同時に起動できるのが嬉しい
HTTPで受け取ったリクエストを簡単にWebSocketに渡せる
これにより簡単にHTTPで受け取ったリクエストを即座にブラウ
ザに反映可能
Chrome、node.jsのWebSocketでリアルタイ
ムに地図を書き換える
HTTPで緯度経度を受け取る
緯度経度をWebSocketでChromeに渡す
Chromeはonmessageメソッドで緯度経度を受け取り
DOMで地図を書き換える
地図を書き換えるには…?
Chrome、node.jsのWebSocketでリアルタイ
ムに地図を書き換える
トリッキーではあるが…以下のように実装
表示してるページのJavaScript関数は使えないが、DOMアクセ
スは可能なので、HTMLを埋め込み、イベントを発火させる
innerHTMLで画面上に緯度経度を書き込む
予めWebページに隠し要素を埋め込んでおき、DOMでボタンの
クリックイベントを実行
緯度経度を読み込み、GoogleMapを更新する
Demo
クラウドで動かす
Aphraelをクラウドで動かしてみた
本番は自鯖
Heroku+Node Ninja
Herokuでnode.jsを動かすときの注意点
HerokuではWebSocketが使えない(2013年7月現在)
同様のことはできる
Socket.ioでxhr-pollingを使えば見た目上変わらないが
WebSocketとは別の処理を書かなければならない。
そもそもポーリングである。
Heroku(無料枠)で1プロジェクトあたり複数のプロセスはHeroku(無料枠)で1プロジェクトあたり複数のプロセスは
動かせない
今回はWebSocketサーバとRESTサーバを動かす必要がある
プロジェクトを2つに分けなければならない
結論:Herokuでnode.js(WebSocketサーバ)を使うのは
断念。
Node Ninja
http://node-ninja.com/
無料(30日)。30日以降はメールで継続願を出す。
Node Ninjaの特長
WebSocket使用可能
複数のプロセス起動可能
WebSocket、RESTサーバを同時に動かせる
仮想マシンにSSH接続可能
Webサイトから起動・停止可能
Webサイトからログ確認可能Webサイトからログ確認可能
Githubコミット即時反映可能
今後の予定
地図移動の軌跡を書くようにする
実機との連携を試す
Android機を購入(Xperia mini)
DTIの3G(月額490円)
複数のユーザが利用できるように改良
認証認証
共有機能(許可したユーザのみ)
サーバサイドJSの勉強を進める
WebScoket
WebWorker

More Related Content

WebSocketでリアルタイム処理をする