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之第六天
Oct 09 PHP
php auth_http类库进行身份效验
Mar 19 PHP
php下清空字符串中的HTML标签的代码
Sep 06 PHP
php数组函数序列之array_splice() - 在数组任意位置插入元素
Nov 07 PHP
PHP函数引用返回的实例详解
Sep 11 PHP
PHP中STDCLASS用法实例分析
Nov 11 PHP
解决微信授权回调页面域名只能设置一个的问题
Dec 11 PHP
ThinkPHP框架实现数据增删改
May 07 PHP
php生成毫秒时间戳的实例讲解
Sep 22 PHP
详解cookie验证的php应用的一种SSO解决办法
Oct 20 PHP
PHP商品秒杀问题解决方案实例详解【mysql与redis】
Jul 22 PHP
php使用redis的有序集合zset实现延迟队列应用示例
Feb 20 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
PHP 删除一个目录及目录下的所有文件的函数代码
2010/05/26 PHP
PHP计算日期相差天数实例分析
2016/02/23 PHP
Thinkphp 框架基础之入口文件功能、定义与用法分析
2020/04/27 PHP
javascript  Error 对象 错误处理
2008/05/18 Javascript
jQuery表单验证插件formValidator(改进版)
2012/02/03 Javascript
select标记美化--JS式插件、后期加载
2013/04/01 Javascript
Jquery遍历checkbox获取选中项value值的方法
2014/02/13 Javascript
浅析Bootstrip的select控件绑定数据的问题
2016/05/10 Javascript
require.js配合插件text.js实现最简单的单页应用程序
2016/07/12 Javascript
微信小程序  wx.request合法域名配置详解
2016/11/23 Javascript
Bootstrap 响应式实用工具实例详解
2017/03/29 Javascript
微信小程序 仿美团分类菜单 swiper分类菜单
2017/04/12 Javascript
解决JS内存泄露之js对象和dom对象互相引用问题
2017/06/25 Javascript
bootstrap 通过加减按钮实现输入框组功能
2017/11/15 Javascript
vue中使用echarts制作圆环图的实例代码
2018/07/27 Javascript
解决Vue 项目打包后favicon无法正常显示的问题
2018/09/01 Javascript
基于vue的验证码组件的示例代码
2019/01/22 Javascript
Layui 导航默认展开和菜单栏选中高亮设置的方法
2019/09/04 Javascript
JavaScript多种图形实现代码实例
2020/06/28 Javascript
[04:09]2018年度DOTA2社区贡献奖-完美盛典
2018/12/16 DOTA
[42:32]完美世界DOTA2联赛循环赛 Magma vs PXG BO2第二场 10.28
2020/10/28 DOTA
Python字典,函数,全局变量代码解析
2017/12/18 Python
Python cookbook(数据结构与算法)对切片命名清除索引的方法
2018/03/13 Python
Python Numpy计算各类距离的方法
2019/07/05 Python
Python切片列表字符串如何实现切换
2020/08/06 Python
解决import tensorflow导致jupyter内核死亡的问题
2021/02/06 Python
linux面试题参考答案(1)
2016/01/22 面试题
管理站站长岗位职责
2013/11/27 职场文书
英文自我鉴定
2013/12/10 职场文书
青安岗事迹材料
2014/05/14 职场文书
高等学院职业生涯规划书范文
2014/09/16 职场文书
出售房屋委托书范本
2014/09/24 职场文书
师德师风个人整改措施
2014/10/27 职场文书
2014年学校总务处工作总结
2014/12/08 职场文书
2015毕业寄语大全
2015/02/26 职场文书
Nginx location 和 proxy_pass路径配置问题小结
2021/09/04 Servers