websocket.class.php 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. <?php
  2. /*
  3. 创建类websocket($config);
  4. $config结构:
  5. $config=array(
  6. 'address'=>'192.168.0.200',//绑定地址
  7. 'port'=>'8000',//绑定端口
  8. 'event'=>'WSevent',//回调函数的函数名
  9. 'log'=>true,//命令行显示记录
  10. );
  11. 回调函数返回数据格式
  12. function WSevent($type,$event)
  13. $type字符串 事件类型有以下三种
  14. in 客户端进入
  15. out 客户端断开
  16. msg 客户端消息到达
  17. 均为小写
  18. $event 数组
  19. $event['k']内置用户列表的userid;
  20. $event['sign']客户标示
  21. $event['msg']收到的消息 $type='msg'时才有该信息
  22. 方法:
  23. run()运行
  24. search(标示)遍历取得该标示的id
  25. close(标示)断开连接
  26. write(标示,信息)推送信息
  27. idwrite(id,信息)推送信息
  28. 属性:
  29. $users 客户列表
  30. 结构:
  31. $users=array(
  32. [用户id]=>array('socket'=>[标示],'hand'=[是否握手-布尔值]),
  33. [用户id]=>arr.....
  34. )
  35. */
  36. class websocket{
  37. public $log;
  38. public $event;
  39. public $signets;
  40. public $users;
  41. public $master;
  42. public function __construct($config){
  43. if (substr(php_sapi_name(), 0, 3) !== 'cli') {
  44. die("请通过命令行模式运行!");
  45. }
  46. error_reporting(E_ALL);
  47. set_time_limit(0);
  48. ob_implicit_flush();
  49. $this->event = $config['event'];
  50. $this->log = $config['log'];
  51. $this->master=$this->WebSocket($config['address'], $config['port']);
  52. $this->sockets=array('s'=>$this->master);
  53. }
  54. function WebSocket($address,$port){
  55. $server = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
  56. socket_set_option($server, SOL_SOCKET, SO_REUSEADDR, 1);
  57. socket_bind($server, $address, $port);
  58. socket_listen($server);
  59. $this->log('开始监听: '.$address.' : '.$port);
  60. return $server;
  61. }
  62. function run(){
  63. while(true){
  64. $changes=$this->sockets;
  65. @socket_select($changes,$write=NULL,$except=NULL,NULL);
  66. foreach($changes as $sign){
  67. if($sign==$this->master){
  68. $client=socket_accept($this->master);
  69. $this->sockets[]=$client;
  70. $user = array(
  71. 'socket'=>$client,
  72. 'hand'=>false,
  73. );
  74. $this->users[] = $user;
  75. $k=$this->search($client);
  76. $eventreturn = array('k'=>$k,'sign'=>$sign);
  77. $this->eventoutput('in',$eventreturn);
  78. }else{
  79. $len=socket_recv($sign,$buffer,2048,0);
  80. $k=$this->search($sign);
  81. $user=$this->users[$k];
  82. if($len<7){
  83. $this->close($sign);
  84. $eventreturn = array('k'=>$k,'sign'=>$sign);
  85. $this->eventoutput('out',$eventreturn);
  86. continue;
  87. }
  88. if(!$this->users[$k]['hand']){//没有握手进行握手
  89. $this->handshake($k,$buffer);
  90. }else{
  91. $buffer = $this->uncode($buffer);
  92. $eventreturn = array('k'=>$k,'sign'=>$sign,'msg'=>$buffer);
  93. $this->eventoutput('msg',$eventreturn);
  94. }
  95. }
  96. }
  97. }
  98. }
  99. function search($sign){//通过标示遍历获取id
  100. foreach ($this->users as $k=>$v){
  101. if($sign==$v['socket'])
  102. return $k;
  103. }
  104. return false;
  105. }
  106. function close($sign){//通过标示断开连接
  107. $k=array_search($sign, $this->sockets);
  108. socket_close($sign);
  109. unset($this->sockets[$k]);
  110. unset($this->users[$k]);
  111. }
  112. function handshake($k,$buffer){
  113. $buf = substr($buffer,strpos($buffer,'Sec-WebSocket-Key:')+18);
  114. $key = trim(substr($buf,0,strpos($buf,"\r\n")));
  115. $new_key = base64_encode(sha1($key."258EAFA5-E914-47DA-95CA-C5AB0DC85B11",true));
  116. $new_message = "HTTP/1.1 101 Switching Protocols\r\n";
  117. $new_message .= "Upgrade: websocket\r\n";
  118. $new_message .= "Sec-WebSocket-Version: 13\r\n";
  119. $new_message .= "Connection: Upgrade\r\n";
  120. $new_message .= "Sec-WebSocket-Accept: " . $new_key . "\r\n\r\n";
  121. socket_write($this->users[$k]['socket'],$new_message,strlen($new_message));
  122. $this->users[$k]['hand']=true;
  123. return true;
  124. }
  125. function uncode($str){
  126. $mask = array();
  127. $data = '';
  128. $msg = unpack('H*',$str);
  129. $head = substr($msg[1],0,2);
  130. if (hexdec($head{1}) === 8) {
  131. $data = false;
  132. }else if (hexdec($head{1}) === 1){
  133. $mask[] = hexdec(substr($msg[1],4,2));
  134. $mask[] = hexdec(substr($msg[1],6,2));
  135. $mask[] = hexdec(substr($msg[1],8,2));
  136. $mask[] = hexdec(substr($msg[1],10,2));
  137. $s = 12;
  138. $e = strlen($msg[1])-2;
  139. $n = 0;
  140. for ($i=$s; $i<= $e; $i+= 2) {
  141. $data .= chr($mask[$n%4]^hexdec(substr($msg[1],$i,2)));
  142. $n++;
  143. }
  144. }
  145. return $data;
  146. }
  147. function code($msg){
  148. $msg = preg_replace(array('/\r$/','/\n$/','/\r\n$/',), '', $msg);
  149. $frame = array();
  150. $frame[0] = '81';
  151. $len = strlen($msg);
  152. $frame[1] = $len<16?'0'.dechex($len):dechex($len);
  153. $frame[2] = $this->ord_hex($msg);
  154. $data = implode('',$frame);
  155. return pack("H*", $data);
  156. }
  157. function ord_hex($data) {
  158. $msg = '';
  159. $l = strlen($data);
  160. for ($i= 0; $i<$l; $i++) {
  161. $msg .= dechex(ord($data{$i}));
  162. }
  163. return $msg;
  164. }
  165. function idwrite($id,$t){//通过id推送信息
  166. if(!$this->users[$id]['socket']){return false;}//没有这个标示
  167. $t=$this->code($t);
  168. return socket_write($this->users[$id]['socket'],$t,strlen($t));
  169. }
  170. function write($k,$t){//通过标示推送信息
  171. $t=$this->code($t);
  172. return socket_write($k,$t,strlen($t));
  173. }
  174. function eventoutput($type,$event){//事件回调
  175. call_user_func($this->event,$type,$event);
  176. }
  177. function log($t){//控制台输出
  178. if($this->log){
  179. $t=$t."\r\n";
  180. fwrite(STDOUT, iconv('utf-8','gbk//IGNORE',$t));
  181. }
  182. }
  183. }