• 日常搜索
  • 百度一下
  • Google
  • 在线工具
  • 搜转载

Laravel广播的工作原理

今天,我们将探索 Laravel Web 框架中广播的概念。它允许您在服务器端发生某些事情时向客户端发送通知。在本文中,我们将使用第三方 Pusher 库向客户端发送通知。

如果您曾经想在 Laravel 的服务器上发生某些事情时将通知从服务器发送到客户端,那么您正在寻找广播功能。

例如,假设您已经实现了一个消息传递应用程序,它允许您系统的用户相互发送消息。现在,当用户 A 向用户 B 发送消息时,您想实时通知用户 B。您可以显示一个弹出窗口或警告框,通知用户 B 新消息!

这是了解 Laravel 中广播概念的完美用例,这也是我们将在本文中实现的内容。

如果您想知道服务器如何向客户端发送通知,它在后台使用套接字来完成它。在深入了解实际实现之前,让我们了解套接字的基本流程。

  • 首先,您需要一个支持 web-sockets 协议并允许客户端建立 web 套接字连接的服务器。

  • 您可以实现自己的服务器或使用 Pusher 等第三方服务。在本文中,我们更喜欢后者。

  • 客户端向 Web 套接字服务器发起 Web 套接字连接,并在连接成功时接收到唯一标识符。

  • 一旦连接成功,客户端就会订阅它希望接收事件的某些频道。

  • 最后,在订阅的频道下,客户端注册它想收听的事件。

  • 现在在服务器端,当特定事件发生时,我们通过向 web-socket 服务器提供通道名称和事件名称来通知它。

  • 最后,web-socket 服务器将该事件广播到该特定通道上的注册客户端。

如果一次看起来太多,请不要担心;当我们阅读本文时,您将掌握它的窍门。

广播配置文件

接下来,我们看一下config/broadcasting.php中的默认广播配置文件。

<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Default Broadcaster
    |--------------------------------------------------------------------------
    |
    | This option controls the default broadcaster that will be used by the
    | framework when an event needs to be broadcast. You may set this to
    | any of the connections defined in the "connections" array below.
    |
    | Supported: "pusher", "redis", "log", "null"
    |
    */

    'default' => env('BROADCAST_DRIVER', 'null'),

    /*
    |--------------------------------------------------------------------------
    | Broadcast Connections
    |--------------------------------------------------------------------------
    |
    | Here you may define all of the broadcast connections that will be used
    | to broadcast events to other systems or over websockets. Samples of
    | each available type of connection are provided inside this array.
    |
    */

    'connections' => [

        'pusher' => [
            'driver' => 'pusher',
            'key' => env('PUSHER_APP_KEY'),
            'secret' => env('PUSHER_APP_SECRET'),
            'app_id' => env('PUSHER_APP_ID'),
            'options' => [
                'cluster' => env('PUSHER_APP_CLUSTER'),
                'useTLS' => true,
            ],
        ],

        'redis' => [
            'driver' => 'redis',
            'connection' => 'default',
        ],

        'log' => [
            'driver' => 'log',
        ],

        'null' => [
            'driver' => 'null',
        ],

    ],

];

默认情况下,Laravel 在核心本身支持多个广播适配器。

在本文中,我们将使用pusher广播适配器。出于调试目的,您还可以使用log适配器。当然,如果你使用log适配器,客户端不会收到任何事件通知,它只会被记录到laravel.log文件中。

从下一节开始,我们将立即深入研究上述用例的实际实现。

设置先决条件

在广播中,有不同类型的频道——公共、私人和在场。当您想公开广播您的活动时,它是您应该使用的公共频道。相反,当您想将 rict 事件通知发送到某些私人频道时,使用私人频道。

在我们的用例中,我们希望在用户收到新消息时通知他们。为了有资格接收广播通知,用户必须登录。因此,在我们的例子中,我们需要使用私有频道。

核心认证功能

首先,您需要启用默认的 Laravel 身份验证系统,以便注册、登录等功能开箱即用。如果您不确定如何做到这一点,官方文档可以快速了解这一点。

Pusher SDK:安装和配置

由于我们将使用Pusher第三方服务作为我们的 web-socket 服务器,因此您需要使用它创建一个帐户,并确保您在注册拥有必要的 api 凭据。

