WebSocketとは
Webアプリケーションに対して、非同期通信を実現する方法の一つです。非同期通信とは、ブラウザを更新しなくても自動的に図表やテキストなどのコンテンツがリアルタイムに更新されていく仕組みとなります。
ここでは、ローカルなどに保存されている連番の画像ファイル(a1.png, a2.png, a3.png…)などを定期的に更新していく方法について述べます。サーバサイド(python側)で画像を読み込み、バイナリデータに変換し、それをローカル側(html)に逐次送信し、画像をアップロードしていきます(バイナリデータとは、画像を一種の文字列に変換したデータとなります)。
コード
HTML側(クライアントサイド: index.html)
<!doctype html>
<head>
<meta charset="utf-8" />
<title>WebSocket: sending image file</title>
<!-- WebSocket -->
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
<script>
$(document).ready(function() {
// WebSocket対応ブラウザかチェック
if (!window.WebSocket) {
// Firexoxかチェック
if (window.MozWebSocket) {
// Firefoxだったら、MozWebSocketを利用
window.WebSocket = window.MozWebSocket;
} else {
// Firefoxでなければ、Websocketを利用不可とメッセージ表示
$('#mes01').append("<li>Your browser doesn't support WebSockets.</li>");
}
}
// WebSocketの定義(サーバのpyファイルのrunと合わせること)
ws = new WebSocket('ws://localhost:8000/websocket');
// 接続時の処理
ws.onopen = function(evt) {
$('#mes01').append('<p>WebSocketを接続しました。</p>');
}
// サーバからメッセージを受け取った時の処理
ws.onmessage = function(evt) {
// 画像の表示(バイナリ)
blob = evt.data // <- pythonから受け取ったバイナリの画像ファイル
var reader = new FileReader();
reader.onload = function(){
var b64 = reader.result;
}
reader.readAsDataURL(blob);
reader.onload = function(evt) {
var data_url = reader.result;
$('#img1').attr('src',data_url);
var base64 = btoa(data_url);
}
}
// 切断時の処理
ws.onclose = function(evt) {
$('#mes01').append('<p>WebSocketを切断しました。</p>');
}
});
</script>
</head>
<!-- HTML記述箇所 -->
<body>
<div id="mes01"></div>
<img id="img1"/>
</body>
</html>
- 8行目〜55行目: websocketのことを記述している領域
- 12行目〜22行目: websocketが対応しているブラウザかどうかチェックする領域。非対応の場合のみ、60行目にあるbody内のdivタグ(id=mes01)に対して、その旨のメッセージを表示するようになっている。
- 27行目〜30行目(ws.open): websocketが接続されたときの処理を書く領域。ここでは、60行目にあるbody内のdivタグ(id=mes01)に対して、その旨のメッセージを表示するようになっている。
- 51行目〜53行目(ws.onclose): websocketが切断されたときの処理を書く領域。ここでは、60行目にあるbody内のdivタグ(id=mes01)に対して、その旨のメッセージを表示するようになっている。
- 33行目〜48行目(ws.onmessage): ここが一番重要。非同期通信中の処理を書く領域。evt.dataに対して、サーバのpython側から送信された文字列が格納されている。今回はバイナリデータによる画像が送信されているので、javascriptが解釈可能な形に変換している。最終的に44行目で、imgタグ(id=img01)に対して、この画像データを渡し、htmlにて表示している。
- 59行目〜62行目:実際のHTMLが記載されている領域。今回は、divタグとimgタグがある。ここに何を入れるのかについて、上の方で色々と定義されている。
なお、div領域に文字をスタックさせたい場合と、上書きしたい場合がある。スタックはappend, 書き換えはtextを利用する。以下は、id=mes01と名付けられたところに、pタグをスタックないしは上書きする処理である。ws.onmessageに入れてみて、出力を確認すると良い。
$('#mes01').append('これはスタック
');
$('#mes01').text('これは上書き
');
bottle-python側(サーバサイド: test.python)
from bottle import route, run, get, post, request, static_file, debug, template
from bottle.ext.websocket import GeventWebSocketServer
from bottle.ext.websocket import websocket
import time
import io
from PIL import Image
# *** 静的ファイルを扱うための設定 ***
@route('/aaa/<filename:path>') # pyがあるディレクトリを起点としたパス指定
def static(filename):
# フルパスで、静的ファイル(画像など)がある場所を指定(pwdコマンドで探すと良い)
return static_file(filename, root="/Users/omae/Desktop/main/ResearchProgramm/JSPrac/bottle_websocket_img/aaa")
@get('/')
def index():
return template('index.html')
# websocket
img_id=0
@get('/websocket', apply=[websocket])
def echo(ws):
global img_id
# whileにて非同期通信を無限ループ化
while True:
# 画像をロードしバイナリ化
tmpimg = Image.open("./aaa/chart"+str(img_id)+".png")
with io.BytesIO() as output:
tmpimg.save(output,format="PNG")
contents = output.getvalue()#バイナリ取得
# HTML側へ画像(バイナリデータ)を送信
ws.send(contents)
# 画像idの変更と通信間隔の設定
img_id = img_id + 1
time.sleep(0.1)
# サーバ起動
run(host="localhost", port=8000, server=GeventWebSocketServer)
- 8行目〜12行目:画像ファイルなど、ロードしたい静的ファイル置き場を定義。
- 15行目〜17行目:先ほど書いたhtmlファイルをロードする領域。
- 19行目〜39行目:非同期通信について定義している領域。while文を無限ループにすることで、常に非同期通信が行われているようになる。ループ中、img_idを1ずつ増加させることで、chart1.png, chart2.png…と画像が連番で呼び出されるようになっている。これをバイナリデータに変換し、ws.sendにて、クライアント側のhtmlファイルにバイナリ画像データを転送している。python側のws.sendで送られたデータを、html側のevt.dataで受け取ることができる。画像転送後、time.sleepにて0.1秒待機させ、次のループへ移る。