Websocket和PHP Socket编程

本来是搜一些html5 websocket资料看的,结果被引去看了php的socket编程。下面是一些简单的例子,在命令行运行php脚本就行

[命令行运行PHP]PHP中有一个php.exe文件,可以用命令执行PHP脚本。如:D:/php.exe -f F:/test.php ; 可以使用php.exe -h查看更多参数 :

server端:

[php] view plain copy
 
  1. <?php  
  2. /** 
  3.  * 服务器端代码 
  4.  * 
  5.  */  
  6. //确保在连接客户端时不会超时  
  7. set_time_limit(0);  
  8. //设置IP和端口号  
  9. $address = "localhost";  
  10. $port = 1234; //调试的时候,可以多换端口来测试程序!  
  11. //创建一个SOCKET  
  12. if (($sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) === false)  
  13. {  
  14.     echo "socket_create() 失败的原因是:" . socket_strerror(socket_last_error()) . "/n";  
  15.     die;  
  16. }  
  17. //阻塞模式  
  18. if (socket_set_block($sock) == false)  
  19. {  
  20.     echo "socket_set_block() 失败的原因是:" . socket_strerror(socket_last_error()) . "/n";  
  21.     die;  
  22. }  
  23. //绑定到socket端口  
  24. if (socket_bind($sock, $address, $port) == false)  
  25. {  
  26.     echo "socket_bind() 失败的原因是:" . socket_strerror(socket_last_error()) . "/n";  
  27.     die;  
  28. }  
  29. //开始监听  
  30. if (socket_listen($sock, 4) == false)  
  31. {  
  32.     echo "socket_listen() 失败的原因是:" . socket_strerror(socket_last_error()) . "/n";  
  33.     die;  
  34. }  
  35. do  
  36. {  
  37.     if (($msgsock = socket_accept($sock)) === false)  
  38.     {  
  39.         echo "socket_accept() failed: reason: " . socket_strerror(socket_last_error()) . "/n";  
  40.         die;  
  41.     }  
  42.     //发到客户端  
  43.     $msg = "welcome /n";  
  44.     if (socket_write($msgsock, $msg, strlen($msg)) === false)  
  45.     {  
  46.         echo "socket_write() failed: reason: " . socket_strerror(socket_last_error()) ."/n";  
  47.         die;  
  48.     }  
  49.     echo "读取客户端发来的信息/n";  
  50.     $buf = socket_read($msgsock, 8192);  
  51.     echo "收到的信息: $buf   /n";  
  52.      
  53.     socket_close($msgsock);  
  54. while (true);  
  55. socket_close($sock);  
  56. ?>  

client端:

[php] view plain copy
 
  1. <?php  
  2. /** 
  3.  * 客户端代码 
  4.  */  
  5.    
  6. error_reporting(0);  
  7. set_time_limit(0);  
  8. echo " TCP/IP Connection /n";  
  9. $service_port = 10001;  
  10. $address = '127.0.0.1';  
  11. $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);  
  12. if ($socket === false)  
  13. {  
  14.     die;  
  15. }  
  16. else  
  17. {  
  18.     echo "OK";  
  19. }  
  20. echo "试图连接 ";  
  21. if (socket_connect($socket, $address, $service_port) == false)  
  22. {  
  23.     $error = socket_strerror(socket_last_error());  
  24.     echo "socket_connect() failed./n","Reason: {$error} /n";  
  25.     die;  
  26. }  
  27. else  
  28. {  
  29.     echo "连接OK/n";  
  30. }  
  31. $in   = "Hello World/r/n";  
  32. if (socket_write($socket, $in, strlen($in)) === false)  
  33. {  
  34.     echo "socket_write() failed: reason: " . socket_strerror(socket_last_error()) ."/n";  
  35.     die;  
  36. }  
  37. else  
  38. {  
  39.     echo "发送到服务器信息成功!/n","发送的内容为: $in  /n";  
  40. }  
  41. $out  = "";  
  42. while ($out = socket_read($socket, 8192))  
  43. {  
  44.     echo "接受的内容为: ".$out;  
  45. }  
  46. echo "关闭SOCKET…/n";  
  47. socket_close($socket);  
  48. echo "关闭OK/n";  
  49. ?>  

再看websocket协议,是HTTP协议升级来的。看其消息头:

所以server端需要解析一下,并返回握手的协议内容:

在网上找到解析的相关代码 phpwebsocket - url:   http://code.google.com/p/phpwebsocket/