接下来,我们需要安装 Pusher PHP SDK,以便我们的 Laravel 应用程序可以向 Pusher web-socket 服务器发送广播通知。

在您的 Laravel 应用程序根目录中,运行以下命令将其安装为 composer 包。

$composer require pusher/pusher-php-server "~3.0"

现在,让我们更改.env文件以启用pusher适配器作为我们的默认广播驱动程序。

...
...
BROADCAST_DRIVER=pusher

PUSHER_APP_ID={YOUR_APP_ID}
PUSHER_APP_KEY={YOUR_APP_KEY}
PUSHER_APP_SECRET={YOUR_APP_SECRET}
PUSHER_APP_CLUSTER={YOUR_APP_CLUSTER}
...
...

如您所见,我们已将默认广播驱动程序更改为pusher您还需要首先配置您应该从 Pusher 帐户获得的其他选项。

最后,让我们通过删除以下行中的注释来启用config/app.php 中的广播服务。

App\Providers\BroadcastServiceProvider::class,

到目前为止,我们已经安装了特定于服务器的库。在下一节中,我们将介绍还需要安装的客户端库。

Pusher 和 Laravel Echo 库——安装和配置

在广播中,客户端的职责是订阅频道并监听想要的事件。在后台,它通过打开到 web-socket 服务器的新连接来完成它。

幸运的是,我们不需要实现任何复杂的javascript来实现它,因为 Laravel 已经提供了一个有用的客户端库 Laravel Echo,它可以帮助我们在客户端处理套接字。此外,它还支持我们将在本文中使用的 Pusher 服务。

你可以使用 NPM 包管理器安装Laravel Echo库。当然,如果您还没有node和 npm,则需要安装它们。其余的非常简单,如下面的代码片段所示。

$npm install laravel-echo

我们感兴趣的是你应该复制到public/echo.js的node_modules/laravel-echo/dist/echo.js文件

至此,我们就完成了客户端库的设置。

后端文件设置

回想一下,我们谈论的是设置一个应用程序,它允许我们应用程序的用户相互发送消息。另一方面,当登录的用户收到来自其他用户的新消息时,我们会向他们发送广播通知。

在本节中,我们将创建实现我们正在寻找的用例所需的文件。

创建模型类

首先,让我们创建一个Message模型来保存用户相互发送的消息。

php artisan make:model Message --migration

我们还需要在表中添加一些字段,例如tofrom因此,让我们在运行 migrate 命令之前更改迁移文件database/migrations/XXXX_XX_XX_XXXXXX_create_messages_table.php 。messagemessages

<?php
 
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
 
class CreateMessagesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('messages', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('from', FALSE, TRUE);
            $table->integer('to', FALSE, TRUE);
            $table->text('message');
            $table->timestamps();
        });
    }
 
    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('messages');
    }
}

现在,让我们运行migrate命令,它会messages数据库中创建表。

$php artisan migrate

创建事件类

每当您想在 Laravel 中引发自定义事件时,都应该为该事件创建一个类。根据事件的类型,Laravel做出相应的反应并采取必要的行动。

如果事件是普通事件,Laravel 会调用相关的监听类。另一方面,如果事件是广播类型,Laravel 会将该事件发送到在config/broadcasting.php文件中配置的 web-socket 服务器。

当我们在示例中使用 Pusher 服务时,Laravel 会将事件发送到 Pusher 服务器。

让我们使用以下 artisan 命令来创建自定义事件类:NewMessageNotification.

$php artisan make:event NewMessageNotification

这应该创建app/Events/NewMessageNotification.php文件。让我们用以下内容替换该文件的内容。

<?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
use App\Message;

class NewMessageNotification implements ShouldBroadcastNow
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $message;
 
    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct(Message $message)
    {
        $this->message = $message;
    }
 
    /**
     * Get the channels the event should broadcast on.
     *
     * @return Channel|array
     */
    public function broadcastOn()
    {
        return new PrivateChannel('user.'.$this->message->to);
    }
}

需要注意的重要一点是NewMessageNotification该类实现了ShouldBroadcastNow接口。因此,当我们引发一个事件时,Laravel 知道该事件应该被广播。

