English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Notas detalladas sobre el uso de WebSocket en PHP, para su referencia.
Servidor:
<&63;php //Enviar usuario que se une //enviar1Enviar información //enviar2Salir de la sesión error_reporting(E_ALL ^ E_NOTICE); ob_implicit_flush(); //file_put_contents('lz.text','123', FILE_APPEND); $sk=new Sock('127.0.0.1',8000); $sk->run(); class Sock{ public $sockets; public $users; public $master; private $sda=array();//Datos recibidos private $slen=array();//Longitud total de datos private $sjen=array();//Longitud de datos recibidos private $ar=array();//Clave de cifrado private $n=array(); public function __construct($address, $port){ $this->master=$this->WebSocket($address, $port);//resource(2, Socket) //El servidor está escuchando var_dump("master:"); var_dump($this->master); $this->sockets=array($this->master);//array (size=1) 0 => resource(2, Socket) . Ejecutar dos php aún así //var_dump($this->sockets); //file_put_contents('lz.text',var_dump($this)->sockets), FILE_APPEND); } function run(){ while(true){ $changes=$this->sockets;//$changes由多变1,但$this->sockets虽然只是稳定的+1; $write=NULL; $except=NULL; //1.运行select()系统调用在给定阵列插座与指定的超时 //2.没有接收到数据就会一直处于阻塞状态 //3.如果没有client过来,进程将一直阻塞,直到有client访问,返回1。 //4.此时返回的changes,不是曾经的changes。虽然还只是一条记录,但已经不是服务器而是客户端 /*select的特殊作用:!!!!!!! 初始为array(0=>resource(2, Socket)) 1,初始状态返回为array(0=>resource(2, Socket))。但socket_accept可以得到resource(3, Socket) 2,初始状态返回为array(0=>resource(2, Socket),1=>resource(3,Socket))。 El cliente que viene es resource(3,Socket)。则返回的数据为resource(3,Socket).!!! */ var_dump($changes); $rr=socket_select($changes,$write,$except,NULL); var_dump($changes); var_dump("---*---"); //exit; /* file_put_contents('lz.text',json_encode($changes), FILE_APPEND); file_put_contents('lz.text','-----', FILE_APPEND);*/ foreach($changes as $sock){ //El cliente que se conecta al host //$this->master siempre es resource(2, Socket)。相当于一个缓存。两种情况:1:为空,使进程阻塞。2:存储刚接收的client。 if($sock==$this->master){ //---Aquí solo se utiliza para almacenar datos. //Después de crear el socket socket_create(), necesariamente debe usarse socket_bind() para nombrar, informar al socket que escucha socket_listen(), esta función aceptará las conexiones entrantes, el enchufe. //¡Una vez conectado con éxito, se devolverá un nuevo recurso de conexión de socket !!, que se puede utilizar para la comunicación. Si hay múltiples conexiones en el socket, se utilizará la primera. //如果没有挂起的连接,socket_accept()将阻塞直到连接成为现在。如果使用了非阻塞套接字且已使用socket_set_blocking()或socket_set_nonblock(),错误将返回。 //返回socket_accept()插座资源不得用于接受新的连接。原来的监听插座仍然开放,但是,仍然是可重复使用的。 $client=socket_accept($this->master); //resource(3, Socket)。表示接受请求,并创建一个子链接!! //var_dump($client); //exit; $key=uniqid(); $this->sockets[]=$client; $this->users[$key]=array( 'socket'=>$client, 'shou'=>false ); /* array (size=1) "57d607085f92a' => //$key array (size=2) 'socket' => resource(3, Socket) //$socket的表现都一样,只有通过$key区分 'shou' => boolean false */ // file_put_contents('lz.text',json_encode($this->users), FILE_APPEND); } else{ //---此处服务器与客户端发信息 $len=0; $buffer=''; do{ /* int socket_recv ( resource socket, string &buf, int len, int flags ) resource socket 是生成的套接字 string &buf 是接收缓冲区 int len 是你打算接收的长度 int flags 是一个标志 0x1 数据应该带外发送,所谓带外数据就是TCP紧急数据 0x2 将有用的数据复制到缓冲区内,但并不从系统缓冲区内删除。 0x4 不要将包路由出去。 以上三项与sock.h文件中定义完全相同 0x8 数据完整记录 0x100 数据完整处理 */ $l=socket_recv($sock,$buf,1000,0);//原本获取数据是一个缓慢的过程,需要一次一次获取数据,并计算每次buf的长度,让总长度不超过设定值 //var_dump($l); // exit; // file_put_contents('lz.text','socket_recv', FILE_APPEND); $len+=$l; $buffer.=$buf; }1000); $k=$this->search($sock);//Obtener el valor de clave según el sock if($len<7){ //El mensaje recibido es demasiado corto, el sistema lo considera como un corte, y corta la conexión. $this->send2($k);//El usuario se desconecta.1Cerrar el socket correspondiente al valor $key, eliminar este registro de clave. Reordenar el objeto del array de sockets. //2 continue; } if(!$this->users[$k]['shou']){//¿Es verdadero el campo de conexión del usuario? De lo contrario, establece nuevamente la conexión. $this->woshou($k,$buffer); //file_put_contents('lz.text','woshou', FILE_APPEND); } else{ //Si el usuario ya ha establecido el protocolo de conexión, se establece la comunicación entre el usuario. ¡Por fin se puede enviar mensajes! $buffer = $this->uncode($buffer,$k); //$mask = array(); if($buffer==false){ continue; } //var_dump($bufffer); //exit; $this->send($k,$buffer); } } } } } function close($k){ socket_close($this->users[$k]['socket']); unset($this->users[$k]); $this->sockets=array($this->master); foreach($this->users as $v){ $this->sockets[]=$v['socket']; } $this->e("key:$k close"); } function search($sock){ foreach ($this->users as $k=>$v){ if($sock==$v['socket']) return $k; } return false; } function WebSocket($address,$port){ //El servidor está escuchando //Se crea y devuelve un recurso de socket, también conocido como un nodo de comunicación. Una conexión de red típica consta de 2 Se compone de un par de sockets, uno ejecutándose en el cliente y el otro en el servidor. //Protocolo, tipo, protocolo específico $server = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); //resource(2, Socket) //file_put_contents('lz.text', $server, FILE_APPEND);//El recurso proporcionado no es un recurso de flujo válido //返回bool.套接字资源,协议级别,可用的套接字选项,值。 $r=socket_set_option($server, SOL_SOCKET, SO_REUSEADDR, 1);//boolean true //绑定 address 到 socket。该操作必须是在使用 socket_connect() 或者 socket_listen() 建立一个连接之前。 $r2=socket_bind($server, $address, $port);//boolean true //在socket套接字已创建使用socket_create()定界与socket_bind()名称,它可以告诉监听套接字传入的连接。 $r3=socket_listen($server);//boolean true $this->e('Server Started : '.date('Y-m-d H:i:s')); $this->e('Listening on : '.$address.' port '.$port); return $server; } function woshou($k,$buffer){ //处理接收到的buffer,并回馈握手!! $buf = substr($buffer,strpos($buffer,'Sec'-WebSocket-Key:')+18); $key = trim(substr($buf,0,strpos($buf,"\r\n"))); $new_key = base64_encode(sha1($key."258EAFA5-E914-47DA-95CA-C5AB0DC85B11",true)); $new_message = "HTTP"/1.1 101 Switching Protocols\r\n" $new_message .= "Upgrade: websocket\r\n" $new_message .= "Sec"-WebSocket-Version: 13\r\n"; $new_message .= "Connection: Upgrade\r\n" $new_message .= "Sec"-WebSocket-Accept: ". $new_key . "\r\n\r\n"; socket_write($this->users[$k]['socket'],$new_message,strlen($new_message));//sokcet,buffer(缓冲区),length $this-return true; function uncode($str,$key){ } 返编译 //$mask = array(); $data = ''; $msg = unpack('H', ',$str);*unpack() 函数从二进制字符串对数据进行解包。//$head = substr($msg[ ],0,1if ($head == '2); ' && !isset($this81>slen[$key])) {-)=substr($msg[ $len=hexdec($len);1,2$s =2); $msg[ hexdec() 函数把十六进制转换为十进制。1,2$s =2else if(substr($msg[ $len=hexdec($len);1,4$s =4); $msg[//} =substr($msg[1$mask[] = hexdec(substr($msg[1,4); )=='ff'){1,2$s =2$len=substr($msg[ $len=hexdec($len);1,4$s =16); $msg[ =substr($msg[1$mask[] = hexdec(substr($msg[1,16); } ],1,4$s =2)); ],1,6$s =2)); ],1,8$s =2)); ],1,10$s =2)); $n=0; 12; else if($this }->slen[$key] > 0){ $len=$this->slen[$key]; $mask=$this->ar[$key]; $n=$this->n[$key]; $s = 0; } $e = strlen($msg[1])-2; for ($i=$s; $i<= $e; $i+= 2) { $data .= chr($mask[$n%4]^hexdec(substr($msg[1],$i,2)) $n++; } $dlen=strlen($data); if($len > 255 && $len > $dlen+intval($this->sjen[$key])){ $this->ar[$key]=$mask; $this->slen[$key]=$len; $this->sjen[$key]=$dlen+intval($this->sjen[$key]); $this->sda[$key]=$this->sda[$key].$data; $this->n[$key]=$n; return false; } else{ unset($this->ar[$key],$this->slen[$key],$this->sjen[$key],$this->n[$key]); $data=$this->sda[$key].$data; unset($this->sda[$key]); return $data; } } function code($msg){ //编译 $frame = array(); $frame[0] = '81'; $len = strlen($msg); if($len < 126){ $frame[1] = $len<16?'0'.dechex($len):dechex($len); } else if($len < 65025){ $s=dechex($len); $frame[1]='7e'.str_repeat('0',4-strlen($s)).$s; } else{ $s=dechex($len); $frame[1]='7f'.str_repeat('0',16-strlen($s)).$s; } $frame[2] = $this->ord_hex($msg); $data = implode('',$frame); return pack("H*", $data); } function ord_hex($data) { $msg = ''; $l = strlen($data); for ($i= 0; $i<$l; $i++) { $msg .= dechex(ord($data{$i})); } return $msg; } //用户加入 function send($k,$msg){ parse_str($msg,$g);//把查询字符串解析到变量中 $ar=array(); if($g['type']=='add'){ $this->users[$k]['name']=$g['ming']; $ar['type']='add'; $ar['name']=$g['ming']; $key='all'; } else{ $ar['nrong']=$g['nr']; $key=$g['key']; } $this->send1($k,$ar,$key); } function getusers(){ $ar=array(); foreach($this->users as $k=>$v){ $ar[]=array('code'=>$k,'name'=>$v['name']); } return $ar; } //$k 发信息人的code $key接受人的 code function send1($k,$ar,$key='all'){ $ar['code1']=$key; $ar['code']=$k; $ar['time']=date('m-d H:i:s'); $str = $this->code(json_encode($ar)); if($key=='all'){ $users=$this->users; if($ar['type']=='add'){ $ar['type']='madd'; $ar['users']=$this->getusers(); $str1 = $this->code(json_encode($ar)); socket_write($users[$k]['socket'],$str1,strlen($str1));//发送者 unset($users[$k]); } foreach($users as $v){ socket_write($v['socket'],$str,strlen($str));//接收者 } } else{ socket_write($this->users[$k]['socket'],$str,strlen($str));//发送者 socket_write($this->users[$key]['socket'],$str,strlen($str));//接收者 } } //用户退出 function send2($k){ $this->close($k); $ar['type']='rmove'; $ar['nrong']=$k; $this->send1(false,$ar,'all'); } function e($str){ //$path=dirname(__FILE__).'/log.txt'; $str=$str."\n"; //error_log($str,3, $path); echo iconv('utf-8','gbk//IGNORE,$str); } } ?>
客户端:
!doctype html <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"/> <title>HTML5 websocket 网页聊天室 javascript php</title> <style type="text/css"> body,p{margin:0px; padding:0px; font-size:14px; color:#333; font-family:Arial, Helvetica, sans-serif;} #ltian,.rin{width:98%; margin:5px auto;} #ltian{border:1px #ccc solid;overflow-y:auto; overflow-x:hidden; position:relative;} #ct{margin-right:111px; height:100%;overflow-y:auto;overflow-x: hidden;} #us{width:110px; overflow-y:auto; overflow-x:hidden; float:right; border-left:1px #ccc solid; height:100%; background-color:#F1F1F1;} #us p{padding:3px 5px; color:#08C; line-height:20px; height:20px; cursor:pointer; overflow:hidden; white-space:nowrap; text-overflow:ellipsis;} #us p:hover,#us p:active,#us p.ck{background-color:#069; color:#FFF;} #us p.my:hover,#us p.my:active,#us p.my{color:#333;background-color:transparent;} button{float:right; width:80px; height:35px; font-size:18px;} input{width:100%; height:30px; padding:2px; line-height:20px; outline:none; border:solid 1px #CCC;} .rin p{margin-right:160px;} .rin span{float:right; padding:6px 5px 0px 5px; position:relative;} .rin span img{margin:0px 3px; cursor:pointer;} .rin span form{position:absolute; width:25px; height:25px; overflow:hidden; opacity:0; top:5px; right:5px;} .rin span input{width:180px; height:25px; margin-left:-160px; cursor:pointer} #ct p{padding:5px; line-height:20px;} #ct a{color:#069; cursor:pointer;} #ct span{color:#999; margin-right:10px;} .c2{color:#999;} .c3{background-color:#DBE9EC; padding:5px;} .qp{position:absolute; font-size:12px; color:#666; top:5px; right:130px; text-index:none; color:#069;} #ems{position:absolute; z-index:5; display:none; top:0px; left:0px; max-width:230px; background-color:#F1F1F1; border:solid 1px #CCC; padding:5px;} #ems img{width:44px; height:44px; border:solid 1px #FFF; cursor:pointer;} #ems img:hover,#ems img:active{border-color:#A4B7E3;} #ems a{color:#069; border-radius:2px; display:inline-block; margin:2px 5px; padding:1px 8px; text-ecoration:none; background-color:#D5DFFD;} #ems a:hover,#ems a:active,#ems a.ck{color:#FFF; background-color:#069;} .tc{text-align:center; margin-top:5px;} </style> </head> <body> <div id="ltian">< <div id="us" class="jb"></div> <div id="ct"></div> <a href="javascript:;" class="qp" onClick="this.parentNode.children[1].innerHTML=''">Borrar Pantalla</a> </div> <div class="rin"> <button id="sd">enviar</button> <span><img src="http://www.yxsss.com/ui/sk/t.png" title="[#1#]" id="imgbq"><img src="http://www.yxsss.com/ui/sk/e.png" title="[#2#]"><form><input type="file" title="[#2#]" id="upimg"></form></span> <p><input id="nrong"></p> </div> <div id="ems"><p></p><p class="tc"></p></div> <script> if(typeof(WebSocket)=='undefined'){ alert('Su navegador no admite WebSocket, se recomienda usar Google Chrome o Mozilla Firefox'); } </script> <script src="http://www.yxsss.com/ui/p/a.js" type="text/javascript"></script> <script> (function(){ var key='all',mkey; var users={}; var url='ws://127.0.0.1:8000'; var so=false,n=false; var lus=A.$('us'),lct=A.$('ct'); function st(){ n=prompt('por favor, elija un nombre llamativo para usted mismo:'); n=n.substr(0,16); if(!n){ return ; } so=new WebSocket(url); so.onopen=function(){ if(so.readyState==1){ alert('888'); so.send('type=add&ming='+n); } } so.onclose=function(){ so=false; lct.appendChild(A.$$('<p class="c2">salir de la sala de chat</p>')); } so.onmessage=function(msg){ eval('var da='+msg.data); var obj=false,c=false; if(da.type=='add'){ var obj=A.$("<p>")+da.name+</p)> lus.appendChild(obj); cuser(obj,da.code); obj=A.$("<p><span>[")+da.time+']<}}/span>欢迎<a>+da.name+</a>加入</p)> c=da.code; } else if(da.type=='madd'){ mkey=da.code; da.users.unshift({'code':'all','name':'大家'}) for(var i=0;i<da.users.length;i++){ var obj=A.$("<p>")+da.users[i].name+</p)> lus.appendChild(obj); if(mkey!=da.users[i].code){ cuser(obj,da.users[i].code); } else{ obj.className='my'; document.title=da.users[i].name; } } obj=A.$("<p><span>[")+da.time+']<}}/span>欢迎+da.name+"加入</p)> users.all.className='ck'; } if(obj==false){ if(da.type=='rmove'){ var obj=A.$("<p class="c2><span>[")+da.time+']<}}/span>"]+users[da.nrong].innerHTML+"退出聊天室</p)> lct.appendChild(obj); users[da.nrong].del(); delete users[da.nrong]; } else{ da.nrong=da.nrong.replace("/{\\(\d+)}/g,function(a,b){ return '<img src="sk/"+b+'.gif">"; }).replace("/^data\:image\/png;base64\,.{50,}$/i,function(a){ return '<img src="+a+">"; }); //da.code 发信息人的code if(da.code1==mkey){ obj=A.$("<p class="c3><span>[")+da.time+']<}}/span><a>'+users[da.code].innerHTML+</a>对我说:+da.nrong+</p)> c=da.code; } else if(da.code==mkey){ if(da.code1!='all') obj=A.$("<p class="c3><span>[")+da.time+']<}}/span>我对<a>+users[da.code1].innerHTML+</a>说:+da.nrong+</p)> else obj=A.$("<p><span>[")+da.time+']<}}/span>我对<a>+users[da.code1].innerHTML+</a>说:+da.nrong+</p)> c=da.code1; } else if(da.code==false){ obj=A.$("<p><span>[")+da.time+']<}}/span>"]+da.nrong+</p)> } else if(da.code1){ obj=A.$("<p><span>[")+da.time+']<}}/span><a>'+users[da.code].innerHTML+</>对'+users[da.code1].innerHTML+':'+da.nrong+</p)> c=da.code; } } } if(c){ obj.children[1].onclick=function(){ users[c].onclick(); } } lct.appendChild(obj); lct.scrollTop=Math.max(0,lct.scrollHeight-lct.offsetHeight); } } A.$('sd').onclick=function(){ if(!so){ return st(); } var da=A.$('nrong').value.trim(); if(da==''){ alert('内容不能为空'); return false; } A.$('nrong').value=''; so.send('nr='+esc(da)+'&key='+key); } A.$('nrong').onkeydown=function(e){ var e=e||event; if(e.keyCode==13){ A.$('sd').onclick(); } } function esc(da){ da=da.replace(/</g,'<').replace(/>/g,'>').replace(/\"/g,'"'); return encodeURIComponent(da); } function cuser(t,code){ users[code]=t; t.onclick=function(){ t.parentNode.children.rcss('ck',''); t.rcss('','ck'); key=code; } } A.$('ltian').style.height=(document.documentElement.clientHeight - 70);+'px'; st(); var bq=A.$('imgbq'),ems=A.$('ems'); var l=80,r=4,c=5,s=0,p=Math.ceil(l/(r*c)); var pt='sk/'; bq.onclick=function(e){ var e=e||event; if(!so){ return st(); } ems.style.display='block'; document.onclick=function(){ gb(); } ct(); try{e.stopPropagation();}catch(o){} } for(var i=0;i<p;i++){ var a=A.$('<a href="javascript:;">'+(i+1)+</a)> ems.children[1.appendChild(a); ef(a,i);} } ems.children[1].children[0].className='ck'; function ct(){ var wz=bq.weiz(); with(ems.style){ top=wz.y-242+'px'; left=wz.x+bq.offsetWidth-235+'px'; } } function ef(t,i){ t.onclick=function(e){ var e=e||event; s=i*r*c; ems.children[0].innerHTML=''; hh(); this.parentNode.children.rcss('ck',''); this.rcss('','ck'); try{e.stopPropagation();}catch(o){} } } function hh(){ var z=Math.min(l,s+r*c); for(var i=s;i<z;i++){ var a=A.$$('<img src="'+pt+i+'.gif">'); hh1(a,i); ems.children[0].appendChild(a); } ct(); } function hh1(t,i){ t.onclick=function(e){ var e=e||event; A.$('nrong').value+='{\\'+i+}; if(!e.ctrlKey){ gb(); } try{e.stopPropagation();}catch(o){} } } function gb(){ ems.style.display=''; A.$('nrong').focus(); document.onclick=''; } hh(); A.on(window,'resize',function(){ A.$('ltian').style.height=(document.documentElement.clientHeight - 70);+'px'; ct(); }); var fimg=A.$('upimg'); var img=new Image(); var dw=400,dh=300; A.on(fimg,'change',function(ev){ if(!so){ st(); return false; } if(key=='all'){ alert('Due to resource limitations, sending images can only be done in private chat'); return false; } var f=ev.target.files[0]; if(f.type.match('image.*')){ var r = new FileReader(); r.onload = function(e){ img.setAttribute('src',e.target.result); }; r.readAsDataURL(f); } }); img.onload=function(){ ih=img.height,iw=img.width; if(iw/ih > dw/dh && iw > dw){ ih=ih/iw*dw; iw=dw; }else if(ih > dh){ iw=iw/ih*dh; ih=dh; } var rc = A.$$('canvas'); var ct = rc.getContext('2d'); rc.width=iw; rc.height=ih; ct.drawImage(img,0,0,iw,ih); var da=rc.toDataURL(); so.send('nr='+esc(da)+'&key='+key); } })(); </script> </body> </html>
Esto es todo el contenido de este artículo, espero que ayude a su aprendizaje y que todos apoyen a la tutorial de gritos.
Aclaración: Este artículo se ha recopilado de la red, pertenece al propietario original, el contenido ha sido contribuido y subido voluntariamente por los usuarios de Internet, este sitio web no posee los derechos de propiedad, no ha sido editado por humanos y no asume ninguna responsabilidad legal. Si encuentra contenido que infringe los derechos de autor, por favor envíe un correo electrónico a: notice#w3Declaración: El contenido de este artículo se ha obtenido de la red, pertenece al propietario original, el contenido se ha subido de manera autónoma por los usuarios de Internet, este sitio web no posee los derechos de propiedad, no se ha realizado un procesamiento editorial humano y no asume ninguna responsabilidad legal relacionada. Si encuentra contenido sospechoso de infracción de derechos de autor, por favor envíe un correo electrónico a: notice#w