laravel使用Websocket推送通知

成功只有一种,那就是用自己喜欢的方式度过一生

简述

系统开发中,实时消息推送是一个很常见的需求。整体过程而言也没有那么复杂,如果不考虑实时性和性能这些,更简单,客户端轮询服务器的消息表即可。建立Web实时通信和传统通信不同的是,因为浏览器和http服务器之间不能进行双向通信,所以需要借助Websocket这么一个桥梁来连接两者。用户的应用产生消息之后,首先发送给Websocket服务器,Websocket服务器收到消息,再发送给已经建立连接的客户端。
整个过程可以简化为:

  • 前端页面初始化,连接到Websocket服务器
  • 应用程序产生通知,连接Websocket服务器,发送消息
  • Websocket服务器接收到应用程序发送的消息,转发给浏览器
  • 浏览器接收到通知,进行页面响应

Websocket服务器

这里使用swoole来编写Websocket服务器。swoole是一个高性能的PHP网络通信扩展。很强大。 这里,建立一个Laravel自定义命令,来管理server。

1
$ php artisan make:command SwooleServer

服务器端值得注意的是,需要用到一个全局的数据结构来管理用户,和用户的连接,当用户刷新浏览器之后,需要更新一下用户key绑定的连接符。这样,当消息再次到达时,能够准确的发送出去。

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
<?php

$server = new Server('0.0.0.0', 9501);

$table = new Table(1024);
$table->column('uid', Table::TYPE_INT);
$table->column('fd', Table::TYPE_INT);
$table->create();

$server->table = $table;

$server->on('open', function (Server $server, $request){
echo "connected\n";
if (isset($request->get['uid'])) {
$uid = $request->get['uid'];
$server->table->set($uid, ['uid' => $uid, 'fd' => $request->fd]);
}
});

$server->on('message', function(Server $server, $frame) {
echo "received from {$frame->fd}:{$frame->data}\n";
$msg = json_decode($frame->data, true);
$user = $server->table->get($msg['user_id']);
if ($user) {
$server->push($user['fd'], $frame->data);
}
});

$server->on('close', function($server, $fd){
echo "client {$fd} closed\n";
$server->table->del($this->user);
});

$server->start();

客户端

客户端只需要在页面加载时,连接Websocket服务器,然后,在接收到消息时,更新页面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// client.js
var socket = new WebSocket('ws://localhost:9501/?uid=1');
socket.onopen = function(event) {
var badge = document.getElementById('msg-cnt');
badge.innerHTML = 0;
console.log('Connected: ' + event);
}
socket.onmessage = function(event) {
var badge = document.getElementById('msg-cnt');
var data = JSON.parse(event.data);
badge.innerHTML = data.cnt;

console.log("Received: " + data);
}
socket.onclose = function(event) {
console.log("Closed..");
}

PHP应用程序

PHP应用程序产生消息之后,需要发送给Websocket服务器。这里说个插曲,之前关于这块,看的是网上的例子,使用Redis来连接应用程序和WS服务器通信。但是他们的WS服务器使用的是Node。我在使用Redis和Swoole这个干时,错误了。因为Redis的订阅操作是阻塞的,所以Swoole不能这么干。

PHP发送消息需要用到PHP的Websocket客户端库来连接,发送消息。有些实现很简单,这里我使用的是这个库websocket-php。发送消息代码:

1
2
3
4
<?php
$cli = new WebsocketClient('ws://localhost:9501');
if (!$cli) {echo 'Connect Error!';exit;}
$cli->send(json_encode($msg->toArray(), JSON_UNESCAPED_UNICODE));

http://memosa.cn/web/2016/04/23/ws-notification.html