PHPのWebSocketを使用してリアルタイムにグラフを描画
PHPのWebSocketを使用して、リアルタイムにグラフを描画します。
必要となる機能は、PHPのソケットサーバー、GoogleChart、ChromeなどのWebSocketが利用できるブラウザになります。
ブラウザ側のソース
index.html
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <script type="text/javascript" src="https://www.google.com/jsapi"></script> <script src="//code.jquery.com/jquery-1.11.0.min.js"></script> <script src="//code.jquery.com/jquery-migrate-1.2.1.min.js"></script> <script src="index.js"></script> <!--<link rel="stylesheet" href="reset.css" type="text/css" media="all">--> <!--<link rel="stylesheet" href="index.css" type="text/css" media="all">--> </head> <body> <table> <tr> <th>WebScoketグラフ</th> <th>WebSocketログ</th> </tr> <tr> <td><div id="chart"></div></td> <td><div id="message"></div></td> </tr> </table> </body> </html>
index.js
//グーグルチャートの読み込み google.load("visualization", "1", {packages:["corechart"]}); //jQueryスタート $(function() { //グラフ用データ var chart; var dataArray = [['Date', 'distance']]; //初期化関数 /*------------------------------------------------------------------------*/ function init(){ //グラフ初期化 chartInit(); } //グラフ初期化 /*------------------------------------------------------------------------*/ function chartInit(){ //0でデーターを埋めておく var i; for(i=1;i<100;i++){ dataArray[i] = ["",0]; } //グラフの作成 google.setOnLoadCallback(drawChart); chart = new google.visualization.LineChart(document.getElementById('chart')); } //グラフの描画 /*------------------------------------------------------------------------*/ function drawChart() { //配列からグラフを描画(初期化) var data = google.visualization.arrayToDataTable( dataArray ); chart.draw(data); //WebScoketの開始 webSocketInit(); } //WebScoketの開始 /*------------------------------------------------------------------------*/ function webSocketInit(){ //URL指定 var wsUri = "ws://localhost:9000/"; websocket = new WebSocket(wsUri); //ソケットオープン websocket.onopen = function(ev) { $('#message').append("<p>Connected!</p>"); }; //メッセージ取得イベント websocket.onmessage = function(ev) { //メッセージを解析 var msg = JSON.parse(ev.data); var date = msg.date; var distance = msg.distance; $('#message').prepend( "<p>date:" + date + " distance:" + distance + "</p>" ); //グラフの配列データーに追加 dataArray.push([date , distance ]); //グラフの配列データーの頭を削除 if( dataArray.length >= 100 ){ dataArray.splice( 1 , 1 ); } //グラフへ反映 var data = google.visualization.arrayToDataTable( dataArray ); chart.draw(data); }; //エラー処理 websocket.onerror = function(ev){$('#message').append("<div class=\"system_error\">Error Occurred - "+ev.data+"</div>");}; websocket.onclose = function(ev){$('#message').append("<div class=\"system_msg\">Connection Closed</div>");}; } init(); });
サーバー側のソース
WebSocketをクライアント(ブラウザ)へ送り出すserver.php
<?php // $host = 'localhost'; //host $port = '9000'; //port $null = NULL; //null var //Create TCP/IP sream socket $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); //reuseable port socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1); //bind socket to specified host socket_bind($socket, 0, $port); //listen to port socket_listen($socket); //create & add listning socket to the list $clients = array($socket); //start endless loop, so that our script doesn't stop while (true) { //manage multipal connections $changed = $clients; //returns the socket resources in $changed array socket_select($changed, $null, $null, 0, 10); //check for new socket if (in_array($socket, $changed)) { $socket_new = socket_accept($socket); //accpet new socket $clients[] = $socket_new; //add socket to client array $header = socket_read($socket_new, 1024); //read data sent by the socket perform_handshaking($header, $socket_new, $host, $port); //perform websocket handshake socket_getpeername($socket_new, $ip); //get ip address of connected socket //$response = mask(json_encode(array('type'=>'system', 'message'=>$ip.' connected'))); //prepare json data //send_message($response); //notify all users about new connection //make room for new socket $found_socket = array_search($socket, $changed); unset($changed[$found_socket]); } // $response_text = mask(json_encode( array('type'=>'usermsg', 'date'=> date('D, d M Y H:i:s') , 'distance'=> rand(1,1000) ) ) ); // send_message($response_text); //send data sleep(1); } // close the listening socket socket_close($sock); function send_message($msg) { global $clients; foreach($clients as $changed_socket) { @socket_write($changed_socket,$msg,strlen($msg)); } return true; } //Unmask incoming framed message function unmask($text) { $length = ord($text[1]) & 127; if($length == 126) { $masks = substr($text, 4, 4); $data = substr($text, 8); } elseif($length == 127) { $masks = substr($text, 10, 4); $data = substr($text, 14); } else { $masks = substr($text, 2, 4); $data = substr($text, 6); } $text = ""; for ($i = 0; $i < strlen($data); ++$i) { $text .= $data[$i] ^ $masks[$i%4]; } return $text; } //Encode message for transfer to client. function mask($text) { $b1 = 0x80 | (0x1 & 0x0f); $length = strlen($text); if($length <= 125) $header = pack('CC', $b1, $length); elseif($length > 125 && $length < 65536) $header = pack('CCn', $b1, 126, $length); elseif($length >= 65536) $header = pack('CCNN', $b1, 127, $length); return $header.$text; } //handshake new client. function perform_handshaking($receved_header,$client_conn, $host, $port) { $headers = array(); $lines = preg_split("/\r\n/", $receved_header); foreach($lines as $line) { $line = chop($line); if(preg_match('/\A(\S+): (.*)\z/', $line, $matches)) { $headers[$matches[1]] = $matches[2]; } } $secKey = $headers['Sec-WebSocket-Key']; $secAccept = base64_encode(pack('H*', sha1($secKey . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'))); //hand shaking header $upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" . "Upgrade: websocket\r\n" . "Connection: Upgrade\r\n" . "WebSocket-Origin: $host\r\n" . "WebSocket-Location: ws://$host:$port/demo/shout.php\r\n". "Sec-WebSocket-Accept:$secAccept\r\n\r\n"; socket_write($client_conn,$upgrade,strlen($upgrade)); }
使い方
コマンドプロンプトなどから
$ php server.php
として、WebSocketなサーバーを立ち上げ、ブラウザでindex.htmlの置いてある場所(http://localhost/yourdir/index.html)にアクセスすればソケット通信を開始してグラフの描画が行われます。
ソケット通信の更新間隔は1秒ごとがよさげなようです。これ以上間隔を短くしてもブラウザが止まってしまいます。
今後はこれをArduinoなどと組み合わせて、リアルタイムに測定値をグラフ化しようと思います。