Laravel Broadcasting广播机制(Redis + socket.io)-学习实例

借助Laravel Broadcasting你可以使用上时下很热的Websocket技术。

注意:请务必使用较新版本的 Laravel。Laravel在最近几个版本进行过比较大的重构,比如路由从 app\Http\routes.php 拆分为到 routes 目录下的多个文件,包括广播在内的各个附加组件也都进行了重构并正式写入文档。所以网上有些教程(特别是入门教程)可能是根据旧版本来写的,容易让你迷惑。当安装完Laravel后执行以下命令查看Laravel版本

php artisan --version

如果低于 5.4 请重新建立新版本的Laravel,具体方法在下面会详细讲解。


实例背景:

假设场景:

假设我们要使用laravel作为服务端做一个新闻推送系统。用户打开页面后不需要刷新页面即可不断的获取到最新的新闻。

环境参数

  • Homestead 7.4.2
  • Laravel 5.6.23 (广播机制在 5.4 以后进行了一次重构,并正式加入文档,所以请务必使用 5.4 及其以上版本)
  • PHP 7.1
  • Redis 4.0.9 (Homestead 自带redis)

建立基本项目框架

使用laravel new 来建立 quickstart 项目

laravel new quickstart

我安装完后的laravel版本为5.6.23

$ php artisan --version
Laravel Framework version 5.6.23

编辑 .env 文件,修改 APP_URL=http://quickstart

APP_URL=http://quickstart

我们还需要做3件事情才能让quickstart成功的被部署:

1. 在 hosts 文件中增加 quickstart 到 192.168.10.10 (vagrant地址)的域名解析

192.168.10.10   quickstart

2. 编辑Homestead.yaml增加 quickstart 这个网站

    - map: quickstart
      to: /home/vagrant/Code/quickstart/public
      php: "7.1"

3 reload  vagrant 虚拟机

cd ~/Homestead
vagrant reload --provision

最后,打开浏览器访问 http://quickstart 你将会看到如下页面


说明项目搭建成功。接下来我们在这个quickstart的基础上为项目添加广播机制。

广播架构

目前有两种广播机制可选:

  • pusher:laravel自带方案,但是有使用限制,需要收费
  • Redis + socket.io:无限制
我们使用业界较流行的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订阅服务的订阅者。

这几样东西配合起来的架构图如下


根据这幅图我们可以知道事件的广播机制流程:

  1. Laravel 通过 broadcasting 机制发布一个Event对象到Redis
  2. Laravel Queue Worker 读取该Event对象,并使用Redis的Sub/Pub机制将该 Event对象发布出去
  3. laravel-echo-server  通过 Redis 的 Sub/Pub机制收听到该 Event
  4. 由于 laravel-echo 使用 socket.io 跟 laravel-echo-server相连接。所以 laravel-echo 会通过socket.io将Event对象发送给laravel-echo
  5. laravel-echo解析通过 socket.io接收到的 Event对象

广播事件种类:

  • public:谁都可以收听的广播
  • private:只有指定用户可以收到的广播
  • presence:不仅可以收听到跟你有关的广播,还可以跟别的用户互动,适合做聊天室

我们假设的场景是新闻推送系统,所以使用最简单的public广播就可以实现。接下来我们详细的来讲解如何将这些组件配置好,并连接起来。

我们按照广播机制的流程来一步一步的设置广播机制。

一步步设置广播机制

让Laravel将Event发布到Redis

建立广播服务

注册 BroadcastServiceProvider,打开 config/app.php 找到 'provides' 属性,将 BroadcastServiceProvider 前的注释去掉

        App\Providers\AppServiceProvider::class,
        App\Providers\AuthServiceProvider::class,
        App\Providers\BroadcastServiceProvider::class,
        App\Providers\EventServiceProvider::class,
        App\Providers\RouteServiceProvider::class,

设置广播路由

打开广播路由配置文件 routes/channels.php 。该文件用来确定广播事件的流向,在presence广播中还肩负着补充事件内容的工作。我们在 channels.php 中增加一个新的广播通道 news

<?php

/*
|--------------------------------------------------------------------------
| Broadcast Channels
|--------------------------------------------------------------------------
|
| Here you may register all of the event broadcasting channels that your
| application supports. The given channel authorization callbacks are
| used to check if an authenticated user can listen to the channel.
|
*/

Broadcast::channel('App.User.{id}', function ($user, $id) {
    return (int) $user->id === (int) $id;
});

Broadcast::channel('news', function ($user, $id) {
    return true;
});

该channel永远返回true意味着无论收听者是谁,他都会收听到最新的广播。

设置Redis连接

由于广播机制是基于queue机制实现的。所以queue的存储设置会直接决定广播事件的存储位置。编辑 .env 文件,修改 QUEUE_DRIVER redis

