欢迎到访爱收集博客,喜欢就Ctrl+D收藏吧!

点击登录
  • 欢迎访问网站,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站

php和前端实现websocket实现实时聊天

网页基础 qaqcn 525次浏览 0个评论

php和前端实现websocket实现实时聊天
地址 到6月23有效 http://mess.qaq6.cn/
后台php

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

	//地址与接口,即创建socket时需要服务器的IP和端口
	$sk=new Sock('127.0.0.1',8000);

	//对创建的socket循环进行监听,处理数据
	$sk->run();

	//下面是sock类
	class Sock{
		public $sockets; //socket的连接池,即client连接进来的socket标志
		public $users;   //所有client连接进来的信息,包括socket、client名字等
		public $master;  //socket的resource,即前期初始化socket时返回的socket资源
		
		private $sda=array();   //已接收的数据
		private $slen=array();  //数据总长度
		private $sjen=array();  //接收数据的长度
		private $ar=array();    //加密key
		private $n=array();
		
		public function __construct($address, $port){

	        //创建socket并把保存socket资源在$this->master
			$this->master=$this->WebSocket($address, $port);

	        //创建socket连接池
			$this->sockets=array($this->master);
		}
		
	    //对创建的socket循环进行监听,处理数据	
		function run(){
	        //死循环,直到socket断开
			while(true){
				$changes=$this->sockets;
				$write=NULL;
				$except=NULL;
	            
	            /*
	            //这个函数是同时接受多个连接的关键,我的理解它是为了阻塞程序继续往下执行。
	            socket_select ($sockets, $write = NULL, $except = NULL, NULL);

	            $sockets可以理解为一个数组,这个数组中存放的是文件描述符。当它有变化(就是有新消息到或者有客户端连接/断开)时,socket_select函数才会返回,继续往下执行。 
	            $write是监听是否有客户端写数据,传入NULL是不关心是否有写变化。 
	            $except是$sockets里面要被排除的元素,传入NULL是”监听”全部。 
	            最后一个参数是超时时间 
	            如果为0:则立即结束 
	            如果为n>1: 则最多在n秒后结束,如遇某一个连接有新动态,则提前返回 
	            如果为null:如遇某一个连接有新动态,则返回
	            */
				socket_select($changes,$write,$except,NULL);
				foreach($changes as $sock){
	                
	                //如果有新的client连接进来,则
					if($sock==$this->master){

	                    //接受一个socket连接
						$client=socket_accept($this->master);

	                    //给新连接进来的socket一个唯一的ID
						$key=uniqid();
						$this->sockets[]=$client;  //将新连接进来的socket存进连接池
						$this->users[$key]=array(
							'socket'=>$client,  //记录新连接进来client的socket信息
							'shou'=>false       //标志该socket资源没有完成握手
						);
	                //否则1.为client断开socket连接,2.client发送信息
					}else{
						$len=0;
						$buffer='';
	                    //读取该socket的信息,注意:第二个参数是引用传参即接收数据,第三个参数是接收数据的长度
						do{
							$l=socket_recv($sock,$buf,1000,0);
							$len+=$l;
							$buffer.=$buf;
						}while($l==1000);

	                    //根据socket在user池里面查找相应的$k,即健ID
						$k=$this->search($sock);

	                    //如果接收的信息长度小于7,则该client的socket为断开连接
						if($len<7){
	                        //给该client的socket进行断开操作,并在$this->sockets和$this->users里面进行删除
							$this->send2($k);
							continue;
						}
	                    //判断该socket是否已经握手
						if(!$this->users[$k]['shou']){
	                        //如果没有握手,则进行握手处理
							$this->woshou($k,$buffer);
						}else{
	                        //走到这里就是该client发送信息了,对接受到的信息进行uncode处理
							$buffer = $this->uncode($buffer,$k);
							if($buffer==false){
								continue;
							}
	                        //如果不为空,则进行消息推送操作
							$this->send($k,$buffer);
						}
					}
				}
				
			}
			
		}
	    
	    //指定关闭$k对应的socket
		function close($k){
	        //断开相应socket
			socket_close($this->users[$k]['socket']);
	        //删除相应的user信息
			unset($this->users[$k]);
	        //重新定义sockets连接池
			$this->sockets=array($this->master);
			foreach($this->users as $v){
				$this->sockets[]=$v['socket'];
			}
	        //输出日志
			$this->e("key:$k close");
		}
	    
	    //根据sock在users里面查找相应的$k
		function search($sock){
			foreach ($this->users as $k=>$v){
				if($sock==$v['socket'])
				return $k;
			}
			return false;
		}
		
	    //传相应的IP与端口进行创建socket操作
		function WebSocket($address,$port){
			$server = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
			socket_set_option($server, SOL_SOCKET, SO_REUSEADDR, 1);//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;
		}
		
		
	    /*
	    * 函数说明:对client的请求进行回应,即握手操作
	    * @$k clien的socket对应的健,即每个用户有唯一$k并对应socket
	    * @$buffer 接收client请求的所有信息
	    */
		function woshou($k,$buffer){

	        //截取Sec-WebSocket-Key的值并加密,其中$key后面的一部分258EAFA5-E914-47DA-95CA-C5AB0DC85B11字符串应该是固定的
			$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));

	        //对已经握手的client做标志
			$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;
			}
			
		}
		
	    //与uncode相对
		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;  
		}
		
		//用户加入或client发送信息
		function send($k,$msg){
	        //将查询字符串解析到第二个参数变量中,以数组的形式保存如:parse_str("name=Bill&age=60",$arr)
			parse_str($msg,$g);
			
			$ar=array();

			if($g['type']=='add'){
	            //第一次进入添加聊天名字,把姓名保存在相应的users里面
				$this->users[$k]['name']=$g['ming'];
				$ar['type']='add';
				$ar['name']=$g['ming'];
				$key='all';
			}else{
	            //发送信息行为,其中$g['key']表示面对大家还是个人,是前段传过来的信息
				$ar['nrong']=$g['nr'];
				$key=$g['key'];

			}
	        //推送信息
			$this->send1($k,$ar,$key);
			//创建本地记录
			if($g['key']){
				$myfile = fopen("newfile.txt", "a+") or die("Unable to open file!");
				$userMessage = $k."-".$g['nr']."-".$g['key']."\r\n";
				fwrite($myfile, $userMessage);
				fclose($myfile);
			}
			
		}
		
	    //对新加入的client推送已经在线的client
		function getusers(){
			$ar=array();
			foreach($this->users as $k=>$v){
				$ar[]=array('code'=>$k,'name'=>$v['name']);
			}
			return $ar;
		}
		
		//$k 发信息人的socketID $key接受人的 socketID ,根据这个socketID可以查找相应的client进行消息推送,即指定client进行发送
		function send1($k,$ar,$key='all'){
			$ar['code1']=$key;
			$ar['code']=$k;
			$ar['time']=date('m-d H:i:s');
			$ar['users']=$this->getusers();
	        //对发送信息进行编码处理
			$str = $this->code(json_encode($ar));

	        //面对大家即所有在线者发送信息
			if($key=='all'){
				$users=$this->users;
	            //如果是add表示新加的client
				if($ar['type']=='add'){
					$ar['type']='madd';
					$ar['users']=$this->getusers();
					        //取出所有在线者,用于显示在在线用户列表中
					$str1 = $this->code(json_encode($ar)); //单独对新client进行编码处理,数据不一样
	                //对新client自己单独发送,因为有些数据是不一样的
					socket_write($users[$k]['socket'],$str1,strlen($str1));
	                //上面已经对client自己单独发送的,后面就无需再次发送,故unset
					unset($users[$k]);
				}
	            //除了新client外,对其他client进行发送信息。数据量大时,就要考虑延时等问题了
				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));
			}
		}
		
		//用户退出向所用client推送信息
		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);
		}
	}
