Home > PHP Framework > Laravel > body text

How the built-in Broadcast function of the Laravel framework achieves real-time communication with the client

不言
Release: 2018-07-31 10:36:27
Original
4252 people have browsed it

Laravel The framework integrates the functions of many development packages. Although it has many things, it is indeed a good framework. The next article will tell you how to use the built-in Broadcast function to achieve real-time communication with the client based on Laravel 5.6 version.

1. Preparation

Broadcast system

User authentication

Event system

Queue system

Front-end Guide

tlaverdure/laravel-echo-server

Yes, this is the knowledge reserve you need.

Because PHP itself does not support WebSocket, we need an indirection layer that can send "server" data to the "client". In other words, realizing real-time communication can be roughly divided into two steps:

「Laravel」-> 「Indirect layer」

「Indirect layer」-> (via WebSocket)-> ;「Client」

As for the implementation of the indirect layer, we will talk about it later.

2. Configuration

According to the above broadcast system documentation, we first need to do the following configuration work.

(1) First, modify the config/broadcasting.php or .env file. Make sure the Broadcast Default Driver is log to turn on this feature and make it easier for us to debug.

(2) To use Broadcast, you must understand Laravel's event system, which are interdependent. Next we create an event that can be "broadcast".

Modify app/Providers/EventServiceProvider.php and add in the member variable $listen:

'App\Events\OrderShipped' => [
    'App\Listeners\SendShipmentNotification',
],
Copy after login

Here we name this event OrderShipped (the order has been settled); execute php artisan event:generate Generate event classes and their listeners.

If you need to modify event execution (i.e., broadcast to the client) to asynchronous, please refer to the queue system documentation above.

(3) In order to allow the event to be "broadcast", we need to let the event class inherit the ShouldBroadcast interface.

Open app/Events/OrderShipped.php and modify the class definition to:

class OrderShipped implements ShouldBroadcast
Copy after login

(4) The ShouldBroadcast interface requires the implementation of the broadcastOn method, which is used to inform the framework: to which "this event should be sent" Channel".

Laravel's broadcast system allows multiple channels to exist. You can use usernames to distinguish different channels, so that different users can get different messages from different channels, and can also communicate with different clients. Communicate individually.

Of course, you can also name the channel arbitrarily, but it is best to have certain rules.

Because we just used the event code generated by the Artisan command, you can already see the definition of the broadcastOn method at the bottom of the file. We make a slight modification:

public function broadcastOn()
{
    return new Channel('orderStatus');
}
Copy after login

Here we name the channel: orderStatus and return. That is to say, when this event is broadcast, it will be broadcast to the channel named orderStatus.

This is a "public channel". Anyone can monitor this channel and receive broadcast messages. Laravel also provides "private channels", which can only be successfully monitored after permission verification. We’ll talk about that later.

(5) By default, Laravel will use the "event name" as the "message name" of the broadcast without any data. We can add any member variables within the event class and modify the constructor to send data to the client.

//可添加任意成员变量
public $id;
//事件构造函数
public function __construct($id)
{
    $this->id = $id;
}
//自定义广播的消息名
public function broadcastAs()
{
    return 'anyName';
}
Copy after login

(6) As above, we have basically established the basic mechanism of broadcasting. Next we need an interface that can "trigger events" (that is, send broadcasts).

Add the following code in routes/api.php:

Route::get('/ship', function (Request $request)
{
    $id = $request->input('id');
    event(new OrderShipped($id)); // 触发事件
    return Response::make('Order Shipped!');
});
Copy after login

(7) OK! Open Postman, enter: http://***/api/ship?id=1000, and send.

Open storage/logs/laravel.log, you will find a few more lines:

[2018-03-02 01:41:19] local.INFO: Broadcasting [App\Events\OrderShipped] on channels [orderStatus] with payload:
{
    "id": "1000",
    "socket": null
}
Copy after login

Congratulations, you have successfully configured the Broadcast log driver.

3. Broadcast

In the previous section, we used log as the Broadcast Driver, which means that the broadcast message will be recorded in the log, so how to really What about communicating with the client? This requires the use of the "indirect layer" mentioned at the beginning.

(1) First, change the Driver from log to pusher.

