作为程序员一定要保持良好的睡眠,才能好编程

Swoole聊天室功能实现

发布时间:2019-03-26


swoole 通过长连接实现websocket 聊天室功能:


server.php

<?php

class Ws{
    public $server=null;
		public $setAttrs=[];
		public $loginUser=[];
    CONST HOST="0.0.0.0";
    const PORT=9992;
    public function __construct(){
        $this->server=new swoole_websocket_server(self::HOST,self::PORT);
        $this->server->on("open",[$this,"onOpen"]);
        //$this->server->on("WorkerStart",[$this,"onWorkerStart"]);
        $this->server->on("message",[$this,"onMessage"]);
        $this->server->on("close",[$this,"onClose"]);
    }
		
		public function onWorkerStart($server,$worker_id){
			 swoole_timer_tick(1000,function(){
							 
				//printf("%s用户连接数量:%d\n",date('Y-m-d H:i:s'),count($this->loginUser));
			});
		}
    public function onOpen($server,$frame){
				if(!isset($this->loginUser[$frame->fd])){
					$this->loginUser[$frame->fd]=$frame;
				}
				echo $frame->fd."---连接成功\n";
    }
        
    public function onMessage($server,$frame){


				echo $frame->data." \n";

				if ($frame->data == 'hh') {
					$mmpic = [
					 			'http://pic15.photophoto.cn/20100402/0036036889148227_b.jpg',
                'http://pic23.nipic.com/20120814/5914324_155903179106_2.jpg',
                'http://pic40.nipic.com/20140403/8614226_162017444195_2.jpg'		
					];
					$picKey = array_rand($mmpic); //随机返回一张图片

					var_dump($picKey);

					//$server->push($frame->fd, $this->reply($frame->data));
					foreach($server->connections as $fd){

						$server->push($fd,$mmpic[$picKey]);
					}
					//$server->push($frame->fd, file_get_contents(__DIR__.'/1.png'), WEBSOCKET_OPCODE_BINARY);
				} else {
								
								foreach($server->connections as $fd) { 
												
								   //$server->push($fd, json_encode($data)); 
							
									$server->push($fd, $this->reply($frame->data,$frame->fd));
								}
								
				}				

							
				//$server->push($frame->fd,"服务器返回消息说:".$frame->data."--".date("Y-m-d H:i:s"));
		}

    public function onClose($server,$fd){
				if(isset($this->loginUser[$fd])){
           unset($this->loginUser[$fd]);
        }					 
				echo $fd." 客户端已经关闭";
		}
		


    /**
     * @param $key worker_num
     * @param $val 2
     */
    public function set($key,$val){
        $this->setAttrs[$key]=$val;
    }

    //启动服务
    public function start(){
        $this->server->set($this->setAttrs);
        $this->server->start();
		}
	
		private function reply($str,$who='') {
        $str = mb_strtolower($str);
        switch ($str) {
            case 'hello':
                $res = 'Hello, Friend.';
                break;

            case 'fuck':
                $res = 'Fuck bitch.';
                break;
            case 'ping':
                $res = 'PONG.';
                break;
            case 'time':
                $res = date('Y-m-d H:i:s');
                break;
            
            default:
                $res = $str;
                break;
        }
        return empty($who)?$res:"客户".$who."say:".$res;
    }




}



$_ws=new Ws();

//echo $_ws->getMemoryUsage();
$_ws->set("worker_num",1);
$_ws->set("daemonize",0);
$_ws->start();



chat.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title></title>
    <style type="text/css">
        #container{
            overflow-y: scroll;
        }
        #container div{
            font-size: 14px;
            color:#666;
            padding: 5px 0px;
            margin:0px auto;
            width:95%;

        }
    </style>

    <script type="text/javascript">

        window.onload=function(){
            var container=document.getElementById("container");
            var submitButton=document.getElementById("submit");
            var message=null;
            var webSocket=new WebSocket("ws://172.28.81.174:9992");

            webSocket.onopen=function(event){
                webSocket.send("你好!");
                console.log("服务器连接成功...");
            }

            webSocket.onmessage=function(event){
                console.log("服务器回复说:"+event.data);
				
				var patt=/^http/i;	
				
				//if(event.data instanceof Blob) {
				if(patt.test(event.data)){	
					 //var img = '<img src="'++'" width="180"/>';
					 //container.appendChild(createDiv("图片不解析了"));
					 container.appendChild(createDiv(event.data,"img")); 
				}else {
					container.appendChild(createDiv(event.data));
				}
				
				//container.scrollTop = container.scrollHeight;               
            }

            webSocket.onclose=function(event){
                console.log("connect close");
            }

            submitButton.onclick=function(){
                message=document.getElementById("message").value;
                if(message){
                    webSocket.send(message);
                    document.getElementById("message").value="";
                }else{
                    alert("请输入内容后,点击发送");
                }
            }

            function createDiv(message,type){
				
				 
				//rerurn "<div>"+message+"</div>";
                var div=document.createElement("div");
				if(typeof(type)==="undefined"){
				   var textNode=document.createTextNode(message);
				   div.appendChild(textNode);
				}else{
				   var imageNode=document.createElement("img");
				   imageNode.width=180;
				   imageNode.src=message;
				   div.appendChild(imageNode);
				}
              
                return div;
            }
        }
    </script>
</head>
<body>
<div id="container" style="border:1px solid #ccc; height:360px;width:600px;">

</div>
<input type="text" id="message"><input type="button" id="submit" value="发送">

</body>
</html>






当用户输入 hh  会自动显示随机美女图片。


输入 ping  会返回  pong

输入 time 会返回当前时间




运行截图:





问题点:


1、如何获取到连接到webSocket所有的用户:

    1、可以通过  $server->connections 这个获取到

    2、通过开启一个 memory Table内存数据表,保留着所有onOpen 进来用户的fd

    3、通过redis、保存

    4、通过类中静态变量保存


    其中 1/2/4 当服务停止、数据会全部消失。 redis 保存还保险一些吧。

    


2、什么时间webSocket会认为已经退出?

     触发了 onClose 方法后,清除掉连接,这样就认为用户已经退出了。


      这需要一定的业务逻辑

     当用户关闭,重新打开后,则通过cookie机制、或会话机制、动态查找数据,显示在用户窗口里。        



3、如何群发消息给用户?

     通过 $server->connections 这里保存着登录服务器的用户,可以找到fd 给用户发送消息。


4、聊天室是怎么建立的? 只是发送部分人

      通过html传输的数据判断是否群聊,如果是群聊,找到群聊id ,找到群里的用户,并循环发送在线用户。

       可以通过 task 任务 后台进行发送,这样可以快速响应、返回


5、websocket 刷新时,认为这个用户已经退出,重新开启了一个连接?

      这时候如何保证一个用户刷新,还保留原来的fd?或不断线?




6、通过websocket 发送二进制信息:

 swoole php 代码   

$server->push($frame->fd, file_get_contents(__DIR__.'/1.png'), WEBSOCKET_OPCODE_BINARY);

   js 代码

//如果是blob类型,则解析一下图片,
if(event.data instanceof Blob) {	
	 var img = '<img src="'+window.URL.createObjectURL(e.data)+'" width="180"/>';
	 
	 container.appendChild(createDiv(event.data,"img")); 
}else {
	container.appendChild(createDiv(event.data));
}


服务器可以返回一个json字符,通过一系列的判断,就能知道是什么类型,做如何处理了。