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コード:
次に、サーバー側の分析です.ここではPHPでサーバー側のsocketサーバーを構築します.PHPはマルチスレッドをサポートしていませんが、ここでは個人の学習demoにすぎません.重要なのは通信成功です.前の文章では、握手に成功するにはクライアントに応答する情報フォーマットが必要だと紹介しました.ここでは紹介しません.
まずはsocket_createなどの方法でsocketサーバを構築し、ポート番号は前のクライアントが要求したポート12345であり、コードは以下の通りである.
次は文字列通信で、クライアント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
ここで梱包と解包はpackとunpackを使っていますが、もちろん他にもいろいろな方法があります.
サーバを構築するにはdosウィンドウの下でPHPでこのserverを実行する必要がある.phpファイル.
ネット上で1つの成熟したphpwebsocketのオープンソースプロジェクトを探して、興味のある学生はダウンロードして見て、住所:https://github.com/nicokaiser/php-websocket
参考資料:
PHP and websocket :http://code.google.com/p/phpwebsocket/
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/