其实你也可以实现这个 ShouldBroadcast接口,Laravel 会在事件队列中添加一个事件。当它有机会这样做时,它将由事件队列工作人员处理。在我们的例子中,我们想立即广播它,这就是我们使用该ShouldBroadcastNow接口的原因。

在我们的例子中,我们想要显示用户收到的消息,因此我们Message在构造函数参数中传递了模型。这样,数据将与事件一起传递。

接下来是broadcastOn方法,它定义了将在其上广播事件的频道的名称。在我们的例子中,我们使用了私有频道,因为我们希望将事件广播限制为登录用户。

$this->message->to变量是指将向其广播事件的用户的 ID。因此,它有效地使频道名称像user.{USER_ID}.

创建广播路由

在私有通道的情况下,客户端必须在与 web-socket 服务器建立连接之前验证自己。它确保在私有频道上广播的事件仅发送给经过身份验证的客户端。在我们的例子中,这意味着只有登录用户才能订阅我们的频道user.{USER_ID}

如果你使用 Laravel Echo 客户端库来订阅频道,那么你很幸运!它会自动处理身份验证部分,您只需要定义通道路由。

让我们继续在routes/channels.php文件中为我们的私人频道添加一个路由。

<?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('user.{toUserId}', function ($user, $toUserId) {
    return $user->id == $toUserId;
});

如您所见,我们已经user.{toUserId}为我们的私人频道定义了路由。

通道方法的第二个参数应该是一个闭包函数。Laravel 自动将当前登录的用户作为闭包函数的第一个参数传递,第二个参数通常是从通道名称中获取的。

当客户端尝试订阅私有频道user.{USER_ID}时,Laravel Echo 库使用XMLHttpRequest对象(通常称为 XHR)在后台进行必要的身份验证。

我们现在已经完成了设置,让我们继续测试它。

前端文件设置

在本节中,我们将创建测试用例所需的文件。

创建控制器

让我们继续在app/Http/Controllers/MessageController.php中创建一个控制器文件,其中包含以下内容。

<?php
namespace App\Http\Controllers;
 
use App\Http\Controllers\Controller;
use App\Message;
use App\Events\NewMessageNotification;
use Illuminate\Support\Facades\Auth;
 
class MessageController extends Controller
{
    public function __construct() {
        $this->middleware('auth');
    }
 
    public function index()
    {
        $user_id = Auth::user()->id;
        $data = array('user_id' => $user_id);
 
        return view('broadcast', $data);
    }
 
    public function send()
    {
        // ...
         
        // message is being sent
        $message = new Message;
        $message->setAttribute('from', 1);
        $message->setAttribute('to', 2);
        $message->setAttribute('message', 'Demo message from user 1 to user 2');
        $message->save();
         
        // want to broadcast NewMessageNotification event
        event(new NewMessageNotification($message));
         
        // ...
    }
}

创建视图

index方法中,我们使用broadcast视图,所以让我们也创建resources/views/broadcast.blade.php视图文件。

<!DOCTYPE html> <html lang="{{ app()->getLocale() }}"> <head>     <meta charset="utf-8">     
<meta http-equiv="X-UA-Compatible" content="IE=edge">     
<meta name="viewport" content="width=device-width, initial-scale=1">       <!-- CSRF Token -->     
<meta name="csrf-token" content="{{ csrf_token() }}">       <title>Test</title>       <!-- Styles -->     
<link href="{{ asset('css/app.css') }}" rel="stylesheet"> </head> <body>     <div id="app">         
<nav class="navbar navbar-default navbar-static-top">             <div>                 <div>                      
 <!-- Collapsed Hamburger -->                     
 <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#app-navbar-collapse">       
 <span>Toggle Navigation</span>  <span></span>  <span></span>  <span></span>  </button> 
<!-- Branding Image -->                     <a href="{{ url('/') }}">  
Test                     </a>                 </div>  
<div class="collapse navbar-collapse" id="app-navbar-collapse">                     <!-- Left Side Of Navbar -->    
 <ul class="nav navbar-nav">                         &nbsp;                     </ul>                 
 <!-- Right Side Of Navbar -->                     <ul class="nav navbar-nav navbar-right">  
