談談php中使用websocket-實例 http://blog.csdn.net/xueling022/article/details/52902358php
原本是搜一些html5 websocket資料看的,結果被引去看了php的socket編程。下面是一些簡單的例子,在命令行運行php腳本就行html
[命令行運行PHP]PHP中有一個php.exe文件,能夠用命令執行PHP腳本。如:D:/php.exe -f F:/test.php ; 能夠使用php.exe -h查看更多參數 :html5
server端:web
<?php /** * 服務器端代碼 * */ //確保在鏈接客戶端時不會超時 set_time_limit(0); //設置IP和端口號 $address = "localhost"; $port = 1234; //調試的時候,能夠多換端口來測試程序! //建立一個SOCKET if (($sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) === false) { echo "socket_create() 失敗的緣由是:" . socket_strerror(socket_last_error()) . "/n"; die; } //阻塞模式 if (socket_set_block($sock) == false) { echo "socket_set_block() 失敗的緣由是:" . socket_strerror(socket_last_error()) . "/n"; die; } //綁定到socket端口 if (socket_bind($sock, $address, $port) == false) { echo "socket_bind() 失敗的緣由是:" . socket_strerror(socket_last_error()) . "/n"; die; } //開始監聽 if (socket_listen($sock, 4) == false) { echo "socket_listen() 失敗的緣由是:" . socket_strerror(socket_last_error()) . "/n"; die; } do { if (($msgsock = socket_accept($sock)) === false) { echo "socket_accept() failed: reason: " . socket_strerror(socket_last_error()) . "/n"; die; } //發到客戶端 $msg = "welcome /n"; if (socket_write($msgsock, $msg, strlen($msg)) === false) { echo "socket_write() failed: reason: " . socket_strerror(socket_last_error()) ."/n"; die; } echo "讀取客戶端發來的信息/n"; $buf = socket_read($msgsock, 8192); echo "收到的信息: $buf /n"; socket_close($msgsock); } while (true); socket_close($sock); ?>
client端:編程
<?php /** * 客戶端代碼 */ error_reporting(0); set_time_limit(0); echo " TCP/IP Connection /n"; $service_port = 10001; $address = '127.0.0.1'; $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); if ($socket === false) { die; } else { echo "OK"; } echo "試圖鏈接 "; if (socket_connect($socket, $address, $service_port) == false) { $error = socket_strerror(socket_last_error()); echo "socket_connect() failed./n","Reason: {$error} /n"; die; } else { echo "鏈接OK/n"; } $in = "Hello World/r/n"; if (socket_write($socket, $in, strlen($in)) === false) { echo "socket_write() failed: reason: " . socket_strerror(socket_last_error()) ."/n"; die; } else { echo "發送到服務器信息成功!/n","發送的內容爲: $in /n"; } $out = ""; while ($out = socket_read($socket, 8192)) { echo "接受的內容爲: ".$out; } echo "關閉SOCKET…/n"; socket_close($socket); echo "關閉OK/n"; ?>
再看websocket協議,是HTTP協議升級來的。看其消息頭:服務器
因此server端須要解析一下,並返回握手的協議內容:websocket
在網上找到解析的相關代碼 phpwebsocket - url: http://code.google.com/p/phpwebsocket/swoole
// Usage: $master=new WebSocket("localhost",12345); class WebSocket{ var $master; var $sockets = array(); var $users = array(); var $debug = false; function __construct($address,$port){ error_reporting(E_ALL); set_time_limit(0); ob_implicit_flush(); $this->master=socket_create(AF_INET, SOCK_STREAM, SOL_TCP) or die("socket_create() failed"); socket_set_option($this->master, SOL_SOCKET, SO_REUSEADDR, 1) or die("socket_option() failed"); socket_bind($this->master, $address, $port) or die("socket_bind() failed"); socket_listen($this->master,20) or die("socket_listen() failed"); $this->sockets[] = $this->master; $this->say("Server Started : ".date('Y-m-d H:i:s')); $this->say("Listening on : ".$address." port ".$port); $this->say("Master socket : ".$this->master."/n"); while(true){ $changed = $this->sockets; socket_select($changed,$write=NULL,$except=NULL,NULL); foreach($changed as $socket){ if($socket==$this->master){ $client=socket_accept($this->master); if($client<0){ $this->log("socket_accept() failed"); continue; } else{ $this->connect($client); } } else{ $bytes = @socket_recv($socket,$buffer,2048,0); if($bytes==0){ $this->disconnect($socket); } else{ $user = $this->getuserbysocket($socket); if(!$user->handshake){ $this->dohandshake($user,$buffer); } else{ $this->process($user,$this->unwrap($buffer)); } } } } } } function process($user,$msg){ /* Extend and modify this method to suit your needs */ /* Basic usage is to echo incoming messages back to client */ $this->send($user->socket,$msg); } function send($client,$msg){ $this->say("> ".$msg); $msg = $this->wrap($msg); socket_write($client,$msg,strlen($msg)); $this->say("! ".strlen($msg)); } function connect($socket){ $user = new User(); $user->id = uniqid(); $user->socket = $socket; array_push($this->users,$user); array_push($this->sockets,$socket); $this->log($socket." CONNECTED!"); $this->log(date("d/n/Y ")."at ".date("H:i:s T")); } function disconnect($socket){ $found=null; $n=count($this->users); for($i=0;$i<$n;$i++){ if($this->users[$i]->socket==$socket){ $found=$i; break; } } if(!is_null($found)){ array_splice($this->users,$found,1); } $index=array_search($socket,$this->sockets); socket_close($socket); $this->log($socket." DISCONNECTED!"); if($index>=0){ array_splice($this->sockets,$index,1); } } function dohandshake($user,$buffer){ $this->log("/nRequesting handshake..."); $this->log($buffer); list($resource,$host,$origin,$key1,$key2,$l8b) = $this->getheaders($buffer); $this->log("Handshaking..."); //$port = explode(":",$host); //$port = $port[1]; //$this->log($origin."/r/n".$host); $upgrade = "HTTP/1.1 101 WebSocket Protocol Handshake/r/n" . "Upgrade: WebSocket/r/n" . "Connection: Upgrade/r/n" . //"WebSocket-Origin: " . $origin . "/r/n" . //"WebSocket-Location: ws://" . $host . $resource . "/r/n" . "Sec-WebSocket-Origin: " . $origin . "/r/n" . "Sec-WebSocket-Location: ws://" . $host . $resource . "/r/n" . //"Sec-WebSocket-Protocol: icbmgame/r/n" . //Client doesn't send this "/r/n" . $this->calcKey($key1,$key2,$l8b) . "/r/n";// . //"/r/n"; socket_write($user->socket,$upgrade.chr(0),strlen($upgrade.chr(0))); $user->handshake=true; $this->log($upgrade); $this->log("Done handshaking..."); return true; } function calcKey($key1,$key2,$l8b){ //Get the numbers preg_match_all('/([/d]+)/', $key1, $key1_num); preg_match_all('/([/d]+)/', $key2, $key2_num); //Number crunching [/bad pun] $this->log("Key1: " . $key1_num = implode($key1_num[0]) ); $this->log("Key2: " . $key2_num = implode($key2_num[0]) ); //Count spaces preg_match_all('/([ ]+)/', $key1, $key1_spc); preg_match_all('/([ ]+)/', $key2, $key2_spc); //How many spaces did it find? $this->log("Key1 Spaces: " . $key1_spc = strlen(implode($key1_spc[0])) ); $this->log("Key2 Spaces: " . $key2_spc = strlen(implode($key2_spc[0])) ); if($key1_spc==0|$key2_spc==0){ $this->log("Invalid key");return; } //Some math $key1_sec = pack("N",$key1_num / $key1_spc); //Get the 32bit secret key, minus the other thing $key2_sec = pack("N",$key2_num / $key2_spc); //This needs checking, I'm not completely sure it should be a binary string return md5($key1_sec.$key2_sec.$l8b,1); //The result, I think } function getheaders($req){ $r=$h=$o=null; if(preg_match("/GET (.*) HTTP/" ,$req,$match)){ $r=$match[1]; } if(preg_match("/Host: (.*)/r/n/" ,$req,$match)){ $h=$match[1]; } if(preg_match("/Origin: (.*)/r/n/" ,$req,$match)){ $o=$match[1]; } if(preg_match("/Sec-WebSocket-Key1: (.*)/r/n/",$req,$match)){ $this->log("Sec Key1: ".$sk1=$match[1]); } if(preg_match("/Sec-WebSocket-Key2: (.*)/r/n/",$req,$match)){ $this->log("Sec Key2: ".$sk2=$match[1]); } if($match=substr($req,-8)) { $this->log("Last 8 bytes: ".$l8b=$match); } return array($r,$h,$o,$sk1,$sk2,$l8b); } function getuserbysocket($socket){ $found=null; foreach($this->users as $user){ if($user->socket==$socket){ $found=$user; break; } } return $found; } function say($msg=""){ echo $msg."/n"; } function log($msg=""){ if($this->debug){ echo $msg."/n"; } } function wrap($msg=""){ return chr(0).$msg.chr(255); } function unwrap($msg=""){ return substr($msg,1,strlen($msg)-2); } } class User{ var $id; var $socket; var $handshake; }
繼承類:能夠本身按需寫,這裏我添加了幾行代碼,sendAll()等,很方便就改爲了一個即時的網頁版聊天室。socket
// Run from command prompt > php -q chatbot.demo.php include "websocket.class.php"; // Extended basic WebSocket as ChatBot class ChatBot extends WebSocket{ function process($user,$msg){ if (isset($user->first)) { $this->send($user->socket,''); $user->first = true; } $this->say("< ".$msg); switch($msg){ case "hello" : $this->send($user->socket,"hello human"); break; case "hi" : $this->send($user->socket,"zup human"); break; case "name" : $this->send($user->socket,"my name is Multivac, silly I know"); break; case "age" : $this->send($user->socket,"I am older than time itself"); break; case "date" : $this->send($user->socket,"today is ".date("Y.m.d")); break; case "time" : $this->send($user->socket,"server time is ".date("H:i:s")); break; case "thanks": $this->send($user->socket,"you're welcome"); break; case "bye" : $this->send($user->socket,"bye"); break; //default : $this->send($user->socket,$msg." not understood"); break; default : $this->sendAll($user, $msg); break; } } function sendAll($currentUser, $msg){ $usersList = $this->users; foreach ($usersList as $user){ if ($user !== $currentUser) // 本身發送的消息就再也不接收一次了 $this->send($user->socket, $msg); } } } $master = new ChatBot("localhost",12345);
客戶端代碼:性能
<html> <head> <title>WebSocket</title> <style> html,body{font:normal 0.9em arial,helvetica;} #log {width:440px; height:200px; border:1px solid #7F9DB9; overflow:auto;} #msg {width:330px;} </style> <script> var socket; function init(){ var host = "ws://localhost:12345/websocket/server.php"; try{ socket = new WebSocket(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); } $("msg").focus(); } function send(){ var txt,msg; txt = $("msg"); msg = txt.value; if(!msg){ alert("Message can not be empty"); return; } txt.value=""; txt.focus(); try{ socket.send(msg); log('Sent: '+msg); } catch(ex){ log(ex); } } function quit(){ log("Goodbye!"); socket.close(); socket=null; } // Utilities function $(id){ return document.getElementById(id); } function log(msg){ $("log").innerHTML+="<br>"+msg; } function onkey(event){ if(event.keyCode==13){ send(); } } </script> </head> <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> <button onclick="quit()">Quit</button> <div>Commands: hello, hi, name, age, date, time, thanks, bye</div> </body> </html>
PS:
* 這個websocket的類文件可能有一點問題,客戶端握手後應該接收的第一條信息都丟失了,沒細看代碼,之後再檢查吧。
樓主的文章仍是很不錯的,不過應用中單進程是死穴,php多進程+WbeSocket能夠看下workerman-chat,是用php寫的一個WebSocket聊天室,性能很強悍。 這個代碼是徹底阻塞,不能並行,只維持1個TCP鏈接沒有任何意義。仍是用swoole擴展吧,基於epoll/kqueue,能夠像Erlang/Golang之類維持上百萬TCP鏈接。 我也寫了一篇相似的文章 http://barretlee.com/websocket-with-php/