PHPのWebSocketを使用してリアルタイムにグラフを描画

f:id:haru-komugi:20140708130637j:plain
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などと組み合わせて、リアルタイムに測定値をグラフ化しようと思います。