?>

js

var myImg = "imgs/link3.jpg"; 
            var L_name = "游客";
        // function faSong(content){
        //     var Words = $("#words");
        //     //得到时间
        //     var date = new Date();
        //     var seperator1 = "-";
        //     var seperator2 = ":";
        //     var month = date.getMonth() + 1;
        //     var day = date.getDate();
        //     var hours = date.getHours();
        //     var minutes = date.getMinutes();
        //     var seconds = date.getSeconds();

        //     var l_time = "";
        //     var hoursX = "";
        //     if (hours>12) {
        //         hoursX = "下午"+(hours-12)+seperator2+minutes;
        //     }else{
        //         hoursX = "上午"+hours+seperator2+minutes;
        //     }
        //     l_time = '<p class="talk_time">'+hoursX+'</p>';
        //     Words.append(l_time);

        //     str = '<div class="atalk"><img src="./aimg.gif"><span class="say" id="asay">' + content +'</span><span class="atalk_XZ sayxz"></span></div>' ;
        //     Words.append(str);
        //     return false;
        // }
$(function(){
//握手监听函数
    //给所有人添加一个公共的页面id
    $("#all").prop("L_UserId","all");
    var key='all',mkey;
    var users={};
    var url='ws://127.0.0.1:8000';
    var so=false,n=false;
    var lus=A.$('user_list'),lct = "";
    st();
    function st(){
        n=prompt('请给自己取一个响亮的名字:');
        n=n.substr(0,16);
        if(!n){
            return ;    
        }
        //创建socket,注意URL的格式:ws://ip:端口
        so=new WebSocket(url);
        console.log(0);
        so.onopen=function(){
            //状态为1证明握手成功,然后把client自定义的名字发送过去
            if(so.readyState==1){
                so.send('type=add&ming='+n);
            }
        }
        
        //握手失败或者其他原因连接socket失败,则清除so对象并做相应提示操作
        so.onclose=function(){
            so=false;
            // lct.appendChild(A.$$('<p class="c2">退出聊天室</p>'));
        }
        
        //数据接收监听,接收服务器推送过来的信息,返回的数据给msg,然后进行显示
        so.onmessage=function(msg){

            eval('var da='+msg.data);
            var obj=false,c=false;
            var msgData = msg.data;
            var cccc = $.parseJSON(msgData);
            var boxid = cccc["code1"]+cccc["code"];
            //发送给谁的名字
            var userList = cccc["users"];
            for (var i = 0; i<userList.length;i++) {
                if(userList[i]["code"] == cccc["code"]){
                    L_name = userList[i]["name"];
                }
            }
            if(da.type=='add'){
                var obj=A.$$('<div class="itme"><img src="imgs/link1.jpg"><div class="name"><span class="name_text">'+da.name+'</span></div></div>');
                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;
                MyCode = da.code;
                da.users.unshift({'code':'all','name':'大家'});
                for(var i=0;i<da.users.length;i++){
                    var obj=A.$$('<div class="itme"><img src="imgs/link1.jpg"><div class="name"><span class="name_text">'+da.users[i].name+'</span></div></div>');
                    lus.appendChild(obj);
                    if(mkey!=da.users[i].code){
                        cuser(obj,da.users[i].code);
                    }else{
                        //自己
                        $(obj).children("img").attr("src","imgs/link3.jpg");
                    }
                }
                obj=A.$$('<p><span>['+da.time+']</span>欢迎'+da.name+'加入</p>');
                users.all.className='itme';
            }
            if(obj==false){
                if(da.type=='rmove'){
                    var obj=A.$$('<p class="c2"><span>['+da.time+']</span>'+users[da.nrong].innerHTML+'退出聊天室</p>');
                    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 '';
                    });
                    //da.code 发信息人的code
                    if(da.code1==mkey){
                        obj=A.$$('<div class="atalk"><img src="imgs/link1.jpg"><span class="say" id="asay">'+da.nrong+'</span><span class="atalk_XZ sayxz"></span></div>');
                        c=da.code;
                    }else if(da.code==mkey){
                        if(da.code1!='all'){
                             obj=A.$$('<div class="btalk"><img src="imgs/link3.jpg"><span class="say" id="bsay">'+da.nrong+'</span><span class="btalk_XZ sayxz"></span></div>');
                            boxid = cccc["code"]+cccc["code1"];
                        }
                        else
                         obj=A.$$('<div class="btalk"><img src="imgs/link3.jpg"><span class="say" id="bsay">'+da.nrong+'</span><span class="btalk_XZ sayxz"></span></div>');
                        c=da.code1;
                    }else if(da.code==false){
                        obj=A.$$('<p><span>['+da.time+']</span>'+da.nrong+'</p>');
                    }else if(da.code1){
                        obj=A.$$('<div class="atalk L_all"><img src="imgs/link1.jpg"><span id="L_UserName">'+L_name+'</span><span class="say" id="asay">'+da.nrong+'</span></div>');//<span class="atalk_XZ sayxz"></span>
                        c=da.code;
                    }
                }
            }
            if(c){
                    obj.children[1].onclick=function(){
                        users[c].onclick();
                    }
                }
                //第一次进入不创建
                if(da.type=='madd' || da.type=='add' || da.type=='rmove'){

                }else{
                    if (cccc["code1"] == "all") {
                        boxid = "all";
                    }
                    // 得到所有的消息页面 然后通过循环查找是否存在
                    var messageList = $(".MessageBox");
                    var isUserId = false;
                    for(var i = 0;i<messageList.length;i++){
                         // $(messageList[i]).addClass("showMB");
                        if(boxid == $(messageList[i]).prop("L_UserId")){
                            // $(".MessageBox").removeClass("showMB");
                            // $(messageList[i]).addClass("showMB");
                            var isUserId = true;
                            break;
                        }
                    }
                    //不存在就添加到
                    if (isUserId) {

                    }else{
                        var xinYeMian = '<div class="MessageBox"><div class="talk_show" id="words"><div class="talk_input"><form action="" method="post" style="height:100%;"><textarea name="yj" class="talk_word" id="talkwords"></textarea></form><input type="button" id="fs" value="发送(S)" class="talk_sub" id="talksub" ></div></div>';
                        $("#talk_con").append(xinYeMian);//不存在就创建创建好在找
                        var index = $(".MessageBox").length-1;
                        var htmlObj = $(".MessageBox")[index];
                        $(htmlObj).prop("L_UserId",boxid);
                        $(htmlObj).attr("L_UserId",boxid);
                    }
                }
                    //发消息
            var messageList = $(".MessageBox");
            var isUserId = false;
            for(var i = 0;i<messageList.length;i++){
                if(boxid == $(messageList[i]).prop("L_UserId")){
                  $(messageList[i]).children("#words").append(obj);
                    break;
                }
            }
                lct.scrollTop=Math.max(0,lct.scrollHeight-lct.offsetHeight);
           
            
        }
    }
    $("#talk_con").delegate("#fs","click",function(){
                //this 发送是按钮
               //它父元素的父元素显示聊天框
                var classMess = this.parentNode.parentNode;
                //它父元素的子元素输入的文本框
                var classXiaoX = $(this.parentNode).find(".talk_word");

                var TalkWords = $(classXiaoX);
                // 定义空字符串
                var str = "";
                if(TalkWords.val() == ""){
                     // 消息为空时弹窗
                    alert("消息不能为空");
                    return;
                }
                 if(!so){
                     return st();
                }
                //发送消息
                so.send('nr='+esc(TalkWords.val())+'&key='+key);
                //将输入框清空
                TalkWords.val("");
    });
    // A.$('talkwords').onkeydown=function(e){
    //     var e=e||event;
    //     if(e.keyCode==13){
    //         A.$('fs').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(){
            //改标题
            $("#talk_Mess").text(this.children[1].innerText);
            t.parentNode.children.rcss('ck','');
            t.rcss('','ck');
            key=code;
            // 创建页面
             var boxid = MyCode+code;
             if (code == "all") {
                    boxid = "all";
                }
                var xinYeMian = '<div class="MessageBox"><div class="talk_show" id="words"><div class="talk_input"><form action="" method="post" style="height:100%;"><textarea name="yj" class="talk_word" id="talkwords"></textarea></form><input type="button" id="fs" value="发送(S)" class="talk_sub" id="talksub" ></div></div>';

                // 得到所有的消息页面 然后通过循环查找是否存在
                var messageList = $(".MessageBox");
                var isUserId = false;
                for(var i = 0;i<messageList.length;i++){
                     $(messageList[i]).addClass("showMB");
                    if(boxid == $(messageList[i]).prop("L_UserId")){
                        $(".MessageBox").removeClass("showMB");
                        $(messageList[i]).addClass("showMB");
                        var isUserId = true;
                        break;
                    }
                }
                //不存在就添加到
                if (isUserId) {

                }else{
                    $("#talk_con").append(xinYeMian);
                    var index = $(".MessageBox").length-1;
                    var htmlObj = $(".MessageBox")[index];
                    $(htmlObj).prop("L_UserId",boxid);
                    $(htmlObj).attr("L_UserId",boxid);
                    $(".MessageBox").removeClass("showMB");
                    $(htmlObj).addClass("showMB");
                }
                $("#user_mess").hide();
        }
    }
             $(".talk_Close").on("click",function(){
                $(".right_meil").css("right","-502px");
             })
             var ui = $(".service");
             $(".service").on("click",function(){
                var index = $(this).index();
                $(".service").removeClass().eq(index).addClass("focus");
                return false;
             })
             $(".talk_back").on("click",function(){
                $(".user_mess").show();
             });
             
        })
    
        function onButFaSong(code){
            console.log("文本框"+MyCode);
                // 拼接
                // L_'+MyCode+code
                var classMess = "#L_"+MyCode+code+ " #words";//显示聊天框
                var classXiaoX = "#L_"+MyCode+code+" #talkwords";//输入的文本框
                var TalkWords = $(classXiaoX);
                // 定义空字符串
                var str = "";
                if(TalkWords.val() == ""){
                     // 消息为空时弹窗
                    alert("消息不能为空");
                    return;
                }
                //拼接消息模块
                str = '<div class="btalk"><img src="./bimg.jpg"><span class="say" id="bsay">' + TalkWords.val() +'</span><span class="btalk_XZ sayxz"></span></div>' ;  
                TalkWords.val("");
                $(classMess).append(str);
                 if(!so){
                     return st();
                }
                so.send('nr='+esc(TalkWords)+'&key='+MyCode);
                console.log(so);
        }
       function showMe(){
            $(".user_mess").show();
       }
之前在一个网站看到过,拿过来修改了一下,多了一个一对一聊天
不同用户不同页面
小白一个如有大神路过,望指点

php命令窗口运行php start.php 就好了
php版本好像在5.3以上,我用的是5.5
php和前端实现websocket实现实时聊天

有需要可以拿去
链接 密码: awcx


爱收集 , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权 , 转载请注明php和前端实现websocket实现实时聊天
喜欢 (6)
支付宝[]
分享 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址