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