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字符,通过一系列的判断,就能知道是什么类型,做如何处理了。