<!-- Authentication Links -->                         @if (Auth::guest()) 
<li><a href="{{ route('login') }}">Login</a></li>                             
<li><a href="{{ route('register') }}">Register</a></li>    @else                             
<li>   <a href="#" data-toggle="dropdown" role="button" aria-expanded="false"> 
{{ Auth::user()->name }} <span></span></a> 
<ul role="menu">                      
<li>                               
 <a href="{{ route('logout') }}"    onclick="event.preventDefault();           
 document.getElementById('logout-form').submit();">                           
Logout                                         </a>        
<form id="logout-form" action="{{ route('logout') }}" method="post" style="display: none;">                              
 {{ csrf_field() }}   </form>   </li>                               
</ul> </li>                        
 @endif                     
 </ul>      
</div></div></nav>           
<div>                 
<div>New notification will be alerted realtime! </div> </div> </div>       
 <!-- receive notifications -->     <script src="{{ asset('js/echo.js') }}"></script>       
 <script src="https://js.pusher.com/4.1/pusher.min.js"></script>   
<script>    
Pusher.logToConsole = true;  
window.Echo = new Echo({             
broadcaster: 'pusher',             
key: 'c91c1b7e8c6ece46053b',             
cluster: 'ap2',             
encrypted: true,             
logToConsole: true           
});                     
Echo.private('user.{{ $user_id }}')           
.listen('NewMessageNotification', (e) => {               
alert(e.message.message);           });         
</script>     
<!-- receive notifications --> </body> 
</html>

添加路线

最后,我们还需要在routes/web.php文件中添加路由。

Route::get('message/index', 'MessageController@index'); Route::get('message/send', 'MessageController@send');

它是如何工作的

在控制器类的构造方法中,你可以看到我们使用了auth中间件来确保控制器方法只有登录用户才能访问。

接下来是index渲染broadcast视图的方法。让我们拉入视图文件中最重要的代码。

<!-- receive notifications -->
<script src="{{ asset('js/echo.js') }}"></script>
 
<script src="https://js.pusher.com/4.1/pusher.min.js"></script>
     
<script>
    Pusher.logToConsole = true;
 
    window.Echo = new Echo({
        broadcaster: 'pusher',
        key: 'c91c1b7e8c6ece46053b',
        cluster: 'ap2',
        encrypted: true,
        logToConsole: true
    });
 
    Echo.private('user.{{ $user_id }}')
    .listen('NewMessageNotification', (e) => {
        alert(e.message.message);
    });
</script>
<!-- receive notifications -->

首先,我们加载必要的客户端库,Laravel Echo 和 Pusher,允许我们打开到 Pusher web-socket 服务器的 web-socket 连接。

pusher接下来,我们通过提供广播适配器和其他必要的推送器相关信息来创建 Echo 实例。

进一步,我们使用privateEcho 的方法订阅私人频道user.{USER_ID}正如我们之前讨论的,客户端必须在订阅私有频道之前验证自己。因此,Echo对象通过在后台发送带有必要参数的 XHR 来执行必要的身份验证。最后,Laravel 尝试找到user.{USER_ID}路由,它应该匹配我们在routes/channels.php文件中定义的路由。

如果一切顺利,您应该与 Pusher web-socket 服务器打开一个 web-socket 连接,并且它会在user.{USER_ID}频道上列出事件!从现在开始,我们将能够接收此频道上的所有传入事件。

在我们的例子中,我们想要监听NewMessageNotification事件,因此我们使用listen了对象的方法Echo来实现它。为简单起见,我们只会提醒我们从 Pusher 服务器收到的消息。

这就是从 web-sockets 服务器接收事件的设置。接下来,我们将浏览send控制器文件中引发广播事件的方法。

让我们快速拉入 send方法的代码。

public function send()
{
    // ...
     
    // message is being sent
    $message = new Message;
    $message->setAttribute('from', 1);
    $message->setAttribute('to', 2);
    $message->setAttribute('message', 'Demo message from user 1 to user 2');
    $message->save();
     
    // want to broadcast NewMessageNotification event
    event(new NewMessageNotification($message));
     
    // ...
}

在我们的例子中,我们将在登录用户收到新消息时通知他们。所以我们试图在send方法中模仿这种行为。

接下来,我们使用了event辅助函数来引发NewMessageNotification事件。由于NewMessageNotification事件是ShouldBroadcastNow类型的,Laravel 从config/broadcasting.php文件加载默认的广播配置。最后,它将NewMessageNotification事件广播到通道上配置的 web-socket 服务器user.{USER_ID}