QUEUE_DRIVER=redis

建立Event

使用以下命令建立 News Event对象

php artisan make:event News

你会发现 app 目录下多出来一个 Events目录,在该目录下产生了广播事件类 News.php。我们要对自动生成的News类进行以下修改:

  • 增加对 ShouldBroadcast 的实现
  • 修改broadcastOn 方法,使用公共广播通道 news
  • 修改构造函数
class News implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $message;
    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct($news_message)
    {
        $this->message = $news_message;
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return \Illuminate\Broadcasting\Channel|array
     */
    public function broadcastOn()
    {
        return new Channel('news');
    }
}

测试广播

接下来,我们要通过新增一个artisan命令来测试是否可以将广播发送到 Redis中。

为项目增加 predis依赖,在项目根目录下执行:

composer require predis/predis

执行完后会发现 composer.json 中增加了 predis 的依赖

    "require": {
        "php": "^7.1.3",
        "fideloper/proxy": "^4.0",
        "laravel/framework": "5.6.*",
        "laravel/tinker": "^1.0",
        "predis/predis": "^1.1"
    },

编辑 routes/console.php ,增加 bignews 命令:

Artisan::command('bignews', function () {
    broadcast(new News(date('Y-m-d h:i:s A').": BIG NEWS!"));
    $this->comment("news sent");
})->describe('Send news');

执行 bignews 命令:

$ php artisan bignews
news sent

通过 redis-cli 查看当前redis中的数据,发现多出来一个queue对象

127.0.0.1:6379> keys *
1) "queues:default"

Laravel的广播机制成功的连接上了 Redis!

接下来我们让Laravel Queue Worker来消费这些Event

让Laravel Queue Worker消费Event

新开一个终端窗口,并在quickstart根目录下启动 Laravel Queue Worker

$ php artisan queue:work

回到之前的终端窗口,再广播一个news Event:

$ php artisan bignews

你可以在queue worker的执行界面看到该Event已经被检测到,并通过Redis Sub/Pub机制传播出去了

vagrant@homestead:~/Code/quickstart$ php artisan queue:work
[2018-05-25 23:24:03][OvuviSKDpM9R5LrIilBEgSLcDpk3hDXc] Processing: App\Events\News
[2018-05-25 23:24:03][OvuviSKDpM9R5LrIilBEgSLcDpk3hDXc] Processed:  App\Events\News

还记得之前通过Laravel广播事件之后redis中会产生一个 queue:default对象么?这次如果你快速的通过 keys * 命令查询redis中的对象,你会发现 queue:default 每次被产生出来就会迅速的被 queue worker消费掉

127.0.0.1:6379> keys *
1) "queues:default"
127.0.0.1:6379> keys *
(empty list or set)

Laravel Queue Worker连接成功!

接下来我们来让 laravel-echo-server 订阅Redis Sub

让 laravel-echo-server 订阅Redis Sub

安装

如果使用pusher那么直接使只用laravel就可以了,如果使用 Redis + socket.io 则需要使用开源项目 laravel-echo-server 。所以我们现在要使用 laravel-echo-server。

再新开一个终端窗口。由于我们之前已经开了两个终端窗口了,所以这是第三个终端窗口。

先切换到root用户,然后安装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.json 配置文件,默认配置为

{
	"authHost": "http://localhost",
	"authEndpoint": "/broadcasting/auth",
	"clients": [],
	"database": "redis",
	"databaseConfig": {
		"redis": {},
		"sqlite": {
			"databasePath": "/database/laravel-echo-server.sqlite"
		}
	},
	"devMode": false,
	"host": null,
	"port": "6001",
	"protocol": "http",
	"socketio": {},
	"sslCertPath": "",
	"sslKeyPath": "",
	"sslCertChainPath": "",
	"sslPassphrase": "",
	"apiOriginAllow": {
		"allowCors": false,
		"allowOrigin": "",
		"allowMethods": "",
		"allowHeaders": ""
	}
}

我们来将 devMode 修改为 true

启动

执行以下命令启动 laravel-echo-server

$ laravel-echo-server start

成功启动后会输出以下日志

vagrant@homestead:~/Code/quickstart$ laravel-echo-server start

L A R A V E L  E C H O  S E R V E R

version 1.3.6

⚠ Starting server in DEV mode...

✔  Running at localhost on port 6001
✔  Channels are ready.
✔  Listening for http events...
✔  Listening for redis events...

Server ready!

我们再回到第一个终端窗口,再次广播一个news Event。你会发现 laravel-echo-server 会输出以下日志

Channel: news
Event: App\Events\News
CHANNEL news

laravel-echo-server连接成功!

