十九、Swoole 基础学习笔记 - Swoole WebSocket 常见问题
hi,我是温新,一名PHPer
文章基于 Swoole 5.0.1 版本编写。
学习目标:了解 websocket 存在哪些问题及如何解决
说明:本篇文章结合官方文档编写及参考网络资料编写,虽非全部原创,但也是结合了自己的理解,若转载请附带本文 URL,编写不易,持续编写更不易,谢谢!
WebSocket 服务启动后就万事大吉了吗?答案当然是否定的,WebSocket 有如下几个问题需要了解:
- 1、心跳检测的必要性;
- 2、校验客户端连接的有效性;
- 3、客户端的重连机制。
心跳检测
心跳检测,是不是很熟悉,没错,在之前的文章中进行过学习。关于心跳检测本篇文章不再啰嗦,详情可以参考 11、Swoole 基础学习笔记 - Swoole 心跳检测。
// 心跳配置
$server->set([
'heartbeat_check_interval' => 10,
'heartbeat_idle_time' => 60,
]);
校验客户端连接的有效性
new Swoole\WebSocket\Server('0.0.0.0', 9501);
它有问题吗?如果仅仅只在本地使用,当然没有问题。若是在生产环境,那问题就来了,0.0.0.0
意味着所有客户端都可以连接到我们的服务,这包含了有效客户端也包含了带有恶意的连接,对于这种结果,显示不是我们所需要的。
如何规避这种问题的?有很多种方法,如:
- 连接的时候传递校验参数,vaild=1;
- 只允许登录的用户进行连接;
- token 校验。客户端每次发送消息时携带 token,服务端对 token 进行验证。
服务端代码
<?php
// 16-swoole-websocket-3.php
class WebSocketServer
{
private $server = null;
public $key = 'ziruchu';
public function __construct()
{
$this->server = new Swoole\WebSocket\Server('0.0.0.0', 9501);
// 设置心跳检测
$this->server->set([
'heartbeat_idle_time' => 60,
'heartbeat_check_interval' => 10,
]);
$this->server->on('Open', [$this, 'onOpen']);
$this->server->on('Message', [$this, 'onMessage']);
$this->server->on('Close', [$this, 'onClose']);
}
public function onOpen($server, $request)
{
$this->checkAccess($server, $request);
}
public function onMessage($server, $frame)
{
$server->push($frame->fd, '服务器返回数据:' . $frame->data);
}
public function onClose($server, $fd)
{
echo '客户端 ' . $fd . '已断开连接' . PHP_EOL;
}
public function start()
{
$this->server->start();
}
/**
* 验证客户端连接
*
* @param [type] $server [description]
* @param [type] $request [description]
* @return [type] [description]
*/
public function checkAccess($server, $request)
{
// 若某个参数不存在,则关闭当前连接
if (!isset($request->get) || !isset($request->get['id']) || !isset($request->get['token'])) {
$server->close($request->fd);
return false;
}
$id = $request->get['id'];
$token = $request->get['token'];
// 校验 token
if (md5(md5($id) . $this->key) != $token) {
$server->close($request->fd);
return false;
}
}
}
$server = new WebSocketServer();
$server->start();
客户端代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>简版聊天室</title>
</head>
<body>
<div>
<textarea name="content" id="content" cols="30" rows="10"></textarea>
<button onclick="send();">发送</button>
</div>
<div>
<ul id="messageList"></ul>
</div>
<script>
let url = "ws://127.0.0.1:9501?id=10&token=27ebb6344564f1427f0f1f8a6d7c5a9c";
let ws = new WebSocket(url);
ws.onopen = function () {
ws.send('my name is WebSocket');
}
ws.onmessage = function (event) {
let data = event.data;
let ul = document.getElementById('messageList');
let li = document.createElement('li');
li.innerHTML = data;
ul.appendChild(li);
}
ws.onclose = function (event) {
console.log('客户端关闭连接:' . event);
}
function send() {
let obj = document.getElementById('content');
let content = obj.value;
ws.send(content)
obj.value = '';
}
</script>
</body>
</html>
客户端重连机制
客户端重连机制可以理解为一种保活机制,也可以和服务端的心跳检测在一起理解为双向心跳。即如何保证客户端与服务端的连接一直有效不断开。
对于客户端来说,只要触发了 error 或 close 或连接失败时,就主动重新连接服务端。
对于这个案例,服务端代码使用:16-swoole-websocket-2.php。
客户端代码如下;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>重连机制</title>
</head>
<body>
<div>
<textarea name="content" id="content" cols="30" rows="10"></textarea>
<button onclick="send();">发送</button>
</div>
<div>
<ul id="messageList"></ul>
</div>
<script>
// websocket 实例
let ws;
// 避免重复连接
let lockReconnect = false;
let url = "ws://127.0.0.1:9501";
// 创建 websocket 实例
function createWebSocket(url)
{
try {
ws = new WebSocket(url);
initEventHandle();
} catch (e) {
// 连接失败,重新连接
reconnect(url);
}
}
function initEventHandle()
{
ws.onclose = function () {
reconnect(url);
}
ws.onerror = function () {
reconnect(url);
}
ws.onopen = function () {
// 心跳检测重置
heartCheck.reset().start();
}
ws.onmessage = function (event) {
let data = event.data;
let ul = document.getElementById('messageList');
let li = document.createElement('li');
li.innerHTML = data;
ul.appendChild(li);
heartCheck.reset().start();
}
}
function reconnect(url)
{
if (lockReconnect) {
return;
}
lockReconnect = true;
setTimeout(function () {
createWebSocket(url);
lockReconnect = false;
}, 2000);
}
let heartCheck = {
timeout: 60000,
timeoutObj: null,
serverTimeoutObj: null,
reset: function () {
clearTimeout(this.timeoutObj);
clearTimeout(this.serverTimeoutObj);
return this;
},
start: function () {
let self = this;
this.timeoutObj = setTimeout(function () {
// 发送心跳检测
// 服务端收到后会返回一个心跳消息
ws.send('');
self.serverTimeoutObj = setTimeout(function () {
ws.close();
}, self.timeout);
}, this.timeout);
}
};
createWebSocket(url);
function send() {
let obj = document.getElementById('content');
let content = obj.value;
ws.send(content)
obj.value = '';
}
</script>
</body>
</html>
本篇文章学习了 websocket 常见问题,我们下篇文章见。
请登录后再评论