在我们的例子中,事件将被广播到user.{USER_ID}频道上的 Pusher web-socket 服务器。如果接收用户的 ID 是1,则该事件将通过user.1频道广播。

正如我们之前讨论的,我们已经有一个监听这个通道上的事件的设置,所以它应该能够接收这个事件,并且向用户显示警报框!

如何测试我们的设置

让我们继续了解您应该如何测试我们迄今为止构建的用例。

在浏览器中打开 URL https://your-laravel-site-domain/message/index 。如果您尚未登录,您将被重定向到登录屏幕。登录后,您应该会看到我们之前定义的广播视图——还没有什么花哨的。

事实上,Laravel 已经在后台为你做了很多工作。由于我们启用了Pusher.logToConsolePusher 客户端库提供的设置,它会在浏览器控制台中记录所有内容以进行调试。让我们看看当你访问http://your-laravel-site-domain/message/index时控制台记录了什么页面时控制台记录了哪些内容。

Pusher : State changed : initialized -> connecting
Pusher : Connecting : {"transport":"ws","url":"wss://ws-ap2.pusher.com:443/app/c91c1b7e8c6ece46053b?protocol=7&client=js&version=4.1.0&flash=false"}
Pusher : Connecting : {"transport":"xhr_streaming","url":"https://sockjs-ap2.pusher.com:443/pusher/app/c91c1b7e8c6ece46053b?protocol=7&client=js&version=4.1.0"}
Pusher : State changed : connecting -> connected with new socket ID 1386.68660
Pusher : Event sent : {"event":"pusher:subscribe","data":{"auth":"c91c1b7e8c6ece46053b:cd8b924580e2cbbd2977fd4ef0d41f1846eb358e9b7c327d89ff6bdc2de9082d","channel":"private-user.2"}}
Pusher : Event recd : {"event":"pusher_internal:subscription_succeeded","data":{},"channel":"private-user.2"}
Pusher : No callbacks on private-user.2 for pusher:subscription_succeeded

它已经打开了与 Pusher web-socket 服务器的 web-socket 连接,并订阅了自己以监听私有通道上的事件。当然,根据您登录的用户 ID,您可以使用不同的频道名称。现在,让我们在开始测试时保持此页面打开send方法时保持此页面打开。

接下来,让我们打开http://your-laravel-site-domain/message/send在另一个选项卡或其他浏览器中如果您要使用其他浏览器,则需要登录才能访问该页面。

一旦您打开http://your-laravel-site-domain/message/send页面,您应该能够在http://your-laravel-site-domain/message的另一个选项卡中看到一条警报消息/索引

让我们导航到控制台,看看刚刚发生了什么。

Pusher : Event recd : {"event":"App\\Events\\NewMessageNotification","data":
{"message":
{"id":57,"from":1,"to":2,"message":"Demo message from user 1 to user 2","created_at":
"2018-01-13 07:10:10","updated_at":"2018-01-13 07:10:10"}},"channel":"private-user.2"}

如您所见,它告诉您刚刚收到App\Events\NewMessageNotification来自 Pusher web-socket 服务器的事件private-user.2从频道

事实上,您也可以看到 Pusher 端正在发生的事情。转到您的 Pusher 帐户并导航到您的应用程序。Debug Console下,您应该能够看到正在记录的消息。

Laravel广播的工作原理  第1张

这使我们到了本文的结尾!希望这不会太多,因为我已经尽我所知尽量简化事情。

结论

今天,我们讨论了 Laravel 中讨论最少的特性之一——广播。它允许您使用 Web 套接字发送实时通知。在本文的整个过程中,我们构建了一个展示上述概念的真实示例。



文章目录
  • 广播配置文件
  • 设置先决条件
    • 核心认证功能
    • Pusher SDK:安装和配置
    • Pusher 和 Laravel Echo 库——安装和配置
  • 后端文件设置
    • 创建模型类
    • 创建事件类
    • 创建广播路由
  • 前端文件设置
    • 创建控制器
    • 创建视图
    • 添加路线
  • 它是如何工作的
  • 如何测试我们的设置
  • 结论