Websocket和PHP Socket编程

谈谈php中使用websocket-实例 http://blog.csdn.net/xueling022/article/details/52902358

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

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

server端:

<?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端需要解析一下,并返回握手的协议内容:

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

// 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()等,很方便就改成了一个即时的网页版聊天室。

// 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/

转载于:https://my.oschina.net/mickelfeng/blog/338301

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值