HTML 5新機能websocketの学習とメモ-HP版(二)


前編:websocketの基礎知識の紹介
Websocketについては,インターネット上で検索された実装コードが多いが,PHPを用いたsocketサーバの資料は比較的少なく,検索された資料や独自の研究に基づいて,この文書を記す.
Websocketクライアントの具体的な実装コードとサーバー(PHP)のエンドコードの紹介、本文のコードはただ1つの小さいdemoで、簡単な通信を実現して、クライアントは1つの文字列を送信して、サーバーのエンドは文字列を受信してクライアントブラウザ(firefox)に応答して、中国語の文字列通信をサポートして、クライアントは正常に応答します
まずクライアントコードを分析し、websocketの紹介によると、MozWebSocket(host)を使用してサーバに接続要求を送信し、作成したsocketにイベントをバインドする方法が必要です.
onopen:socket接続が正常に確立されたときにトリガーされるイベント
onmessage:クライアントがサーバ側から返された情報を受信したときにトリガーされるイベント
onerror:接続の確立エラー時にトリガーされるイベント
onclose:接続解除時にトリガーされるイベント
client.htmlコード:
<script>
var socket;

/**
 *             websocket  ,host   localhost,       12345
 *    socket          
 */
function init(){
    var host = "ws://localhost:12345/websocket/server.php";
    try{
        socket = new MozWebSocket(host);
        log('WebSocket - status '+socket.readyState);
        socket.onopen    = function(msg){ log("Welcome - status "+this.readyState); };
        socket.onmessage = function(msg){ log("Received: "+msg.data); };
        socket.onclose   = function(msg){ log("Disconnected - status "+this.readyState); };
    } catch(ex) {
        log(ex);
    }
}

/**
 * send           
 */
function send(){
    var msg = $("msg").value;
    if (!msg) return false;
    
    $("msg").value="";
    try{
        socket.send(msg);
        log('Sent: '+msg);
    } catch(ex) {
        log(ex);
    }
}

//        
function $(id) {
    return document.getElementById(id);
}
function log(msg) {
    $("log").innerHTML+="<br>"+msg;
}
function onkey(event){
    if (event.keyCode == 13) send();
}
</script>
<body onload="init()">
 <h3>WebSocket v2.00</h3>
 <div id="log"></div>
 <input id="msg" type="textbox" onkeypress="onkey(event)"/>
 <button onclick="send()">Send</button>
</body>

