ThinkPHP5 使用swoole 网页版实时聊天 发表于 2020-06-02
| 字数总计: 2.6k | 阅读时长: 14分钟 | 阅读量: |
目前官方已经开放了 ThinkPHP5.1版本的swoole:https://github.com/top-think/think-swoole
安装 服务器必须开启swoole扩展,如使用宝塔,则在对应的php版本—设置—安装扩展—选择swoole4
安装。
安装完之后用命令执行php -m
,看到swoole
即代表安装完成。
在swoole扩展安装后,tp5的项目根目录下执行composer命令安装think-swoole:
1 composer require topthink/think-swoole
注意:对应的端口要放行,这里使用的端口是9501
服务端启动 swoole
相关数据表 如果有登录注册,无则忽略
用户表(简约版)
1 2 3 4 5 6 7 8 9 10 11 CREATE TABLE `dy_user` ( `uid` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '账号', `password` varchar(64) NOT NULL COMMENT '密码', `create_time` int(10) DEFAULT NULL COMMENT '创建时间', `login_time` int(10) DEFAULT NULL COMMENT '上次登录时间', `ip` varchar(32) CHARACTER SET utf8 DEFAULT NULL COMMENT '注册ip', `pass_time` int(11) DEFAULT NULL COMMENT '密码错误时间', `pass_error` tinyint(1) unsigned DEFAULT '0' COMMENT '密码错误次数', PRIMARY KEY (`uid`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
token表
1 2 3 4 5 6 7 8 CREATE TABLE `dy_user_token` ( `id` int(11) NOT NULL AUTO_INCREMENT, `uid` int(11) NOT NULL DEFAULT '0' COMMENT '用户uid', `access_token` varchar(250) NOT NULL DEFAULT '' COMMENT '随机字符串', `create_time` int(11) NOT NULL DEFAULT '0' COMMENT '添加时间', PRIMARY KEY (`id`) USING BTREE, KEY `uid` (`uid`) USING BTREE COMMENT '用户ID' ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='保存token随机字符串';
相关代码 服务端 在控制器下新建一个控制器继承Server
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 <?php namespace app\index\controller; // 必须 use 并继承 \think\swoole\Server 类 use think\swoole\Server; use think\Db; use think\facade\Cache; class Swoole extends Server { protected static $token; protected $host = '0.0.0.0';// 监听所有地址 protected $port = 9501;// 监听 9501 端口 protected $serverType = 'socket'; protected $mode = SWOOLE_PROCESS;// 指定运行模式为多进程 protected $sockType = SWOOLE_SOCK_TCP;// 指定 socket 的类型为 ipv4 的 tcp socket protected static $uid = ''; protected $option = [ 'worker_num' => 4, // 设置启动的Worker进程数 'daemonize' => false, //守护进程化。 'backlog' => 128, //Listen队列长度, 'dispatch_mode' => 2, // 'heartbeat_check_interval' => 5, // 延时 // 'heartbeat_idle_time' => 100, // 心跳 ]; //收到信息时回调函数 // public function onRequest($request, $response) // { // var_dump('qweqw'); // } //建立连接时回调函数 public function onOpen($server,$req) { $fd = $req->fd;//客户端标识 $token = $req->get['token'];//客户端传递的用户登录token $token_info = Db::name("user_token")->alias("a") ->join('user b',"b.uid=a.uid") ->where(['a.access_token'=>$token]) ->field('a.create_time,b.uid,b.username')->find(); $uid = $token_info['uid']; $username = $token_info['username']; $end_time = $token_info['create_time'] + 60*60*24;//一天 if (time() > $end_time) { $arr = array('status'=>2,'message'=>'登录已失效,请重新登录'); $server->push($fd, json_encode($arr)); $server->close($fd); return; } //给用户绑定uid //清除解绑 Cache::rm('_fd_'.$fd); $data = ['fd'=>$fd,'uid'=>$uid,'username'=>$username,'token'=>$token]; //fb绑定uid Cache::set('_fd_'.$fd, $data); echo "用户{$uid}建立了连接,标识为{$fd}\n"; } //接收数据时回调函数 public function onMessage($server,$frame) { $fd = $frame->fd; $message = $frame->data; //通过fd查询用户uid $fd_data = Cache::get('_fd_'.$fd); $data['token'] = $fd_data['token']; $data['message'] = $fd_data['username'].'发送了:'.$message; $data['post_time'] = date("m/d H:i",time()); $arr = array('status'=>1,'message'=>'success','data'=>$data); //仅推送给当前连接用户 //$server->push($fd, json_encode($arr)); //推送给全部连接用户 foreach($server->connections as $fd) { $server->push($fd, json_encode($arr)); } } //连接关闭时回调函数 function onClose(\swoole_server $server, int $fd, int $reactorId) // public function onClose($server,$fd) { $fd_data = Cache::get('_fd_'.$fd); Cache::rm('_fd_'.$fd);//清除fd绑定标识 echo "标识{$fd}关闭了连接\n"; } }
客户端 这里使用的是网页
html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 <!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" /> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /> <title>Chat</title> <link rel="stylesheet" type="text/css" href="/public/static/index/css/bootstrap-grid.min.css" /> <link rel="stylesheet" type="text/css" href="/public/static/index/css/chat.css" /> <script type="text/javascript" src="/public/static/admin/js/jquery.min.js"></script> <script type="text/javascript" src="/public/static/plugins/layer/layer.js"></script> <script src="/public/static/index/js/flexible.js"></script> </head> <body> <header class="header"> <!-- <a class="back" href="javascript:history.back()"></a> --> <h5 class="tit">实时聊天</h5> <!-- <div class="right">退出</div> --> </header> <!-- 聊天内容 start--> <div class="message"></div> <!-- 聊天内容 end--> <!-- 底部 start--> <div class="footer"> <img src="/public/static/index/images/hua.png" alt="暂无此功能" /> <img src="/public/static/index/images/xiaolian.png" alt="暂无此功能" /> <input type="text" maxlength="300" id="msg" /> <p id="sendBtn">发送</p> </div> <!-- 底部 end--> </body> </html> <script type="text/javascript"> $(function () { var token = "{$token}";//用户token,需要服务端新建一个控制器返回该页面token值 //判断浏览器是否支持WebSocket var supportsWebSockets = 'WebSocket' in window || 'MozWebSocket' in window; if (supportsWebSockets) { //建立WebSocket连接(ip地址换成自己主机ip) var ws = new WebSocket("ws://106.55.11.78:9501?token="+token); ws.onopen = function () { layer.msg('服务器连接成功',{shade:0.1,icon:1,time:600}); }; ws.onerror = function () { layer.msg('服务器连接失败',{shade:0.1,icon:2,time:600}); }; ws.onmessage = function (evt) { var data = $.parseJSON(evt.data); //错误提示 if(data.status != 1){ layer.alert(data.message,{icon:2}); return; } //消息返回 if (data.status==1 && data.data.message!='') { var html = ""; if (data.data.token == token) { html += "<div class=\"show\"><div class=\"time\">"+data.data.post_time+"</div><div class=\"msg\"><img src=\""+data.data.head_img+"\" alt=\"\" /><p><i clas=\"msg_input\"></i>"+data.data.message+"</p></div></div>"; }else{ html += "<div class=\"send\"><div class=\"time\">"+data.data.post_time+"</div><div class=\"msg\"><img src=\""+data.data.head_img+"\" alt=\"\" /><p><i clas=\"msg_input\"></i>"+data.data.message+"</p></div></div>"; } } $(".message").append(html); setTimeout(function () { ($('.message').children("div:last-child")[0]).scrollIntoView();//向上滚动 },100); }; ws.onclose = function (res) { }; //按钮发送 $("#sendBtn").click(function () { var contents = $("#msg").val().trim(); if(contents == null || contents == ""){ layer.msg('内容为空',{shade:0.1,icon:2,time:600}); return false; }else{ ws.send(contents); $("#msg").val(""); } }); //回车发送 $("#msg").keydown(function (evel) { var that = $(this); if (evel.keyCode == 13) { evel.cancelBubble = true; evel.preventDefault(); evel.stopPropagation(); var contents = that.val().trim(); if(contents == null || contents == ""){ layer.msg('内容为空',{shade:0.1,icon:2,time:600}); return false; }else{ ws.send(contents); that.val(""); } } }); }else{ layer.alert("您的浏览器不支持 WebSocket!"); } }); </script> <script type="text/javascript" charset="utf-8"> //发送按钮变色 $(function(){ $('.footer').on('keyup','input',function(){ if($(this).val().length>0){ $(this).next().css('background','#114F8E').prop('disabled',true); }else{ $(this).next().css('background','#ddd').prop('disabled',false); } }) /*$('.footer p').click(function(){ show("/public/static/index/images/touxiangm.png",$(this).prev().val()); test(); })*/ }) </script>
css
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 h5{ margin: 0; } img{ max-width: 100%; vertical-align: middle } input{ outline: none; } body{ max-width: 720px; margin: 0 auto; background: #f1f1f1; color:#333; font-size: 0.26rem; } .header{ border-bottom: 1px solid #dfdfdf; padding:0 0.2rem; height: 1rem; line-height: 1rem; background: #fff; position: fixed; width: 100%; max-width: 720px; box-sizing: border-box; z-index: 100; } .back{ position: absolute; top: 0; left: 0.3rem; background:url(./images/left.png) no-repeat; width: 0.2rem; height:0.4rem; margin-top: 0.34rem; background-size: 0.2rem 0.4rem; } .header .tit{ font-size: 0.32rem; vertical-align: middle; text-align: center; height: 1rem; line-height: 1rem; font-weight: normal; } .header .right{ position: absolute; right: 0.3rem; top: 0; float: none; font-size: 0.24rem; line-height: 1.2rem; } .message{ background-color: #f1f1f1; padding: 1.2rem 0.3rem 1rem 0.3rem; } .time{ font-size:0.24rem; color:#999; margin-bottom: 0.3rem; text-align: center; } .footer{ position: fixed; bottom: 0; height:1rem; background-color:#fff; line-height:1rem; width: 100%; max-width: 720px; border-top: 1px solid #ddd; } .footer img{ margin-left:0.2rem; width: 0.5rem; } .footer input{ margin-left:0.2rem; width:3.5rem; height:0.64rem; border-radius: 0.1rem; border:0.01rem solid #ddd; padding : 0 0.15rem; } .footer p{ width:1.2rem; height:0.68rem; font-size:0.3rem; color:#fff; line-height:0.68rem; text-align:center; background-color:#ddd; border-radius: 0.1rem; float:right; margin-top:0.2rem; margin-right:0.2rem; } .send:after,.show:after,.msg:after{ content: ""; clear: both; display: table; } .msg>img{ width: 0.8rem; float: left; } .msg>p{ float: left; margin:0 0.4rem; padding: 0.25rem; background: #fff; font-size: 0.3rem; position: relative; border-radius: 0.2rem; max-width:5rem ; box-sizing: border-box; } .msg_input{ position: absolute; background: url(./images/msg-input.png) no-repeat; background-size: 0.31rem auto; width: 0.31rem; height: 0.51rem; left: -0.31rem; top: 0.25rem; } .show .msg img,.show .msg p,.show .msg{ float: right; } .show .msg_input{ left: auto; right: -0.11rem; transform:rotate(180deg); -ms-transform:rotate(180deg); /* IE 9 */ -moz-transform:rotate(180deg); /* Firefox */ -webkit-transform:rotate(180deg); /* Safari 和 Chrome */ -o-transform:rotate(180deg); /* Opera */ } .send,.show{ padding-bottom: 0.3rem; } .alert_novip,.flower_num,.give_flower{ display: none; padding: 0.3rem 0.5rem; font-size: 0.28rem; } .alert_novip p,.flower_num p{ margin-bottom: 0.45rem; .layui-layer-wrap button{ font-size: 0.28rem; padding: 0.2rem 0.3rem; margin-top: 0.1rem; background: #f8f8f8; border-radius: 10px; } } .flower_num button{ padding: 0.2rem 0.5rem; border-radius: 10px; } .layui-layer-wrap button:first-child{ float: left; } .layui-layer-wrap button:last-child{ float: right; background: #FF7171; color: #fff; } .alert_novip button{ padding: 0.2rem 0.3rem; border-radius: 10px } .flower{ width: 0.8rem; margin: 0 auto; } .give_flower{ text-align: center; } .give_flower p{ text-align: center; line-height: 1.5; } .give_flower input{ width: 1rem; margin-right: 0.1rem; margin-top: 0.2rem; } .give_flower button{ display: block; width: 3rem; font-size: 0.28rem; margin: 0 auto; margin-top: 0.6rem; float: none!important; line-height: 0.65rem; border-radius: 10px; }
html样式js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 (function (doc, win) { var docEl = doc.documentElement, resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize', recalc = function () { var clientWidth = docEl.clientWidth; if (!clientWidth) return; if(clientWidth>=720){ docEl.style.fontSize = '100px'; }else{ docEl.style.fontSize = 100 * (clientWidth / 720) + 'px'; } }; if (!doc.addEventListener) return; win.addEventListener(resizeEvt, recalc, false); doc.addEventListener('DOMContentLoaded', recalc, false); })(document, window);