#0 广播架构及相关概念
- 本文使用Redis + socket.io 方案
- 架构组件及涉及到的概念
-
laravel-echo-server
:使用socket.io
机制实现的broadcasting
服务端 -
laravel-echo
:laravel-echo
是laravel broadcasting
的客户端。
注意:
laravel-echo
并不是laravel-echo-server
专属的客户端,laravel-echo
有两种连接机制可以选:pusher
和socket.io
。 而laravel-echo-server
是开发出来专门用于socket.io
连接的服务端。如果你使用的是pusher
,那么不需要使用laravel-echo-server
,但是你依然要使用laravel-echo
-
Socket.IO
:websocket
的一种nodejs
实现。laravel-echo
如果要使用socket.io
则需要先安装socket.io-client
。 -
Predis
:redis
客户端的php
实现,如果要使用redis
作为广播机制的实现,则需要先安装predis
-
Laravel Event
:广播事件类 -
Laravel Queue
:广播机制是基于queue
机制来实现的 -
Redis Sub/Pub
:Redis
的订阅机制。laravel-echo-server
本质上只是一个Redis
订阅服务的订阅者。
- 架构图
Laravel
事件的广播机制流程:
-
Laravel
通过broadcasting
机制发布一个Event
对象到Redis
-
Laravel Queue Worker
读取该Event对象
,并使用Redis
的Sub/Pub机制
将该Event对象
发布出去 -
laravel-echo-server
通过Redis
的Sub/Pub机制
收听到该Event
- 由于
laravel-echo
使用socket.io
跟laravel-echo-server
相连接。所以laravel-echo
会通过socket.io
将Event对象
发送给laravel-echo
-
laravel-echo
解析通过socket.i
o接收到的Event对象
- 广播事件种类:
-
public
:谁都可以收听的广播 -
private
:只有指定用户可以收到的广播 -
presence
:不仅可以收听到跟你有关的广播,还可以跟别的用户互动,适合做聊天室
#1 让Laravel将Event发布到Redis
#1 建立广播服务
打开 config/app.php
找到 provides
属性,将 BroadcastServiceProvider
前的注释去掉
//...
'providers' => [
/*
* Laravel Framework Service Providers...
*/
Illuminate\Auth\AuthServiceProvider::class,
Illuminate\Broadcasting\BroadcastServiceProvider::class,
Illuminate\Bus\BusServiceProvider::class,
Illuminate\Cache\CacheServiceProvider::class,
//...
],
//...
复制代码
#2 设置Redis连接
##.env文件
BROADCAST_DRIVER=redis
CACHE_DRIVER=redis
#QUEUE_CONNECTION=sync
QUEUE_CONNECTION=redis
SESSION_DRIVER=file
SESSION_LIFETIME=120
复制代码
#3 安装predis
composer require predis/predis
复制代码
#4 新建Event和Listener
php artisan make:event Backend/Poster/PosterEvent
php artisan make:listener Backend/Poster/PosterNotification
复制代码
<?php
namespace App\Events\Backend\Poster;
use App\Models\Poster\Poster;
use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
class PosterEvent implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $poster;
public $action;
public $broadcastQueue = 'default';
/**
* Create a new event instance.
*
* @param Poster $poster
* @param string $action
*/
public function __construct(Poster $poster, $action = 'created')
{
$this->poster = $poster;
$this->action = $action;
}
/**
* 事件的广播名称
* #如果你使用了 broadcastAs 方法来自定义广播名称,你应当确保在你注册监听器时加上一个 . 的前缀
* #这将指示 Echo 不要在事件之前添加应用程序的命名空间
* @return string
*/
public function broadcastAs() : string
{
return 'backend_poster';
}
/**
* @return array
*/
public function broadcastWith() : array
{
$url = '';
$msg = '';
if (in_array($this->action, ['created', 'updated'])) {
$url = route('b.poster.edit', $this->poster);
$msg = "海报☆{$this->poster->title}☆已" . ($this->action === 'created' ? '新增' : '更新') . '!';
}
if ($this->action === 'deleted') {
$msg = "海报☆{$this->poster->title}☆已删除!";
}
$data = [
'total' => Poster::getActiveTotal(),
'month' => Poster::getMonthActiveTotal(),
'data' => $this->poster,
'url' => $url,
'msg' => $msg,
];
return $data;
}
/**
* Get the channels the event should broadcast on.
*
* @return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new Channel('poster_room');
}
}
复制代码
<?php
namespace App\Listeners\Backend\Poster;
use App\Events\Backend\Poster\PosterEvent;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
class PosterNotification implements ShouldQueue
{
/**
* 任务将被发送到的队列的连接的名称
* @var string
*/
public $connection = 'redis';
/**
* 任务将被发送到的队列的名称
* @var string
*/
public $queue = 'default';
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* @param PosterEvent $event
*/
public function handle(PosterEvent $event)
{
//TODO
}
}
复制代码
#5 设置广播路由
//routes/channels.php
Broadcast::channel('poster_room', function () {
return true;
});
复制代码
2# 运行队列处理器,让Laravel Queue Worker消费Event
1# 安装 Supervisor
sudo apt-get install supervisor
复制代码
2# 配置 Supervisor
cd /etc/supervisor/conf.d/
sudo vim laravel-backend.conf
复制代码
[program:laravel-backend]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/top/artisan horizon
autostart=true
autorestart=true
user=locoroco
redirect_stderr=true
stdout_logfile=/home/locoroco/top-booking.com/worker.log
复制代码
sudo vim laravel-echo.conf
复制代码
[program:laravel-echo]
process_name=%(program_name)s_%(process_num)02d
directory=/var/www/top
command=laravel-echo-server start
autostart=true
autorestart=true
user=locoroco
redirect_stderr=true
stdout_logfile=/home/locoroco/top-booking.com/echo-server.log
复制代码
numprocs 会告诉 Supervisor 运行 8 个 queue:work 进程并且管理它们,当它们关闭时会将其自动重启
3# 启动 Supervisor
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start laravel-backend:*
##restart
sudo supervisorctl restart laravel-backend:*
复制代码
3# 让 laravel-echo-server 订阅Redis Sub
## 安装laravel-echo-server
npm install -g laravel-echo-server
## 初始化laravel-echo-server
laravel-echo-server init
? Do you want to run this server in development mode? No
? Which port would you like to serve from? 6001
? Which database would you like to use to store presence channel members? redis
? Enter the host of your Laravel authentication server. http://localhost
? Will you be serving on http or https? http
? Do you want to generate a client ID/Key for HTTP API? No
? Do you want to setup cross domain access to the API? No
Configuration file saved. Run laravel-echo-server start to run server.
## 启动 laravel-echo-server
laravel-echo-server start
复制代码
4# 让laravel-echo收听到广播
// bootstrap.js
import Echo from "laravel-echo";
window.io = require('socket.io-client');
window.Echo = new Echo({
broadcaster: 'socket.io',
host: window.location.hostname + ':6001'
});
//webpack.mix.js
const { mix } = require('laravel-mix');
mix.sass('resources/backend/sass/app.scss', 'public/css/iframe/backend.css')
.js('resources/backend/js/app.js','public/js/iframe/backend/app.js');
复制代码
package.json
{
"private": true,
"scripts": {
"dev": "npm run development",
"development": "NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
"watch": "npm run development -- --watch",
"watch-poll": "npm run watch -- --watch-poll",
"watch-mobile": "npm run watch-poll -- --env.mobile",
"watch-backend": "npm run watch-poll -- --env.backend",
"watch-web": "npm run watch-poll -- --env.web",
"watch-mobile1": "npm run watch-poll -- --env.mobile1",
"hot": "NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
"prod": "npm run production",
"production": "NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
},
"devDependencies": {
"axios": "^0.18.0",
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"browser-sync": "^2.26.3",
"browser-sync-webpack-plugin": "2.0.1",
"cross-env": "^5.1",
"laravel-mix": "^2.0",
"lodash": "^4.17.5",
"vue": "^2.5.17"
},
"dependencies": {
"css-loader": "^2.0.0",
"sass-loader": "^7.1.0",
"laravel-echo": "^1.5.1",
"socket.io-client": "^2.2.0",
"toastr": "^2.1.4",
"vue-axios": "^2.1.4",
"vue-router": "^3.0.2",
"vuex": "^3.0.1"
}
}
复制代码
编译
npm install
##清空缓存
##npm cache verify
npm run prod
复制代码
视图页面引入
<script src="{{asset('js/iframe/backend/app.js')}}"></script>
<script>
//recieve poster-room message
Echo.channel('poster_room')
.listen('.backend_poster', (e) => {
//TODO
console.log(e);
});
</script>
复制代码