欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Laravel广播如何运作

程序员文章站 2022-06-15 20:09:51
...

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

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

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

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

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

  • 首先,您需要一台支持Web套接字协议并允许客户端建立Web套接字连接的服务器。
  • 您可以实现自己的服务器,也可以使用诸如Pusher之类的第三方服务。 在本文中,我们更喜欢后者。
  • 客户端启动到Web套接字服务器的Web套接字连接,并在成功连接后接收到唯一标识符。
  • 一旦连接成功,客户端将订阅其希望接收事件的某些频道。
  • 最终,客户端在订阅的频道**册要监听的事件。
  • 现在在服务器端,当发生特定事件时,我们通过为其提供通道名称和事件名称来通知Web套接字服务器。
  • 最后,网络套接字服务器将该事件广播到该特定频道上的注册客户端。

一次不要担心它看起来太多了;不要担心。 当我们阅读本文时,您将掌握它。

接下来,让我们看看默认的广播配置文件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', 'log'),

    /*
    |--------------------------------------------------------------------------
    | 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'),
        ],

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

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

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

    ],

];

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

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

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

设置先决条件

在广播中,有不同类型的频道-公共,私人和在线状态。 当您要公开广播事件时,应该使用该公共频道。 相反,当您要将事件通知限制为某些专用频道时,将使用专用频道。

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

核心认证功能

首先,您需要启用默认的Laravel身份验证系统,以便开箱即用的功能(例如注册,登录等)。 如果您不确定如何执行此操作,则官方文档可提供对此的快速了解。

Pusher SDK-安装和配置

当我们将Pusher第三方服务用作我们的网络套接字服务器时,您需要使用该服务创建一个帐户 ,并确保在注册后拥有必要的API凭据。 如果您在创建它时遇到任何麻烦,请随时在评论部分询问我。

接下来,我们需要安装Pusher PHP SDK,以便我们的Laravel应用程序可以将广播通知发送到Pusher Web套接字服务器。

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

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

现在,让我们更改广播配置文件以将Pusher适配器启用为我们的默认广播驱动程序。

<?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', 'pusher'),

    /*
    |--------------------------------------------------------------------------
    | 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' => 'ap2',
                        'encrypted' => true
            ],
        ],

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

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

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

    ],

];

如您所见,我们已将默认的广播驱动程序更改为Pusher。 我们还首先添加了应该从Pusher帐户获得的群集和加密配置选项。

另外,它正在从环境变量中获取值。 因此,请确保我们确实在.env文件中设置了以下变量。

BROADCAST_DRIVER=pusher

PUSHER_APP_ID={YOUR_APP_ID}
PUSHER_APP_KEY={YOUR_APP_KEY}
PUSHER_APP_SECRET={YOUR_APP_SECRET}

接下来,我必须在几个核心Laravel文件中进行一些更改,以使其与最新的Pusher SDK兼容。 当然,我不建议在核心框架中进行任何更改,而只是强调需要做的事情。

继续并打开vendor/laravel/framework/src/Illuminate/Broadcasting/Broadcasters/PusherBroadcaster.php文件。 只需use Pusher;替换代码段即可use Pusher; use Pusher\Pusher;

接下来,我们打开vendor/laravel/framework/src/Illuminate/Broadcasting/BroadcastManager.php文件,并在以下代码段中进行类似的更改。

return new PusherBroadcaster(
  new \Pusher\Pusher($config['key'], $config['secret'],
  $config['app_id'], Arr::get($config, 'options', []))
);

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

App\Providers\BroadcastServiceProvider::class,

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

Pusher和Laravel Echo库-安装和配置

在广播中,客户端的责任是订阅频道并收听所需的事件。 在后台,它通过打开与Web套接字服务器的新连接来完成此任务。

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

您可以使用NPM软件包管理器安装Laravel Echo。 当然,如果尚未安装node和npm,则需要首先安装它们。 其余部分非常简单,如以下代码片段所示。

$npm install laravel-echo

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

是的,我知道,仅获取一个JavaScript文件是有点矫kill过正。 如果您不想进行此练习,可以从我的GitHub下载echo.js文件。

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

后端文件设置

回想一下我们在谈论建立一个应用程序,该应用程序允许我们应用程序的用户相互发送消息。 另一方面,当用户收到来自其他用户的新消息时,我们将向他们发送广播通知。

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

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

$php artisan make:model Message --migration

我们还需要向我们的消息表中添加一些字段,例如tofrommessage 因此,让我们在运行migration命令之前更改迁移文件。

<?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套接字服务器。

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

让我们使用以下artisan命令创建一个自定义事件类NewMessageNotification

$php artisan make:event NewMessageNotification

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

<?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
use App\Message;

class NewMessageNotification implements ShouldBroadcastNow
{
    use 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套接字服务器建立连接之前对其进行身份验证。 它可以确保在专用频道上广播的事件仅发送给经过身份验证的客户端。 在我们的情况下,这意味着只有登录用户才能订阅我们的频道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}路由。

channel方法的第二个参数应为闭包函数。 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 class="container">
                <div class="navbar-header">

                    <!-- Collapsed Hamburger -->
                    <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#app-navbar-collapse">
                        <span class="sr-only">Toggle Navigation</span>
                        <span class="icon-bar"></span>
                        <span class="icon-bar"></span>
                        <span class="icon-bar"></span>
                    </button>

                    <!-- Branding Image -->
                    <a class="navbar-brand123" 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 class="dropdown">
                                <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">
                                    {{ Auth::user()->name }} <span class="caret"></span>
                                </a>

                                <ul class="dropdown-menu" 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 class="content">
                <div class="m-b-md">
                    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', 'aaa@qq.com');
Route::get('message/send', 'aaa@qq.com');

在controller类的构造方法中,您可以看到我们已经使用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套接字服务器的Web套接字连接。

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

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

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

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

这就是用于从Web套接字服务器接收事件的设置。 接下来,我们将通过引发广播事件的控制器文件中的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事件广播到user.{USER_ID}频道上已配置的Web套接字服务器。

在我们的情况下,该事件将通过user.{USER_ID}频道广播到Pusher网络套接字服务器。 如果接收方用户的ID为1 ,则事件将通过user.1频道广播。

正如我们前面讨论的,我们已经有了一个侦听此通道上的事件的设置,因此它应该能够接收此事件,并且向用户显示警告框!

让我们继续前进,逐步介绍如何测试到目前为止我们构建的用例。

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

实际上,Laravel已经在后台为您完成了很多工作。 当我们启用了Pusher客户端库提供的Pusher.logToConsole设置时,它将所有内容记录在浏览器控制台中以进行调试。 让我们看看访问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套接字服务器的Web套接字连接,并已订阅以侦听专用频道上的事件。 当然,根据您登录时所用用户的ID,您可以使用不同的频道名称。 现在,让我们继续打开此页面,以测试send方法。

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

打开http:// your-laravel-site-domain / message / send页后,您应该能够在http:// your-laravel-site-domain / message的其他标签中看到警报消息/ index

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

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"}

如您所见,它告诉您您刚刚从private-user.2频道上的Pusher Web套接字服务器收到了App\Events\NewMessageNotification事件。

实际上,您也可以在Pusher端看到正在发生的事情。 转到您的Pusher帐户并导航到您的应用程序。 在“ 调试 控制台”下 ,您应该能够看到正在记录的消息。

Laravel广播如何运作

这就使我们到了本文的结尾! 希望这不是一次尝试,因为我已尽我所能简化了事情。

结论

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

是的,我知道,一篇文章中有很多内容需要摘要,因此,如果在实施过程中遇到麻烦,请随时使用下面的评论提要。

翻译自: https://code.tutsplus.com/tutorials/how-laravel-broadcasting-works--cms-30500