次に、サーバー側の分析です.ここではPHPでサーバー側のsocketサーバーを構築します.PHPはマルチスレッドをサポートしていませんが、ここでは個人の学習demoにすぎません.重要なのは通信成功です.前の文章では、握手に成功するにはクライアントに応答する情報フォーマットが必要だと紹介しました.ここでは紹介しません.
まずはsocket_createなどの方法でsocketサーバを構築し、ポート番号は前のクライアントが要求したポート12345であり、コードは以下の通りである.
$master  = WebSocket("localhost",12345);
$sockets[] = $master;
function WebSocket($address,$port) {
    $master=socket_create(AF_INET, SOCK_STREAM, SOL_TCP)     or die("socket_create() failed");
    socket_set_option($master, SOL_SOCKET, SO_REUSEADDR, 1)  or die("socket_option() failed");
    socket_bind($master, $address, $port)                    or die("socket_bind() failed");
    socket_listen($master,20)                                or die("socket_listen() failed");
    echo "Server Started : ".date('Y-m-d H:i:s')."
"; echo "Master socket : ".$master."
"; echo "Listening on : ".$address." port ".$port."

"; return $master; }
サーバが正常に確立された後、クライアントが本サーバへの接続を要求した場合、socket_acceptなどの方法で新しいsocket接続を確立し,クライアントの要求情報を受信し,処理後,応答情報を返し,握手に成功した.
次は文字列通信で、クライアントsendから文字列情報が来て、サーバ側はクライアントにこの文字列を受信して返します.
まず、受信した情報を処理し、前編で紹介したデータ伝送フォーマットに従って、firefoxのFINはずっと1であり、RSV 1,2,3は0であり、テキストメッセージであればopcodeは1であるため、パケットの最初のデータは0 x 81であり、次いでmask値であり、firefoxから送られてきたデータはマスクが施されているので、mask値は1であり、後から7ビットがデータ情報長である.クライアントがhiを送信する例では、長さが2バイトであり、2番目のデータが0 x 82であり、ここでは拡張データが約束されていないため、拡張データ長バイトは存在しない.次は4つのデータのマスクです(ここではhi,2バイトの情報を送信するため125バイト未満であるため,マスクは3−6番目のデータであり,データ長によってマスクの位置も異なり,その7ビットが示す値が126であればマスクは5−8番目のデータであり,その7ビットが示す値が127であればマスクは11−14番目のデータである),その後クライアントから送信されるコンテンツデータ、受信したデータを処理するには、取得したマスクで順番にコンテンツデータと排他的OR(^)演算を行い、最初のコンテンツデータと最初のマスク排他的OR、2番目のコンテンツデータと2番目のマスク排他的OR...5番目のコンテンツデータと最初のマスク排他的OR...このようにして、終了までコンテンツを符号化する必要があります.
次に,サーバ側がクライアントに送信する応答情報であり,データフォーマットは受信した情報と同様であるが,マスクを生成する必要はなく,マスクビットは0であり,その後も4つのマスクデータは存在しない.
具体的なPHPコードは以下の通りです.
server.php
<?php
/**
 * php - websocket
 */

error_reporting(E_ALL);
set_time_limit(0);
ob_implicit_flush(true);

date_default_timezone_set("Asia/shanghai");
$sockets = array();
$users   = array();

$master  = WebSocket("localhost",12345);
$sockets[] = $master;

while(true){
    $changed = $sockets;
    socket_select($changed,$write=NULL,$except=NULL,NULL);
    foreach ($changed as $socket) {
        if ($socket == $master) {
            $client=socket_accept($master);
            if ($client !== false) {
                skConnect($client);
            }
        } else {
            $data = @socket_recv($socket,$buffer,2048,0);
            
            if ($data != 0) {
                $user = getuserbysocket($socket);
                
                if (!$user->handshake) {
                    dohandshake($user,$buffer);
                } else {
                    process($socket,$buffer);
                }
            }
        }
    }

    sleep(1);
}

//---------------------------------------------------------------

function WebSocket($address,$port) {
    $master=socket_create(AF_INET, SOCK_STREAM, SOL_TCP)     or die("socket_create() failed");
    socket_set_option($master, SOL_SOCKET, SO_REUSEADDR, 1)  or die("socket_option() failed");
    socket_bind($master, $address, $port)                    or die("socket_bind() failed");
    socket_listen($master,20)                                or die("socket_listen() failed");
    echo "Server Started : ".date('Y-m-d H:i:s')."
"; echo "Master socket : ".$master."
"; echo "Listening on : ".$address." port ".$port."

"; return $master; } function getuserbysocket($socket){ global $users; $found=null; foreach($users as $user){ if($user->socket==$socket){ $found=$user; break; } } return $found; } function skConnect($socket){ global $sockets,$users; $user = new User(); $user->id = uniqid(); $user->socket = $socket; $users[] = $user; $sockets[] = $socket; } function disconnect($socket){ global $sockets,$users; $found=null; $n=count($users); for($i=0;$i<$n;$i++){ if($users[$i]->socket==$socket){ $found=$i; break; } } if(!is_null($found)){ array_splice($users,$found,1); } $index = array_search($socket,$sockets); socket_close($socket); if($index>=0){ array_splice($sockets,$index,1); } } function getheaders($req){ $r=$h=$o=null; if(preg_match("/GET (.*) HTTP\/1\.1\r
/" ,$req,$match)){ $r=$match[1]; } if(preg_match("/Host: (.*)\r
/" ,$req,$match)){ $h=$match[1]; } if(preg_match("/Sec-WebSocket-Origin: (.*)\r
/",$req,$match)){ $o=$match[1]; } if(preg_match("/Sec-WebSocket-Key: (.*)\r
/",$req,$match)){ $key=$match[1]; } return array($r,$h,$o,$key); } function dohandshake($user,$buffer){ list($resource,$host,$origin,$strkey) = getheaders($buffer); $strkey .= "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; $hash_data = base64_encode(sha1($strkey,true)); $upgrade = "HTTP/1.1 101 Switching Protocols\r
" . "Upgrade: websocket\r
" . "Connection: Upgrade\r
" . "Sec-WebSocket-Accept: " . $hash_data . "\r
" . "Sec-WebSocket-Protocol: websocket\r
" . "\r
"; socket_write($user->socket,$upgrade,strlen($upgrade)); $user->handshake=true; return true; } function process($socket,$msg){ $action = unwrap($msg); say("< ".$action); send($socket, $action); } function send($client,$msg){ say("> ".$msg); $msg = wrap($msg); socket_write($client,$msg,strlen($msg)); return true; } function ord_hex($data) { $msg = ""; $l = strlen($data); for ($i= 0; $i< $l; $i++) { $msg .= dechex(ord($data{$i})); } return $msg; } function wrap($msg="") { $frame = array(); $frame[0] = "81"; $msg .= " is ok!"; $len = strlen($msg); $frame[1] = $len<16?"0".dechex($len):dechex($len); $frame[2] = ord_hex($msg); $data = implode("",$frame); return pack("H*", $data); } function unwrap($msg="") { $mask = array(); $data = ""; $msg = unpack("H*",$msg); $head = substr($msg[1],0,2); if (hexdec($head{1}) === 8) { $data = false; } else if (hexdec($head{1}) === 1) { $mask[] = hexdec(substr($msg[1],4,2)); $mask[] = hexdec(substr($msg[1],6,2)); $mask[] = hexdec(substr($msg[1],8,2)); $mask[] = hexdec(substr($msg[1],10,2)); $s = 12; $e = strlen($msg[1])-2; $n = 0; for ($i= $s; $i<= $e; $i+= 2) { $data .= chr($mask[$n%4]^hexdec(substr($msg[1],$i,2))); $n++; } } return $data; } function say($msg=""){ print_r($msg."
"); } class User{ var $id; var $socket; var $handshake; }

ここで梱包と解包はpackとunpackを使っていますが、もちろん他にもいろいろな方法があります.
サーバを構築するにはdosウィンドウの下でPHPでこのserverを実行する必要がある.phpファイル.
ネット上で1つの成熟したphpwebsocketのオープンソースプロジェクトを探して、興味のある学生はダウンロードして見て、住所:https://github.com/nicokaiser/php-websocket
参考資料:
PHP and websocket :http://code.google.com/p/phpwebsocket/