因为laravel-echo-server使用的是 6001 端口,所以记得去 Homestead.yaml里面添加6001 端口的映射

- send: 6001
  to: 6001

添加完后记得reload vagrant!

接下来,我们要做一个测试页面,让前端js通过socket.io连接上laravel-echo-server。

让laravel-echo收听到广播

安装

由于前端使用的是 laravel-echo来收听广播,我们选择的底层实现方式是socket.io。所以首先我们要在package.json中添加 laravel-echo 和 socket.io的依赖

$ npm i --save socket.io-client
$ npm i --save laravel-echo

成功安装后输出以下日志

vagrant@homestead:~/Code/quickstart$ npm i --save socket.io-client
npm notice created a lockfile as package-lock.json. You should commit this file.
+ [email protected]
added 30 packages from 23 contributors in 2.401s
[+] no known vulnerabilities found [50 packages audited]

vagrant@homestead:~/Code/quickstart$ npm i --save laravel-echo
+ [email protected]
added 1 package from 1 contributor in 1.083s
[+] no known vulnerabilities found [51 packages audited]



   ╭────────────────────────────────────────────────────────────────╮
   │                                                                │
   │       New minor version of npm available! 6.0.0 → 6.1.0        │
   │   Changelog: https://github.com/npm/npm/releases/tag/v6.1.0:   │
   │               Run npm install -g npm to update!                │
   │                                                                │
   ╰────────────────────────────────────────────────────────────────╯

测试页面

js调用

打开 /resources/assets/js/bootstrap.js 你会发现在这个文件的结尾已经预先写上了 laravel-echo 的使用例子

/**
 * Echo exposes an expressive API for subscribing to channels and listening
 * for events that are broadcast by Laravel. Echo and event broadcasting
 * allows your team to easily build robust real-time web applications.
 */

// import Echo from 'laravel-echo'

// window.Pusher = require('pusher-js');

// window.Echo = new Echo({
//     broadcaster: 'pusher',
//     key: process.env.MIX_PUSHER_APP_KEY,
//     cluster: process.env.MIX_PUSHER_APP_CLUSTER,
//     encrypted: true
// });

我们只需要将注释去掉,然后修改为使用 socket.io

import Echo from 'laravel-echo'
window.io = require('socket.io-client');

window.Echo = new Echo({
    broadcaster: 'socket.io',
    host: window.location.hostname + ':6001'
});

这样页面在初始化的时候就会连接上laravel-echo-server。

bootstrap.js 会被 resources/assets/js/app.js 调用。app.js又通过以下引用在页面被调用:

<script src="{{ mix('js/app.js') }}"></script>

创建页面

现在到了创建页面的时候啦!在 resources/views/ 下建立页面 newsroom.blade.php 内容为

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <title>News Room</title>
    <link href="{{ mix('css/app.css') }}" rel="stylesheet">
</head>
<body>
<div class="content">
    News Room
</div>
<script src="{{ mix('js/app.js') }}"></script>
<script>
    Echo.channel('news')
        .listen('News', (e) => {
        console.log(e.message);
    });
</script>
</body>
</html>

js代码的意思是收听news通道内的News事件对象,将接收到的事件在控制台打印出来。

添加页面路由

在 routes/web.php中增加newsroom的路由信息:

<?php

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', function () {
    return view('welcome');
});

Route::view('newsroom', 'newsroom');

基本构建

在打开浏览器访问页面之前,我们先进行一些基本的环境构建。

停止 laravel-echo-server 然后执行:

$ npm install

打包js和css

$ npm run dev

打包成功后会输出以下日志

 DONE  Compiled successfully in 8489ms                                                                                                                                    00:28:16

       Asset    Size  Chunks                    Chunk Names
  /js/app.js  1.6 MB       0  [emitted]  [big]  /js/app
/css/app.css  196 kB    0, 0  [emitted]         /js/app, /js/app

重新启动laravel-echo-server

基本构建完毕要记得重新启动laravel-echo-server哦!这步太重要了,以至于我专门用了一个节来提醒你。

检验成果

终于到最后的一步了!

打开浏览器访问 http://quickstart/newsroom 你会看到以下页面


打开开发人员工具


接下来是见证奇迹的时刻啦!我们回到第一个终端窗口,发送一个bignews广播:

vagrant@homestead:~/Code/quickstart$ php artisan bignews
news sent

我们回到测试页面newsroom的控制台,可以看到收到了一条广播:


大功告成!

如果你没有收到这条广播,没有关系。之前的步骤中我都有提供了如何通过输出日志查看事件的流动结果,按照之前的步骤逐步排查究竟是哪步出了问题就行。






猜你喜欢

转载自blog.csdn.net/nsrainbow/article/details/80428769
今日推荐