In order to save the steps of installing Redis, we will use the HTTP protocol to push directly to the compatible "local Pusher server" (ie, indirect layer).

(2) Since Laravel does not have a built-in Broadcast Pusher Driver, you need to use Composer to install the Pusher PHP SDK:

composer require pusher/pusher-php-server
Copy after login

(3) Register App\Providers\BroadcastServiceProvider.

For Laravel 5.6, you only need to uncomment the corresponding comments in the providers array in config/app.php.

(4) Next, you need to install and configure the "indirect layer" for communication between the server and the client.

We use the official document recommendation: tlaverdure/laravel-echo-server. This is a WebSocket server implemented using Node.js Socket.IO.

It is compatible with Pusher HTTP API, so you can directly modify the Driver to Pusher instead of Redis as above.

npm install -g laravel-echo-server
Copy after login

Initialize the configuration file and fill it in according to the prompts.

laravel-echo-server init
Copy after login

Open laravel-echo-server.json and check whether some key configuration items are correct:

"authHost": "http://xxx" // 确保能够访问到你的 Laravel 项目
"port": "6001" // 建议不作修改,这是与客户端通信的端口
"protocol": "http" // 与客户端通信的协议,支持 HTTPS
Copy after login

复制两个值:clients.appId 以及 clients.key,并修改 config/broadcasting.php。

'pusher' => [
    'driver' => 'pusher',
    'key' => env('PUSHER_APP_KEY'),
    'secret' => null,
    'app_id' => env('PUSHER_APP_ID'),
    'options' => [
        'host' => 'localhost',
        'port' => 6001,
    ],
],
Copy after login

顾名思义,将 appId 和 key 分别填入相应配置项或修改 .env 文件即可。

接下来,我们便可以使用命令 laravel-echo-server start 来启动服务器,监听来自 Laravel 的「广播」请求、以及来自客户端的「收听」请求,并转发相应广播消息。

(5)从这里开始我们将会配置前端部分。

首先,需要配置 CSRF Token。

如果你使用 Blade 模板引擎,则执行 php artisan make:auth 命令即可将 Token 加入 resources/views/layouts/app.blade.php。

若没有使用此模板文件,则可以直接在 Blade 模板文件首行直接写入 Meta 值。

为了便于测试,我们在 resources/views/welcome.blade.php 内添加:

Copy after login

对于前后端分离的项目,可关闭 CSRF Token 验证。

(6)其次,你需要引用 Socket.IO 的客户端 JS 文件,位置同上。

Copy after login

(7)这里我们采用官方的 Laravel Echo 扩展包收听服务端广播。打开 resources/assets/js/bootstrap.js,修改最后几行即可。

import Echo from 'laravel-echo'

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

接着编写「收听频道」代码,非常简单:

Echo.channel(`orderStatus`) // 广播频道名称
    .listen('OrderShipped', (e) => { // 消息名称
        console.log(e); // 收到消息进行的操作,参数 e 为所携带的数据
    });
Copy after login

(8)安装依赖,编译前端文件。执行如下命令:

npm install
npm run dev
Copy after login

最后,在 Blade 模板内引用我们刚刚写好的 JS 脚本。由于 app.js 默认已经引用 bootstrap.js,所以我们只需要引用 app.js 即可。

我们在第(5)步的文件内添加:

Copy after login

(9)好了!在查看效果前,不要忘记执行第(4)步的最后一条命令,启动 Laravel Echo Server。

Laravel 默认已经定义首页路由渲染 welcome.blade.php 模板,现在只要使用浏览器访问应用 URL 即可。

如果查看 Chrome 控制台,没有任何错误产生;查看命令行窗口,没有错误输出;则说明客户端与服务器似乎已经正常建立 WebSocket 连接。

这时,你可以重新打开Postman,发送上一节中的请求。

再次查看如上两个窗口,会有惊喜哟。

4、私有频道

上一节我们成功实现可以被任何人收听的「共有频道」广播,那么如何与部分客户端进行通讯呢?仅仅依靠前端的验证是不够的。我们需要创建带有「认证」功能的「私有频道」。

(1)首先,打开 app/Providers/BroadcastServiceProvider.php,在上一节中我们已经注册此服务提供者,现在我们需要取消注释一部分代码。