[php] view plain copy
 
  1. // Usage: $master=new WebSocket("localhost",12345);  
  2. class WebSocket{  
  3.   var $master;  
  4.   var $sockets = array();  
  5.   var $users   = array();  
  6.   var $debug   = false;  
  7.    
  8.   function __construct($address,$port){  
  9.     error_reporting(E_ALL);  
  10.     set_time_limit(0);  
  11.     ob_implicit_flush();  
  12.     $this->master=socket_create(AF_INET, SOCK_STREAM, SOL_TCP)     or die("socket_create() failed");  
  13.     socket_set_option($this->master, SOL_SOCKET, SO_REUSEADDR, 1)  or die("socket_option() failed");  
  14.     socket_bind($this->master, $address, $port)                    or die("socket_bind() failed");  
  15.     socket_listen($this->master,20)                                or die("socket_listen() failed");  
  16.     $this->sockets[] = $this->master;  
  17.     $this->say("Server Started : ".date('Y-m-d H:i:s'));  
  18.     $this->say("Listening on   : ".$address." port ".$port);  
  19.     $this->say("Master socket  : ".$this->master."/n");  
  20.     while(true){  
  21.       $changed = $this->sockets;  
  22.       socket_select($changed,$write=NULL,$except=NULL,NULL);  
  23.       foreach($changed as $socket){  
  24.         if($socket==$this->master){  
  25.           $client=socket_accept($this->master);  
  26.           if($client<0){ $this->log("socket_accept() failed"); continue; }  
  27.           else{ $this->connect($client); }  
  28.         }  
  29.         else{  
  30.           $bytes = @socket_recv($socket,$buffer,2048,0);  
  31.           if($bytes==0){ $this->disconnect($socket); }  
  32.           else{  
  33.             $user = $this->getuserbysocket($socket);  
  34.             if(!$user->handshake){ $this->dohandshake($user,$buffer); }  
  35.             else{ $this->process($user,$this->unwrap($buffer)); }  
  36.           }  
  37.         }  
  38.       }  
  39.     }  
  40.   }  
  41.   function process($user,$msg){  
  42.     /* Extend and modify this method to suit your needs */  
  43.     /* Basic usage is to echo incoming messages back to client */  
  44.     $this->send($user->socket,$msg);  
  45.   }  
  46.   function send($client,$msg){  
  47.     $this->say("> ".$msg);  
  48.     $msg = $this->wrap($msg);  
  49.     socket_write($client,$msg,strlen($msg));  
  50.     $this->say("! ".strlen($msg));  
  51.   }  
  52.   function connect($socket){  
  53.     $user = new User();  
  54.     $user->id = uniqid();  
  55.     $user->socket = $socket;  
  56.     array_push($this->users,$user);  
  57.     array_push($this->sockets,$socket);  
  58.     $this->log($socket." CONNECTED!");  
  59.     $this->log(date("d/n/Y ")."at ".date("H:i:s T"));  
  60.   }  
  61.   function disconnect($socket){  
  62.     $found=null;  
  63.     $n=count($this->users);  
  64.     for($i=0;$i<$n;$i++){  
  65.       if($this->users[$i]->socket==$socket){ $found=$i; break; }  
  66.     }  
  67.     if(!is_null($found)){ array_splice($this->users,$found,1); }  
  68.     $index=array_search($socket,$this->sockets);  
  69.     socket_close($socket);  
  70.     $this->log($socket." DISCONNECTED!");  
  71.     if($index>=0){ array_splice($this->sockets,$index,1); }  
  72.   }  
  73.   function dohandshake($user,$buffer){  
  74.     $this->log("/nRequesting handshake...");  
  75.     $this->log($buffer);  
  76.     list($resource,$host,$origin,$key1,$key2,$l8b) = $this->getheaders($buffer);  
  77.     $this->log("Handshaking...");  
  78.     //$port = explode(":",$host);  
  79.     //$port = $port[1];  
  80.     //$this->log($origin."/r/n".$host);  
  81.     $upgrade  = "HTTP/1.1 101 WebSocket Protocol Handshake/r/n" .  
  82.                 "Upgrade: WebSocket/r/n" .  
  83.                 "Connection: Upgrade/r/n" .  
  84.                                 //"WebSocket-Origin: " . $origin . "/r/n" .  
  85.                                 //"WebSocket-Location: ws://" . $host . $resource . "/r/n" .  
  86.                 "Sec-WebSocket-Origin: " . $origin . "/r/n" .  
  87.                     "Sec-WebSocket-Location: ws://" . $host . $resource . "/r/n" .  
  88.                     //"Sec-WebSocket-Protocol: icbmgame/r/n" . //Client doesn't send this  
  89.                 "/r/n" .  
  90.                     $this->calcKey($key1,$key2,$l8b) . "/r/n";// .  
  91.                         //"/r/n";  
  92.     socket_write($user->socket,$upgrade.chr(0),strlen($upgrade.chr(0)));  
  93.     $user->handshake=true;  
  94.     $this->log($upgrade);  
  95.     $this->log("Done handshaking...");  
  96.     return true;  
  97.   }  
  98.    
  99.   function calcKey($key1,$key2,$l8b){  
  100.         //Get the numbers  
  101.         preg_match_all('/([/d]+)/', $key1, $key1_num);  
  102.         preg_match_all('/([/d]+)/', $key2, $key2_num);  
  103.         //Number crunching [/bad pun]  
  104.         $this->log("Key1: " . $key1_num = implode($key1_num[0]) );  
  105.         $this->log("Key2: " . $key2_num = implode($key2_num[0]) );  
  106.         //Count spaces  
  107.         preg_match_all('/([ ]+)/', $key1, $key1_spc);  
  108.         preg_match_all('/([ ]+)/', $key2, $key2_spc);  
  109.         //How many spaces did it find?  
  110.         $this->log("Key1 Spaces: " . $key1_spc = strlen(implode($key1_spc[0])) );  
  111.         $this->log("Key2 Spaces: " . $key2_spc = strlen(implode($key2_spc[0])) );  
  112.         if($key1_spc==0|$key2_spc==0){ $this->log("Invalid key");return; }  
  113.         //Some math  
  114.         $key1_sec = pack("N",$key1_num / $key1_spc); //Get the 32bit secret key, minus the other thing  
  115.         $key2_sec = pack("N",$key2_num / $key2_spc);  
  116.         //This needs checking, I'm not completely sure it should be a binary string  
  117.         return md5($key1_sec.$key2_sec.$l8b,1); //The result, I think  
  118.   }  
  119.    
  120.   function getheaders($req){  
  121.     $r=$h=$o=null;  
  122.     if(preg_match("/GET (.*) HTTP/"               ,$req,$match)){ $r=$match[1]; }  
  123.     if(preg_match("/Host: (.*)/r/n/"              ,$req,$match)){ $h=$match[1]; }  
  124.     if(preg_match("/Origin: (.*)/r/n/"            ,$req,$match)){ $o=$match[1]; }  
  125.     if(preg_match("/Sec-WebSocket-Key1: (.*)/r/n/",$req,$match)){ $this->log("Sec Key1: ".$sk1=$match[1]); }  
  126.     if(preg_match("/Sec-WebSocket-Key2: (.*)/r/n/",$req,$match)){ $this->log("Sec Key2: ".$sk2=$match[1]); }  
  127.     if($match=substr($req,-8))                                                                  { $this->log("Last 8 bytes: ".$l8b=$match); }  
  128.     return array($r,$h,$o,$sk1,$sk2,$l8b);  
  129.   }  
  130.   function getuserbysocket($socket){  
  131.     $found=null;  
  132.     foreach($this->users as $user){  
  133.       if($user->socket==$socket){ $found=$user; break; }  
  134.     }  
  135.     return $found;  
  136.   }  
  137.   function     say($msg=""){ echo $msg."/n"; }  
  138.   function     log($msg=""){ if($this->debug){ echo $msg."/n"; } }  
  139.   function    wrap($msg=""){ return chr(0).$msg.chr(255); }  
  140.   function  unwrap($msg=""){ return substr($msg,1,strlen($msg)-2); }  
  141. }  
  142. class User{  
  143.   var $id;  
  144.   var $socket;  
  145.   var $handshake;  
  146. }  

