webSoket+php搭建chatServer(附源碼)

摘要: 最近在公司利用直播間搭建一個圖文直播間時正好要用到chatsever,研究了一下html5的websocket 實現了雙向通信,根據前人的經驗折騰了幾天弄了個聊天室,實現了發送圖片,發送QQ表情,群聊私聊等功能,特地分享給各位新手參考學習,大牛可以忽略。

最近在公司利用直播間搭建一個圖文直播間時正好要用到chatsever,研究了一下html5的websocket 實現了雙向通信,根據前人的經驗折騰了幾天弄了個聊天室,實現了發送圖片,發送QQ表情,群聊私聊等功能,特地分享給各位新手參考學習,大牛可以忽略。

前端:client.html

<!doctype html>n<html>n<head>n<meta charset="utf-8">n<meta name="viewport" content="width_=device-width, initial-scale=1, user-scalable=no"/>n<title>HTML5 websocket 網頁聊天室 javascript php</title>n<style type="text/css">nbody,p{margin:0px; padding:0px; font-size:14px; color:#333; font-family:Arial, Helvetica, sans-serif;}n#ltian,.rin{width:98%; margin:5px auto;}n#ltian{border:1px #ccc solid;overflow-y:auto; overflow-x:hidden; position:relative;}n#ct{margin-right:111px; height:100%;overflow-y:auto;overflow-x: hidden;}n#us{width:110px; overflow-y:auto; overflow-x:hidden; float:right; border-left:1px #ccc solid; height:100%; background-color:#F1F1F1;}n#us p{padding:3px 5px; color:#08C; line-height:20px; height:20px; cursor:pointer; overflow:hidden; white-space:nowrap; text-overflow:ellipsis;}n#us p:hover,#us p:active,#us p.ck{background-color:#069; color:#FFF;}n#us p.my:hover,#us p.my:active,#us p.my{color:#333;background-color:transparent;}nbutton{float:right; width:80px; height:35px; font-size:18px;}ninput{width:100%; height:30px; padding:2px; line-height:20px; outline:none; border:solid 1px #CCC;}n.rin p{margin-right:160px;}n.rin span{float:right; padding:6px 5px 0px 5px; position:relative;}n.rin span img{margin:0px 3px; cursor:pointer;}n.rin span form{position:absolute; width:25px; height:25px; overflow:hidden; opacity:0; top:5px; right:5px;}n.rin span input{width:180px; height:25px; margin-left:-160px; cursor:pointer}nn#ct p{padding:5px; line-height:20px;}n#ct a{color:#069; cursor:pointer;}n#ct span{color:#999; margin-right:10px;}n.c2{color:#999;}n.c3{background-color:#DBE9EC; padding:5px;}n.qp{position:absolute; font-size:12px; color:#666; top:5px; right:130px; text-decoration:none; color:#069;}n#ems{position:absolute; z-index:5; display:none; top:0px; left:0px; max-width:230px; background-color:#F1F1F1; border:solid 1px #CCC; padding:5px;}n#ems img{width:44px; height:44px; border:solid 1px #FFF; cursor:pointer;}n#ems img:hover,#ems img:active{border-color:#A4B7E3;}n#ems a{color:#069; border-radius:2px; display:inline-block; margin:2px 5px; padding:1px 8px; text-decoration:none; background-color:#D5DFFD;}n#ems a:hover,#ems a:active,#ems a.ck{color:#FFF; background-color:#069;}n.tc{text-align:center; margin-top:5px;}n</style>n</head>nnn<body>n<div id="ltian">n <div id="us" class="jb"></div>n <div id="ct"></div>n <a href="javascript:;" class="qp" onClick="this.parentNode.children[1].innerHTML=">清屏</a>n</div>n<div class="rin">n <button id="sd">發送</button>n http://www.yxsss.com/ui/sk/t.png" title="表情" id="imgbq">http://www.yxsss.com/ui/sk/e.png" title="上傳圖片">nn <p><input id="nrong"></p>n</div>n<div id="ems"><p></p><p class="tc"></p></div>n<script>nif(typeof(WebSocket)==undefined){n alert(你的瀏覽器不支持 WebSocket ,推薦使用Google Chrome 或者 Mozilla Firefox); n}n</script>n<a href="http://www.yxsss.com/ui/p/a.js" data-editable="true" data-title="yxsss.com 的頁面">http://www.yxsss.com/ui/p/a.js</a>" type="text/javascript">n<script>n(function(){n var key=all,mkey;n var users={};n var url=ws://127.0.0.1:8000;n var so=false,n=false;n var lus=A.$(us),lct=A.$(ct);n function st(){n var Arr1 = ["聰明的","狡猾的","可愛的","美麗的","狡猾的","善良的","帥氣的","逗比的"]; n var Arr2 = ["大灰狼","小白兔","母老虎","外星人","皮卡丘","HelloKitty","吳亦凡","薛之謙"]; n var ran1 = Math.floor(Math.random() * Arr1.length + 1)-1; n var ran2 = Math.floor(Math.random() * Arr2.length + 1)-1; n var n=Arr1[ran1]+Arr2[ran2]; n //以上五行是用來隨機生成用戶昵稱的方法,參考一下 ,如果想自定義用戶名可以將以上五行注釋,然後以下兩行取消注釋n //n=prompt(請給自己取一個霸氣的名字:);n //n=n.substr(0,16);n //console.log(n);n if(!n){n return ; n }n so=new WebSocket(url);n so.onopen=function(){n if(so.readyState==1){n so.send(type=add&ming=+n);n }n }n n so.onclose=function(){n so=false;n lct.appendChild(A.$(<p class="c2">退出聊天室</p>));n }n n so.onmessage=function(msg){n eval(var da=+msg.data);n var obj=false,c=false;n if(da.type==add){n var obj=A.$(<p>+da.name+</p>);n lus.appendChild(obj);n cuser(obj,da.code);n obj=A.$(<p><span>[+da.time+]</span>歡迎<a>+da.name+</a>加入</p>);n c=da.code;n }else if(da.type==madd){n mkey=da.code;n da.users.unshift({code:all,name:大家});n for(var i=0;i<da.users.length;i++){n var obj=A.$(<p>+da.users[i].name+</p>);n lus.appendChild(obj);n if(mkey!=da.users[i].code){n cuser(obj,da.users[i].code);n }else{n obj.className=my;n document.title=da.users[i].name;n }n }n obj=A.$(<p><span>[+da.time+]</span>歡迎+da.name+加入</p>);n users.all.className=ck;n }n n if(obj==false){n if(da.type==rmove){n var obj=A.$(<p class="c2"><span>[+da.time+]</span>+users[da.nrong].innerHTML+退出聊天室</p>);n lct.appendChild(obj);n users[da.nrong].del();n delete users[da.nrong];n }else{n da.nrong=da.nrong.replace(/{(d+)}/g,function(a,b){n return <img src="sk/+b+.jpg">;n }).replace(/^data:image/png;base64,.{50,}$/i,function(a){n return <img src="+a+">;n });n //da.code 發信息人的coden if(da.code1==mkey){n obj=A.$(<p class="c3"><span>[+da.time+]</span><a>+users[da.code].innerHTML+</a>對我說:+da.nrong+</p>);n c=da.code;n }else if(da.code==mkey){n if(da.code1!=all)n obj=A.$(<p class="c3"><span>[+da.time+]</span>我對<a>+users[da.code1].innerHTML+</a>說:+da.nrong+</p>);n elsen obj=A.$(<p><span>[+da.time+]</span>我對<a>+users[da.code1].innerHTML+</a>說:+da.nrong+</p>);n c=da.code1;n }else if(da.code==false){n obj=A.$(<p><span>[+da.time+]</span>+da.nrong+</p>);n }else if(da.code1){n obj=A.$(<p><span>[+da.time+]</span><a>+users[da.code].innerHTML+</a>對+users[da.code1].innerHTML+說:+da.nrong+</p>);n c=da.code;n }n }n }n if(c){n obj.children[1].onclick=function(){n users[c].onclick();n }n }n lct.appendChild(obj);n lct.scrollTop=Math.max(0,lct.scrollHeight-lct.offsetHeight);n }n }n A.$(sd).onclick=function(){n if(!so){n return st();n }n var da=A.$(nrong).value.trim();n if(da==){n alert(內容不能為空);n return false; n }n A.$(nrong).value=;n so.send(nr=+esc(da)+&key=+key);n }n A.$(nrong).onkeydown=function(e){n var e=e||event;n if(e.keyCode==13){n A.$(sd).onclick();n }n }n function esc(da){n da=da.replace(/</g,<).replace(/>/g,>).replace(/"/g,");n return encodeURIComponent(da);n }n function cuser(t,code){n users[code]=t;n t.onclick=function(){n t.parentNode.children.rcss(ck,);n t.rcss(,ck);n key=code;n }n }n A.$(ltian).style.height=(document.documentElement.clientHeight - 70)+px;n st();n nn var bq=A.$(imgbq),ems=A.$(ems);n var l=80,r=4,c=5,s=0,p=Math.ceil(l/(r*c));n var pt=sk/;n bq.onclick=function(e){n var e=e||event;n if(!so){n return st();n }n ems.style.display=block;n document.onclick=function(){n gb(); n }n ct();n try{e.stopPropagation();}catch(o){}n }n n for(var i=0;i<p;i++){n var a=A.$(<a href="javascript:;">+(i+1)+</a>);n ems.children[1].appendChild(a);n ef(a,i);n }n ems.children[1].children[0].className=ck;n n function ct(){n var wz=bq.weiz();n with(ems.style){n top=wz.y-242+px;n left=wz.x+bq.offsetWidth-235+px;n }n }n n function ef(t,i){n t.onclick=function(e){n var e=e||event;n s=i*r*c;n ems.children[0].innerHTML=;n hh();n this.parentNode.children.rcss(ck,);n this.rcss(,ck);n try{e.stopPropagation();}catch(o){}n }n }n n function hh(){n var z=Math.min(l,s+r*c);n for(var i=s;i<z;i++){n var a=A.$(<img src="+pt+i+.jpg">);n hh1(a,i);n ems.children[0].appendChild(a);n }n ct();n }n n function hh1(t,i){n t.onclick=function(e){n var e=e||event;n A.$(nrong).value+={+i+};n if(!e.ctrlKey){n gb();n }n try{e.stopPropagation();}catch(o){}n }n }n n function gb(){n ems.style.display=;n A.$(nrong).focus();n document.onclick=;n }n hh();n A.on(window,resize,function(){n A.$(ltian).style.height=(document.documentElement.clientHeight - 70)+px;n ct();n }) nn var fimg=A.$(upimg);n var img=new Image();n var dw=400,dh=300;n A.on(fimg,change,function(ev){n if(!so){n st();n return false;n }n if(key==all){n alert(由於資源限制 發圖只能私聊);n return false; n }n var f=ev.target.files[0];n if(f.type.match(image.*)){n var r = new FileReader();n r.onload = function(e){n img.setAttribute(src,e.target.result);n };n r.readAsDataURL(f);n }n });n img.onload=function(){n ih=img.height,iw=img.width;n if(iw/ih > dw/dh && iw > dw){n ih=ih/iw*dw;n iw=dw;n }else if(ih > dh){n iw=iw/ih*dh;n ih=dh;n }n var rc = A.$(canvas);n var ct = rc.getContext(2d);n rc.width_=iw;n rc.height=ih;n ct.drawImage(img,0,0,iw,ih);n var da=rc.toDataURL();n so.send(nr=+esc(da)+&key=+key);n }n n})();n</script>n</body>n</html>n

後端代碼:webserver.php

<?phpnerror_reporting(E_ALL ^ E_NOTICE);nob_implicit_flush();nn$sk=new Sock(127.0.0.1,8000);n$sk->run();nclass Sock{ntpublic $sockets;ntpublic $users;ntpublic $master;ntntprivate $sda=array();//已接收的數據ntprivate $slen=array();//數據總長度ntprivate $sjen=array();//接收數據的長度ntprivate $ar=array();//加密keyntprivate $n=array();ntntpublic function __construct($address, $port){ntt$this->master=$this->WebSocket($address, $port);ntt$this->sockets=array($this->master);nt}ntntntfunction run(){nttwhile(true){nttt$changes=$this->sockets;nttt$write=NULL;nttt$except=NULL;ntttsocket_select($changes,$write,$except,NULL);ntttforeach($changes as $sock){nttttif($sock==$this->master){nttttt$client=socket_accept($this->master);nttttt$key=uniqid();nttttt$this->sockets[]=$client;nttttt$this->users[$key]=array(nttttttsocket=>$client,nttttttshou=>falsenttttt);ntttt}else{nttttt$len=0;nttttt$buffer=;ntttttdo{ntttttt$l=socket_recv($sock,$buf,1000,0);ntttttt$len+=$l;ntttttt$buffer.=$buf;nttttt}while($l==1000);nttttt$k=$this->search($sock);ntttttif($len<7){ntttttt$this->send2($k);nttttttcontinue;nttttt}ntttttif(!$this->users[$k][shou]){ntttttt$this->woshou($k,$buffer);nttttt}else{ntttttt$buffer = $this->uncode($buffer,$k);nttttttif($buffer==false){ntttttttcontinue;ntttttt}ntttttt$this->send($k,$buffer);nttttt}ntttt}nttt}ntttntt}nttnt}ntntfunction close($k){nttsocket_close($this->users[$k][socket]);nttunset($this->users[$k]);ntt$this->sockets=array($this->master);nttforeach($this->users as $v){nttt$this->sockets[]=$v[socket];ntt}ntt$this->e("key:$k close");nt}ntntfunction search($sock){nttforeach ($this->users as $k=>$v){ntttif($sock==$v[socket])ntttreturn $k;ntt}nttreturn false;nt}ntntfunction WebSocket($address,$port){ntt$server = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);nttsocket_set_option($server, SOL_SOCKET, SO_REUSEADDR, 1);nttsocket_bind($server, $address, $port);nttsocket_listen($server);ntt$this->e(Server Started : .date(Y-m-d H:i:s));ntt$this->e(Listening on : .$address. port .$port);nttreturn $server;nt}ntntntfunction woshou($k,$buffer){ntt$buf = substr($buffer,strpos($buffer,Sec-WebSocket-Key:)+18);ntt$key = trim(substr($buf,0,strpos($buf,"rn")));ntntt$new_key = base64_encode(sha1($key."258EAFA5-E914-47DA-95CA-C5AB0DC85B11",true));nttntt$new_message = "HTTP/1.1 101 Switching Protocolsrn";ntt$new_message .= "Upgrade: websocketrn";ntt$new_message .= "Sec-WebSocket-Version: 13rn";ntt$new_message .= "Connection: Upgradern";ntt$new_message .= "Sec-WebSocket-Accept: " . $new_key . "rnrn";nttnttsocket_write($this->users[$k][socket],$new_message,strlen($new_message));ntt$this->users[$k][shou]=true;nttreturn true;nttnt}ntntfunction uncode($str,$key){ntt$mask = array(); ntt$data = ; ntt$msg = unpack(H*,$str);ntt$head = substr($msg[1],0,2); nttif ($head == 81 && !isset($this->slen[$key])) { nttt$len=substr($msg[1],2,2);nttt$len=hexdec($len);ntttif(substr($msg[1],2,2)==fe){ntttt$len=substr($msg[1],4,4);ntttt$len=hexdec($len);ntttt$msg[1]=substr($msg[1],4);nttt}else if(substr($msg[1],2,2)==ff){ntttt$len=substr($msg[1],4,16);ntttt$len=hexdec($len);ntttt$msg[1]=substr($msg[1],16);nttt}nttt$mask[] = hexdec(substr($msg[1],4,2)); nttt$mask[] = hexdec(substr($msg[1],6,2)); nttt$mask[] = hexdec(substr($msg[1],8,2)); nttt$mask[] = hexdec(substr($msg[1],10,2));nttt$s = 12;nttt$n=0;ntt}else if($this->slen[$key] > 0){nttt$len=$this->slen[$key];nttt$mask=$this->ar[$key];nttt$n=$this->n[$key];nttt$s = 0;ntt}nttntt$e = strlen($msg[1])-2;nttfor ($i=$s; $i<= $e; $i+= 2) { nttt$data .= chr($mask[$n%4]^hexdec(substr($msg[1],$i,2))); nttt$n++; ntt} ntt$dlen=strlen($data);nttnttif($len > 255 && $len > $dlen+intval($this->sjen[$key])){nttt$this->ar[$key]=$mask;nttt$this->slen[$key]=$len;nttt$this->sjen[$key]=$dlen+intval($this->sjen[$key]);nttt$this->sda[$key]=$this->sda[$key].$data;nttt$this->n[$key]=$n;ntttreturn false;ntt}else{ntttunset($this->ar[$key],$this->slen[$key],$this->sjen[$key],$this->n[$key]);nttt$data=$this->sda[$key].$data;ntttunset($this->sda[$key]);ntttreturn $data;ntt}nttnt}ntntntfunction code($msg){ntt$frame = array(); ntt$frame[0] = 81; ntt$len = strlen($msg);nttif($len < 126){nttt$frame[1] = $len<16?0.dechex($len):dechex($len);ntt}else if($len < 65025){nttt$s=dechex($len);nttt$frame[1]=7e.str_repeat(0,4-strlen($s)).$s;ntt}else{nttt$s=dechex($len);nttt$frame[1]=7f.str_repeat(0,16-strlen($s)).$s;ntt}ntt$frame[2] = $this->ord_hex($msg); ntt$data = implode(,$frame); nttreturn pack("H*", $data); nt}ntntfunction ord_hex($data) { ntt$msg = ; ntt$l = strlen($data); nttfor ($i= 0; $i<$l; $i++) { nttt$msg .= dechex(ord($data{$i})); ntt} nttreturn $msg; nt}ntnt//用戶加入ntfunction send($k,$msg){nttparse_str($msg,$g);ntt$ar=array();nttif($g[type]==add){nttt$this->users[$k][name]=$g[ming];nttt$ar[type]=add;nttt$ar[name]=$g[ming];nttt$key=all;ntt}else{nttt$ar[nrong]=$g[nr];nttt$key=$g[key];ntt}ntt$this->send1($k,$ar,$key);nt}ntntfunction getusers(){ntt$ar=array();nttforeach($this->users as $k=>$v){nttt$ar[]=array(code=>$k,name=>$v[name]);ntt}nttreturn $ar;nt}ntnt//$k 發信息人的code $key接受人的 codentfunction send1($k,$ar,$key=all){ntt$ar[code1]=$key;ntt$ar[code]=$k;ntt$ar[time]=date(m-d H:i:s);ntt$str = $this->code(json_encode($ar));nttif($key==all){nttt$users=$this->users;ntttif($ar[type]==add){ntttt$ar[type]=madd;ntttt$ar[users]=$this->getusers();ntttt$str1 = $this->code(json_encode($ar));nttttsocket_write($users[$k][socket],$str1,strlen($str1));nttttunset($users[$k]);nttt}ntttforeach($users as $v){nttttsocket_write($v[socket],$str,strlen($str));nttt}ntt}else{ntttsocket_write($this->users[$k][socket],$str,strlen($str));ntttsocket_write($this->users[$key][socket],$str,strlen($str));ntt}nt}ntnt//用戶退出ntfunction send2($k){ntt$this->close($k);ntt$ar[type]=rmove;ntt$ar[nrong]=$k;ntt$this->send1(false,$ar,all);nt}ntntfunction e($str){ntt//$path=dirname(__FILE__)./log.txt;ntt$str=$str."n";ntt//error_log($str,3,$path);nttecho iconv(utf-8,gbk//IGNORE,$str);nt}n}n?>n

很多童鞋反應用我的源碼項目還是報錯,不能運行,說下詳細安裝部署步驟。

首先把下載下來的源代碼解壓放到web目錄下,比如我的就是applications/Xampp/xamppfiles/htdocs/phpb/websocket,

然後使用命令行工具cd進這個目錄,運行命令:

php websocket.phpn

運行效果圖:

接著打開Apache伺服器,在瀏覽器訪問localhost/phpb/websocke

運行效果圖:

源碼鏈接:babyanzichen/Html5-webSoket-php-chatsever

有什麼不明白的地方可以在下面留言,共同進步

推薦閱讀:

MQTT和Websocket的區別是什麼?
對於 Socket 粘包的困惑?
web AR系統-3 效率評估-websocket
一步一步教您用websocket+nodeJS搭建簡易聊天室(4)
tornado如何實現非同步websocket推送?

TAG:Web开发 | PHP | WebSocket |