public function boot()
{
    Broadcast::routes(); // 还记得 laravel-echo-server.json 的 authEndpoint 配置项吗?
    require base_path('routes/channels.php');
}
Copy after login

Broadcast::routes() 用于注册验证路由(即 /broadcasting/auth),当客户端收听频道时,Laravel Echo Server 会访问此路由,以验证客户端是否符合「认证」条件。

(2)Broadcast 认证分为两个部分:

使用 Laravel 内置的 Auth 系统进行认证。

根据自定义规则进行部分频道的认证。

(3)首先,需要配置 Auth 认证系统。

根据情况修改 .env 文件的数据库配置项后,只需要执行 php artisan make:auth 创建所需文件,再执行 php artisan migrate 创建所需数据库结构即可。

深入了解,请参考用户认证文档。

接下来我们在浏览器中打开你的应用,会发现右上角多了登录和注册,我们随意注册一个测试用户以备使用。

(4)接下来,配置频道认证。

还记得第(1)步的 routes/channels.php 吗,我们打开此文件。并新增一条频道认证规则。

注意:虽然此文件位于 routes 目录下,但并不是路由文件!在此定义后并不能访问,且无法使用分组、中间件。所有的验证路由都已经在 Broadcast::routes() 中定义。

Broadcast::channel('orderStatus', function ($user, $value) {
    return true; // or false
});
Copy after login

由于 Broadcast 已经使用 Auth 进行用户登录认证,所以我们只需无条件返回 true 即可实现:任何已登录用户都可以收听此频道。

(5)认证部分我们已经配置完成,接下来将共有频道的定义改为私有。

修改广播消息基于的事件类 app/Events/OrderShipped.php:

public function broadcastOn()
{
    return new PrivateChannel('orderStatus'); // 私有频道
}
Copy after login

修改客户端收听代码 resources/assets/js/bootstrap.js。

Echo.private(`orderStatus`) // 私有频道
    .listen('OrderShipped', (e) => {
        console.log(e);
    });
Copy after login

(6)接下来,再次运行 Laravel Echo Server。使用浏览器打开你的应用首页,在未登录状态,可以看到 Echo Server 输出一个来自 Laravel 的 AccessDeniedHttpException 异常,提示用户认证失败,无法收听频道。

登录后,即可获得与上一节相同的预期结果。

(7)如上,我们成功实现所有登录用户均可收听私有频道。那么如何实现某部分用户可以收听,某部分用户不可以收听频道?例如:某个用户均有属于自己的频道,他只能收听自己的频道。请继续往下看。

首先,修改广播消息基于的事件类 app/Events/OrderShipped.php。你需要将频道命名修改为动态的值。

public $userId; // 新增成员变量 userId,不要忘记在构造函数内对其进行赋值

public function broadcastOn()
{
    return new PrivateChannel('orderStatus-' . $this->userId); // 动态命名私有频道
}
Copy after login

其次,修改第(4)步中的 routes/channels.php 文件。Broadcast 支持使用通配符匹配「某一类」频道进行验证。

Broadcast::channel('orderStatus-{userId}', function ($user, $value) {
    // $user    Auth认证通过的用户模型实例
    // $value   频道规则匹配到的 userId 值
    return $user->id == $value; // 可使用任意条件验证此用户是否可监听此频道
});
Copy after login

最后别忘记修改前端收听的频道名称。

再次打开浏览器测试即可发现:在本例中,若客户端收听的频道不匹配当前用户 ID,则会报错。

(8)如上,我们成功实现对私有频道进行自定义规则的认证;但如果我们没有使用 Auth 认证系统,或采用了自己编写的用户认证中间件,该如何兼容呢?

经过一番源码调试,我发现 Broadcast 在 vendor/laravel/framework/src/Illuminate/Broadcasting/Broadcasters/ 文件夹下定义的 Broadcaster 内调用 $request->user() 进行了用户验证。

例如我们采用的 PusherBroadcaster.php:

/**
 * Authenticate the incoming request for a given channel.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return mixed
 * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
 */
public function auth($request)
{
    if (Str::startsWith($request->channel_name, ['private-', 'presence-']) &&
        ! $request->user()) {
        throw new AccessDeniedHttpException;
    }
    $channelName = Str::startsWith($request->channel_name, 'private-')
                        ? Str::replaceFirst('private-', '', $request->channel_name)
                        : Str::replaceFirst('presence-', '', $request->channel_name);
    return parent::verifyUserCanAccessChannel(
        $request, $channelName
    );
}
Copy after login

