我不辛苦,我命苦!
针对websocket中常见的几个问题做一个详细的总结说明,具体要说的重点大概有下面3个:
- 心跳检测的必要性
- 校验客户端连接的有效性
- 客户端的重连机制
心跳检测
swoole内置了心跳检测机制,我们只需要做如下简单的配置即可
1 2 3 4
| $serv->set([ 'heartbeat_check_interval' => N, 'heartbeat_idle_time' => M, ]);
|
如上,分别配置heartbeat_check_interval和heartbeat_idle_time参数,二者配合使用,其含义就是N秒检查一次,看看哪些连接M内没有活动的,就认为这个连接是无效的,server就会主动关闭这个无效的连接。
是不是说N秒server会主动向客户端发一个心跳包,没有收到客户端响应的才认为这个连接是死连接呢?那还要heartbeat_idle_time做什么,对吧?
swoole的实现原理是这样的:server每次收到客户端的数据包都会记录一个时间戳,N秒内循环检测下所有的连接,如果M秒内该连接还没有活动,才断开这个连接。
校验客户端连接的有效性
实际项目上线后,如果你的websocket server是对外开放的,就需要把ip修改为服务器外网的ip地址或者修改为0.0.0.0。
如此,也便带来了新的问题:任意客户端都可以连接到我们的server了,这个“任意”可不止我们自己认为有效的客户端,还包括你的我的所有的非有效或者恶意的连接,这可不是我们想要的。
如何避免这一问题呢?方法有很多种,比如我们可以在连接的时候认为只有get传递的参数valid=1才允许连接;或者我们只允许登录用户才可以连接server;再或者我们可以校验客户端每次send所携带的token,server对该值校验通过后才认为当前是有效连接等等。与此同时,server开启心跳检测,对于恶意无效的连接,直接干掉!
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
| <?php
class WebSocketServerValid { private $_serv; public $key = '^manks.top&swoole$';
public function __construct() { $this->_serv = new swoole_websocket_server("127.0.0.1", 9501); $this->_serv->set([ 'worker_num' => 1, 'heartbeat_check_interval' => 30, 'heartbeat_idle_time' => 62, ]); $this->_serv->on('open', [$this, 'onOpen']); $this->_serv->on('message', [$this, 'onMessage']); $this->_serv->on('close', [$this, 'onClose']); }
public function onOpen($serv, $request) { $this->checkAccess($serv, $request); }
public function onMessage($serv, $frame) { $this->_serv->push($frame->fd, 'Server: ' . $frame->data); } public function onClose($serv, $fd) { echo "client {$fd} closed.\n"; }
public function checkAccess($serv, $request) { if (!isset($request->get) || !isset($request->get['uid']) || !isset($request->get['token'])) { $this->_serv->close($request->fd); return false; } $uid = $request->get['uid']; $token = $request->get['token']; if (md5(md5($uid) . $this->key) != $token) { $this->_serv->close($request->fd); return false; } }
public function start() { $this->_serv->start(); } }
$server = new WebSocketServerValid; $server->start();
|
可以看到,checkAccess是授权方法,我们在onOpen回调内对uid以及token进行了校验,无效则关闭连接。
客户端重连机制
客户端重连机制又可以理解为一种保活机制,你也可以跟服务端的心跳检测在一起理解为双向心跳。即我们有一种需求是,如何能保证客户端和服务端的连接一直是有效的,不断开的。
其实很简单,对客户端而言,只要触发error或者close再或者连接失败,就主动重连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
| <script> var ws; var lockReconnect = false; var wsUrl = 'ws://127.0.0.1:9501';
function createWebSocket(url) { try { ws = new WebSocket(url); initEventHandle(); } catch (e) { reconnect(url); } }
function initEventHandle() { ws.onclose = function () { reconnect(wsUrl); }; ws.onerror = function () { reconnect(wsUrl); }; ws.onopen = function () { heartCheck.reset().start(); }; ws.onmessage = function (event) { heartCheck.reset().start(); } }
function reconnect(url) { if(lockReconnect) return; lockReconnect = true; setTimeout(function () { createWebSocket(url); lockReconnect = false; }, 2000); }
var heartCheck = { timeout: 60000, timeoutObj: null, serverTimeoutObj: null, reset: function(){ clearTimeout(this.timeoutObj); clearTimeout(this.serverTimeoutObj); return this; }, start: function(){ var self = this; this.timeoutObj = setTimeout(function(){ ws.send(""); self.serverTimeoutObj = setTimeout(function(){ ws.close(); }, self.timeout); }, this.timeout); } }
createWebSocket(wsUrl);
</script>
|