继承类:可以自己按需写,这里我添加了几行代码,sendAll()等,很方便就改成了一个即时的网页版聊天室。

[php] view plain copy
 
  1. // Run from command prompt > php -q chatbot.demo.php  
  2. include "websocket.class.php";  
  3. // Extended basic WebSocket as ChatBot  
  4. class ChatBot extends WebSocket{  
  5.     function process($user,$msg){  
  6.           
  7.         if (isset($user->first)) {  
  8.             $this->send($user->socket,'');  
  9.             $user->first = true;  
  10.         }   
  11.           
  12.         $this->say("< ".$msg);  
  13.         switch($msg){  
  14.           case "hello" : $this->send($user->socket,"hello human");                       break;  
  15.           case "hi"    : $this->send($user->socket,"zup human");                         break;  
  16.           case "name"  : $this->send($user->socket,"my name is Multivac, silly I know"); break;  
  17.           case "age"   : $this->send($user->socket,"I am older than time itself");       break;  
  18.           case "date"  : $this->send($user->socket,"today is ".date("Y.m.d"));           break;  
  19.           case "time"  : $this->send($user->socket,"server time is ".date("H:i:s"));     break;  
  20.           case "thanks": $this->send($user->socket,"you're welcome");                    break;  
  21.           case "bye"   : $this->send($user->socket,"bye");                               break;  
  22.           //default      : $this->send($user->socket,$msg." not understood");              break;  
  23.           default      : $this->sendAll($user, $msg);              break;  
  24.         }  
  25.     }  
  26.     function sendAll($currentUser, $msg){  
  27.         $usersList = $this->users;  
  28.         foreach ($usersList as $user){  
  29.           if ($user !== $currentUser) // 自己发送的消息就不再接收一次了  
  30.             $this->send($user->socket, $msg);  
  31.         }  
  32.     }  
  33. }  
  34. $master = new ChatBot("localhost",12345);  

客户端代码:

<html>

<head>

<title>WebSocket</title>

<style>

 html,body{font:normal 0.9em arial,helvetica;}

 #log {440px; height:200px; border:1px solid #7F9DB9; overflow:auto;}

 #msg {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的类文件可能有一点问题,客户端握手后应该接收的第一条信息都丢失了,没细看代码,以后再检查吧。

原文地址:https://www.cnblogs.com/liangxiaofeng/p/5206456.html