这几天写了个socket的通知消息,记录一下使用心得,网络上有很多资料,但特别乱。在开发过程中也遇到了各种各样的问题,特此记录一下。
仅在laravel8、swooletw/laravel-swoole: 2.8 中进行了测试,其他版本请自行测试
设置
swoole_http.php配置文件
websocket要设置task_enable_coroutine属性true,同时打开websocket的使用。
不开task_enable_coroutine会报错的,注意
swoole_websocket.php配置文件
需要自定义一下handler,parser,
不修改的话前端调用时一定要注意数据包的结构。
var data = {"test": "测试"};
websocket.send(encodeMessage('loginCheck', data));
function encodeMessage(event, data)
{
return JSON.stringify([event,data]);
}
自定义handler,parser的话可以任意定义
// 'handler' => SwooleTW\Http\Websocket\SocketIO\WebsocketHandler::class,
'handler' => \App\services\WebsocketHandler::class,
// 'parser' => SwooleTW\Http\Websocket\SocketIO\SocketIOParser::class,
'parser' => \App\services\WebsocketParser::class,
WebsocketHandler.php
<?php
namespace App\services;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Config;
use Swoole\Websocket\Frame;
use SwooleTW\Http\Server\Facades\Server;
use SwooleTW\Http\Websocket\HandlerContract;
class WebsocketHandler implements HandlerContract
{
// 连接建立时触发
public function onOpen($fd, Request $request)
{
// 在触发 WebSocket 连接建立事件之前,Laravel 应用初始化的生命周期已经结束,你可以在这里获取 Laravel 请求和会话数据
if (! $request->input('sid')) {
$payload = json_encode(
[
'sid' => uniqid(),
]
);
// 调用 push 方法向客户端推送数据,fd 是客户端连接标识字段
App::make(Server::class)->push($fd, $payload);
return true;
}
return false;
}
// 收到消息时触发
public function onMessage(Frame $frame)
{
//根据前端心跳的返回,可写自己的判断
App::make(Server::class)->push($frame->fd, 'pong');
}
// 关闭连接时触发
public function onClose($fd, $reactorId)
{
App::make(Server::class)->disconnect($fd);
}
}
WebsocketParser.php
<?php
namespace App\services;
use SwooleTW\Http\Websocket\Parser;
//这里只是进行了接管,并没有拿它实现代码逻辑
class WebsocketParser extends Parser
{
/**
* Encode output payload for websocket push.
* @param string $event
* @param mixed $data
* @return mixed
*/
public function encode(string $event, $data)
{
$string = ['event' => $event, 'data' => $data];
return json_encode($string);
}
/**
* Input message on websocket connected.
* Define and return event name and payload data here.
* @param \Swoole\Websocket\Frame $frame
* @return array
*/
public function decode($frame)
{
//这里是解析客户端发来的数据,我们约定所有的传输都是json
$json = $frame->data;
$data = json_decode($json, true);
if (!$data || !isset($data['event'])) {
return ['event' => 'error', 'data' => $frame->data];
}
return ['event' => $data['event'], 'data' => $data['data'] ?? ''];
}
}
重点是 websocket的 routes文件
在vendor/swooletw/laravel-swoole/routes文件夹中有一个websocket.php
具体如下
<?php
use Illuminate\Http\Request;
use SwooleTW\Http\Websocket\Facades\Websocket;
/*
|--------------------------------------------------------------------------
| Websocket Routes
|--------------------------------------------------------------------------
|
| Here is where you can register websocket events for your application.
|
*/
Websocket::on('connect', function ($websocket, Request $request) {
echo "connect";
// called while socket on connect
});
Websocket::on('disconnect', function ($websocket) {
echo "disconnect";
// called while socket on disconnect
});
Websocket::on('example', function ($websocket, $data) {
$websocket->emit('message', $data);
});
这个文件定义了一些触发事件,你要在你的laravel的路由器中自己定义一下,进行接管。
可以非常方便像定义 laravel 路由一样,定义各种事件
这里我注释了 disconnect,这样就会直接进入我WebsocketHandler.php中的onClose,否则会先进入 disconnect 中进行处理。
当然你也可以定义其他事件,想怎么写都行。
前端
只是简单设置了心跳,其他的可以自己填写,注意前端调用时数据包的结构
<script>
var url = 'ws://a.test1.com/ws';
var ws;
var lockReconnect = false; //避免ws重复连接
createWebSocket(url);//连接服务器
function createWebSocket(url) {
try {
if ('WebSocket' in window) {
console.log("连接 WebSocket");
ws = new WebSocket(url);
} else if ('MozWebSocket' in window) {
console.log("尝试重新连接 MozWebSocket");
ws = new MozWebSocket(url);
} else {
alert("您的浏览器不支持websocket")
}
ws.onopen = function (event) {
heartCheck.start(); //心跳检测重置
console.log("已经与服务器建立了连接\r\n当前连接状态:" + this.readyState);
};
ws.onmessage = function (event) {
heartCheck.reset().start(); //拿到任何消息都说明当前连接是正常的
if (event.data !== 'pong')
{
data=JSON.parse(event.data);
console.log("接收到服务器发送的数据:\r\n" +event.data);
}
};
ws.onclose = function (event) {
console.log("已经与服务器断开连接\r\n当前连接状态:" + this.readyState);
reconnect(url);
};
ws.onerror = function (event) {
console.log("WebSocket异常!");
};
} catch (e) {
console.log(e);
}
}
function reconnect(url) {
if (lockReconnect) return;
lockReconnect = true;
setTimeout(function () { //没连接上会一直重连,设置延迟避免请求过多
console.log("尝试重新连接");
createWebSocket(url);//连接服务器
lockReconnect = false;
}, 1000);
}
//心跳检测
var heartCheck = {
timeout: 8000, //8s
timeoutObj: null,
serverTimeoutObj: null,
reset: function () {
clearTimeout(this.timeoutObj);
clearTimeout(this.serverTimeoutObj);
return this;
},
start: function () {
var self = this;
this.timeoutObj = setTimeout(function () {
//这里发送一个心跳,后端收到后,返回一个心跳消息,
//onmessage拿到返回的心跳就说明连接正常
ws.send("ping");
console.log("ping!");
self.serverTimeoutObj = setTimeout(function () { //如果超过一定时间还没重置,说明后端主动断开了
console.log("try=close");
ws.close();
}, self.timeout)
}, this.timeout)
}
};
</script>