php+webSoket实现聊天室示例代码(附源码)


Posted in PHP onFebruary 17, 2017

最近在公司利用直播间搭建一个图文直播间时正好要用到chatsever,研究了一下html5的websocket 实现了双向通信,根据前人的经验折腾了几天弄了个聊天室,实现了发送图片,发送QQ表情,群聊私聊等功能,特地分享给各位新手参考学习,大牛可以忽略。

前端:client.html

<!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-decoration: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-decoration: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:;" rel="external nofollow" rel="external nofollow" class="qp" onClick="this.parentNode.children[1].innerHTML=''">清屏</a>
</div>
<div class="rin">
 <button id="sd">发送</button>
 <span><img src="http://www.yxsss.com/ui/sk/t.png" title="表情" id="imgbq"><img src="http://www.yxsss.com/ui/sk/e.png" title="上传图片"><form><input type="file" title="上传图片" 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('你的浏览器不支持 WebSocket ,推荐使用Google Chrome 或者 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(){
  var Arr1 = ["聪明的","狡猾的","可爱的","美丽的","狡猾的","善良的","帅气的","逗比的"]; 
  var Arr2 = ["大灰狼","小白兔","母老虎","外星人","皮卡丘","HelloKitty","吴亦凡","薛之谦"]; 
  var ran1 = Math.floor(Math.random() * Arr1.length + 1)-1; 
  var ran2 = Math.floor(Math.random() * Arr2.length + 1)-1; 
  var n=Arr1[ran1]+Arr2[ran2]; 
  //以上五行是用来随机生成用户昵称的方法,参考一下 ,如果想自定义用户名可以将以上五行注释,然后以下两行取消注释
  //n=prompt('请给自己取一个霸气的名字:');
  //n=n.substr(0,16);
  //console.log(n);
  if(!n){
   return ; 
  }
  so=new WebSocket(url);
  so.onopen=function(){
   if(so.readyState==1){
    so.send('type=add&ming='+n);
   }
  }
  
  so.onclose=function(){
   so=false;
   lct.appendChild(A.$$('<p class="c2">退出聊天室</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+'.jpg">';
     }).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+'</a>对'+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:;" rel="external nofollow" rel="external nofollow" >'+(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+'.jpg">');
   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('由于资源限制 发图只能私聊');
   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>

后端代码:webserver.php

<?php
error_reporting(E_ALL ^ E_NOTICE);
ob_implicit_flush();

$sk=new Sock('127.0.0.1',8000);
$sk->run();
class Sock{
 public $sockets;
 public $users;
 public $master;
 
 private $sda=array();//已接收的数据
 private $slen=array();//数据总长度
 private $sjen=array();//接收数据的长度
 private $ar=array();//加密key
 private $n=array();
 
 public function __construct($address, $port){
 $this->master=$this->WebSocket($address, $port);
 $this->sockets=array($this->master);
 }
 
 
 function run(){
 while(true){
 $changes=$this->sockets;
 $write=NULL;
 $except=NULL;
 socket_select($changes,$write,$except,NULL);
 foreach($changes as $sock){
 if($sock==$this->master){
  $client=socket_accept($this->master);
  $key=uniqid();
  $this->sockets[]=$client;
  $this->users[$key]=array(
  'socket'=>$client,
  'shou'=>false
  );
 }else{
  $len=0;
  $buffer='';
  do{
  $l=socket_recv($sock,$buf,1000,0);
  $len+=$l;
  $buffer.=$buf;
  }while($l==1000);
  $k=$this->search($sock);
  if($len<7){
  $this->send2($k);
  continue;
  }
  if(!$this->users[$k]['shou']){
  $this->woshou($k,$buffer);
  }else{
  $buffer = $this->uncode($buffer,$k);
  if($buffer==false){
  continue;
  }
  $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){
 $server = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
 socket_set_option($server, SOL_SOCKET, SO_REUSEADDR, 1);
 socket_bind($server, $address, $port);
 socket_listen($server);
 $this->e('Server Started : '.date('Y-m-d H:i:s'));
 $this->e('Listening on : '.$address.' port '.$port);
 return $server;
 }
 
 
 function woshou($k,$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));
 $this->users[$k]['shou']=true;
 return true;
 
 }
 
 function uncode($str,$key){
 $mask = array(); 
 $data = ''; 
 $msg = unpack('H*',$str);
 $head = substr($msg[1],0,2); 
 if ($head == '81' && !isset($this->slen[$key])) { 
 $len=substr($msg[1],2,2);
 $len=hexdec($len);
 if(substr($msg[1],2,2)=='fe'){
 $len=substr($msg[1],4,4);
 $len=hexdec($len);
 $msg[1]=substr($msg[1],4);
 }else if(substr($msg[1],2,2)=='ff'){
 $len=substr($msg[1],4,16);
 $len=hexdec($len);
 $msg[1]=substr($msg[1],16);
 }
 $mask[] = hexdec(substr($msg[1],4,2)); 
 $mask[] = hexdec(substr($msg[1],6,2)); 
 $mask[] = hexdec(substr($msg[1],8,2)); 
 $mask[] = hexdec(substr($msg[1],10,2));
 $s = 12;
 $n=0;
 }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);
 }
}
?>

很多童鞋反应用我的源码项目还是报错,不能运行,说下详细安装部署步骤。

首先把下载下来的源代码解压放到web目录下,比如我的就是applications/Xampp/xamppfiles/htdocs/phpb/websocket,

php+webSoket实现聊天室示例代码(附源码)

路径

然后使用命令行工具cd进这个目录,运行命令:

php websocket.php

运行效果图:

php+webSoket实现聊天室示例代码(附源码)

命令行操作

接着打开Apache服务器,在浏览器访问http://localhost/phpb/websocket/client.html

运行效果图:

php+webSoket实现聊天室示例代码(附源码)

源码链接:webSoket-php-chatsever_jb51.rar

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

PHP 相关文章推荐
无数据库的详细域名查询程序PHP版(4)
Oct 09 PHP
PHP日期时间函数的高级应用技巧
May 16 PHP
批量获取memcache值并按key的顺序返回的实现代码
Jun 14 PHP
php中使用explode查找某个字符是否存在的方法
Jul 12 PHP
php中判断数组是一维,二维,还是多维的解决方法
May 04 PHP
php smarty模板引擎的6个小技巧
Apr 24 PHP
php版淘宝网查询商品接口代码示例
Jun 17 PHP
PHP实现将HTML5中Canvas图像保存到服务器的方法
Nov 28 PHP
Discuz!X中SESSION机制实例详解
Sep 23 PHP
微信公众平台DEMO(PHP)
May 04 PHP
PHP中include/require/include_once/require_once使用心得
Aug 28 PHP
微信开发之php表单微信中自动提交两次问题解决办法
Jan 08 PHP
PHP中检索字符串的方法分析【strstr与substr_count方法】
Feb 17 #PHP
PHP常用的三种设计模式
Feb 17 #PHP
轻松实现php文件上传功能
Feb 17 #PHP
php自定义函数br2nl实现将html中br换行符转换为文本输入中换行符的方法【与函数nl2br功能相反】
Feb 17 #PHP
php过滤输入操作之htmlentities与htmlspecialchars用法分析
Feb 17 #PHP
php使用parse_str实现查询字符串解析到变量中的方法
Feb 17 #PHP
php使用str_shuffle()函数生成随机字符串的方法分析
Feb 17 #PHP
You might like
XAMPP安装与使用方法详细解析
2013/11/27 PHP
PHP中的按位与和按位或操作示例
2014/01/27 PHP
?牟┛途W扣了一??效果出?? target=
2007/05/27 Javascript
JQuery UI皮肤定制
2009/07/27 Javascript
Web Inspector:关于在 Sublime Text 中调试Js的介绍
2013/04/18 Javascript
Jquery实现图片放大镜效果的思路及代码(自写)
2013/10/18 Javascript
JQuery+Ajax无刷新分页的实例代码
2014/02/08 Javascript
javascript将数字转换整数金额大写的方法
2015/01/27 Javascript
jquery实现全选、反选、获得所有选中的checkbox
2020/09/13 Javascript
Angular使用$http.jsonp发送跨站请求的方法
2017/03/16 Javascript
jquery仿微信聊天界面
2017/05/06 jQuery
Angular限制input框输入金额(是小数的话只保留两位小数点)
2017/07/13 Javascript
详谈js原型继承的一些问题
2017/09/06 Javascript
详解es6超好用的语法糖Decorator
2018/08/01 Javascript
angular4笔记系列之内置指令小结
2018/11/09 Javascript
vue配置nprogress实现页面顶部进度条
2019/09/21 Javascript
JS正则表达式常见函数与用法小结
2020/04/13 Javascript
vue中移动端调取本地的复制的文本方式
2020/07/18 Javascript
Python实现判断字符串中包含某个字符的判断函数示例
2018/01/08 Python
通过PYTHON来实现图像分割详解
2019/06/26 Python
使用python将最新的测试报告以附件的形式发到指定邮箱
2019/09/20 Python
解决pycharm安装第三方库失败的问题
2020/05/09 Python
图解Python中深浅copy(通俗易懂)
2020/09/03 Python
利用python如何实现猫捉老鼠小游戏
2020/12/04 Python
基于pycharm 项目和项目文件命名规则的介绍
2021/01/15 Python
CSS3制作轮播图的一种方法
2019/11/11 HTML / CSS
Aeropostale官网:美国著名校园品牌及青少年服饰品牌
2019/03/21 全球购物
英超联赛的首选足球:Mitre足球
2019/05/06 全球购物
体育教育个人自荐信范文
2013/12/01 职场文书
自主实习接收函
2014/01/13 职场文书
2014年检验科工作总结
2014/11/22 职场文书
运动会200米广播稿
2015/08/19 职场文书
OpenCV-Python实现图像平滑处理操作
2021/06/08 Python
python基础之错误和异常处理
2021/10/24 Python
Go语言基础函数基本用法及示例详解
2021/11/17 Golang
mysql sum(if())和count(if())的用法说明
2022/01/18 MySQL