1.准备工作
1.gateway-worker扩展 (composer仓库中的插件与文档存在一定偏差,所以采用提供的demo包)gateway-worker
2.将下载的文件放在extend目录下(此demo的gateway-worker是3.1.0)
3.windows环境下启动extend文件夹之下的start_for_windows.bat
4.安装gateway-client扩展 –GatewayWorker3.0.8及以上版本请使用 3.0.13版本的GatewayClient
composer require workerman/gatewayclient -v 3.0.13
5.extend\GatewayWorker\vendor\workerman\gateway-worker\src\Lib 该目录下有gateway.php 修改配置
6.注意!!! 业务逻辑都是gatewayclient完成,gatewayworker只做一个启动服务与配置的操作,同时需要注意gateway_client的版本和gatewayworker是否适配
2.代码内容
events内容
class Events { /** * 当客户端连接时触发 * 如果业务不需此回调可以删除onConnect * * @param int $client_id 连接id */ public static function onConnect($client_id) { $resData = [ 'type' => 'init', 'client_id' => $client_id, 'msg' => 'connect is success' // 连接成功 ]; Gateway::sendToClient($client_id, json_encode($resData)); } /** * 当客户端发来消息时触发 * @param int $client_id 连接id * @param mixed $message 具体消息 */ public static function onMessage($client_id, $message) { //message里需要包含user_id/order_id/client_id,业务参数自己定义 $msg = json_decode($message,true); if (!$msg){ return ''; } //可能存在一种情况,前端已经连接上了,但是用户一直刷新这个这个页面 $res = Gateway::getClientIdByUid($msg['user_id']); var_dump($res); //初始化 if ($msg['type'] == 'init'){ $returnMessage = [ 'type' => 'init', 'content' => 'init', ]; if ($res){//如果绑定过就自动解绑,但是实际测试不解除绑定根据client_id获取到的只有一个数据并不是多个 Gateway::unbindUid($res[0],$msg['user_id']); } Gateway::bindUid($client_id,$msg['user_id']); } //如果是心跳检测 if ($msg['type'] == 'heartbeat'){ $returnMessage = [ 'type' => 'heartbeat', 'content' => 'heartbeat', ]; } //如果是正常数据 if ($msg['type'] == 'MessageReceived'){ $returnMessage = [ 'type' => 'MessageReceived', 'content' => 'MessageReceived', ]; } $returnMessage = json_encode($returnMessage); Gateway::sendToClient($client_id,$returnMessage); } /** * 当用户断开连接时触发 * @param int $client_id 连接id */ public static function onClose($client_id) { $resDatas = [ 'type' => 'colse', 'client_id' => $client_id, 'msg' => 'close is success' ]; // 向所有人发送 GateWay::sendToAll(json_encode($resDatas)); } }
重新连接之后client_id并不是多个 如图
模拟前端代码内容
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> 这里只做接收消息展示用, 发送心跳包和init. 刷新页面可以看到init init是链接成功展示,发送消息,在event里面可以看到 </body> <script> ws = new WebSocket("ws://"+document.domain+":8282"); ws.onopen = function() { console.log("连接成功"); var init = JSON.stringify({"type":"init","user_id":"1"}); ws.send(init); //10秒发送一次心跳包 var heart = JSON.stringify({"type":"heartbeat","user_id":"1"}); setInterval(function(){ ws.send(heart); },10000); }; // 服务端主动推送消息时会触发这里的onmessage ws.onmessage = function(e){ console.log(e); var data = eval("("+e.data+")"); // console.log(data); var type = data.type || ''; // console.log(type); switch(type){ case 'heartbeat': console.log('heartbeat'); break; // 当mvc框架调用GatewayClient发消息时直接打印出来 case 'MessageReceived': // 接收到tp中主动发送数据提示的标记 console.log('MessageReceived', data); break; default : console.log(e.data); } }; </script> </html>
也可以这样连接
f12–控制台–输入 –new WebSocket(‘ws://localhost:8282’)
网络-WS-消息-可以看到client_id
连接成功如图所示
实际代码中调用
class Message extends Controller { // 发送消息 进行操作 public function send($serverId) { require_once ROOT_PATH . "extend/GatewayWorker/vendor/workerman/gateway-worker/src/Protocols/GatewayProtocol.php"; require_once ROOT_PATH . "extend/GatewayWorker/vendor/workerman/gateway-worker/src/Lib/Gateway.php"; require_once ROOT_PATH . "extend/GatewayWorker/vendor/workerman/gateway-worker/src/Lib/Context.php"; require_once ROOT_PATH . "extend/GatewayWorker/vendor/workerman/gateway-worker/src/Lib/Db.php"; require_once ROOT_PATH . "extend/GatewayWorker/vendor/workerman/gateway-worker/src/Lib/DbConnection.php"; $client_id = \GatewayWorker\Lib\Gateway::getClientIdByUid($serverId); if ($client_id) { //$num = count($client_id); $resData = [ 'type' => 'new_order', 'client_id' => $client_id, // 'msg' => cdnurl(config('site.order_sound'), true), 'msg' => 'try this', ]; \GatewayWorker\Lib\Gateway::sendToClient($client_id[0], json_encode($resData)); } } }
}
重连机制
1.创建一个全局生命周期的定时器(注意只能创建一次,无限制创建定时器客户端会崩)
2.连接建立后,周期性的每30s向服务器发送心跳包,比如ping
3.服务器收到客户端心跳包ping时,回复pong
4.客户端仅记录最后收到pong的时间戳
4.由全局定时器检查网络状况是否良好
if (lastPong + 30 *1.5 < now) {
//超时重连 todo
1.连接时候用设备id和客户端id,请求接口绑定;
2.做好心跳
3.做好重连机制(低段位的肯定在onClose注册个断线重连,这种基本就是傻)
业务代码案例
//注册 \GatewayClient\Gateway::$registerAddress = '127.0.0.1:1238'; //是否在线 $res =\GatewayClient\Gateway::isOnline($info->client_id); if ($res == 1){ \GatewayClient\Gateway::bindUid($info->client_id,$this->auth->id); $socket = new Common(); $data = ['userinfo' => $this->auth->getUser()]; $data['userinfo']['token'] = $this->auth->getToken(); $socket->send($this->auth->id, 'scan_login',$data); }