Upload
ryuichi-tanaka
View
13.672
Download
0
Embed Size (px)
DESCRIPTION
WebSocketを使って位置情報のトラッキングをリアルタイム処理するシステムを紹介。Heroku+Node Ninjaを使用。
Citation preview
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