由此可得,我们有两种方式实现。

第一种

直接注释 throw new AccessDeniedHttpException,并修改 app/Providers/BroadcastServiceProvider.php。

Broadcast::routes() 可接收一个参数。在 vendor/laravel/framework/src/Illuminate/Broadcasting/BroadcastManager.php 可查看其定义:

/**
 * Register the routes for handling broadcast authentication and sockets.
 *
 * @param  array|null  $attributes
 * @return void
 */
public function routes(array $attributes = null)
{
    if ($this->app->routesAreCached()) {
        return;
    }
    $attributes = $attributes ?: ['middleware' => ['web']];
    $this->app['router']->group($attributes, function ($router) {
        $router->post('/broadcasting/auth', '\\'.BroadcastController::class.'@authenticate');
    });
}
Copy after login

通过源码可知:此参数等效于 Route::group() 的第一个参数。所以我们只要将其修改为如下形式:

Broadcast::routes(['middleware' => ['yourMiddleware'])
Copy after login

并在中间件内进行用户认证;如未登录,照常抛出 AccessDeniedHttpException 异常即可。

第二种

在 vendor/laravel/framework/src/Illuminate/Http/Request.php 可以查看到 $request->user() 的定义。

/**
 * Get the user making the request.
 *
 * @param  string|null  $guard
 * @return mixed
 */
public function user($guard = null)
{
    return call_user_func($this->getUserResolver(), $guard);
}
Copy after login

如上可知,它使用 $this->userResolver 内的匿名函数获取用户模型。所以我们只需要在AuthServiceProvider 注册后,Broadcast 认证前,替换掉其 userResolver 即可。

例如:继承 Illuminate\Auth\AuthServiceProvider(vendor/laravel/framework/src/Illuminate/Auth/AuthServiceProvider.php),并重写 registerRequestRebindHandler 方法及构造函数,添加如下代码。

$request->setUserResolver(function ($guard = null) use ($app) {
    // 在此判断用户登录状态
    // 若登录,请返回 App\User 或其它用户模型实例
    // 未登录,请返回 null
});
Copy after login

修改 config/app.php,使用改造过的 AuthServiceProvider 类替换原服务提供者即可。

5、总结

至此,你已经建立对 Laravel Boardcast 的基本认识,成功入门「广播系统」。

另外,Laravel 5.6 新增一条关于 Broadcast 的新特性,避免在 routes/channels.php 文件内编写众多闭包导致的难以维护。详情可查看:Laravel 5.6 新版特性。

其实,本文只不过抛砖引玉而已。对于部署到生产环境,仍然存在许多问题,例如:

与常见 PHP 应用不同,广播基于 WebSocket 长连接;那么如何确保通信稳定性,如何剔除死链?

为了确保应用访问速度,广播通常是异步执行的;那么如何配置事件队列?如何将队列配置为异步?

如何确保 Laravel Echo Server 进程稳定运行?如何处理异常?是否需要关闭 Debug 模式?

如何确保 Laravel Echo 与服务端连接稳定?是否需要定时检查?如何捕捉并完善地处理所有异常?

客户端收听私有频道,若被服务器拒绝,是否需要断开连接?如何给予用户友好地提示?

……

当然,这些问题本文没有提到,但网络上已经有无数成熟、便捷的解决方案;更多高级用法也可以参照本文最初列出的官方文档。

以上就是本篇文章的全部内容了,更多内容请关注laravel入门教程!

相关文章推荐:

Laravel框架中管道设计模式之中间件的基本工作原理

Laravel框架实现发送短信验证功能代码,laravel发送短信

相关课程推荐:

全方位解读Laravel框架及实战视频教程

The latest five recommended Laravel video tutorials in 2017

The above is the detailed content of How the built-in Broadcast function of the Laravel framework achieves real-time communication with the client. For more information, please follow other related articles on the PHP Chinese website!

Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact [email protected]
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
About us Disclaimer Sitemap
php.cn:Public welfare online PHP training,Help PHP learners grow quickly!