>PHP 프레임워크 >Laravel >Laravel 프레임워크에 내장된 Broadcast 기능이 클라이언트와 실시간 통신을 달성하는 방법

Laravel 프레임워크에 내장된 Broadcast 기능이 클라이언트와 실시간 통신을 달성하는 방법

不言
不言원래의
2018-07-31 10:36:274719검색

Laravel 프레임워크는 많은 개발 패키지의 기능을 통합하지만 실제로는 좋은 프레임워크입니다. 다음 글에서는 Laravel 5.6 버전을 기반으로 내장된 Broadcast 기능을 사용하여 클라이언트와 실시간 통신하는 방법을 알려드리겠습니다.

1. 준비

방송 시스템

사용자 인증

이벤트 시스템

큐 시스템

프론트 엔드 가이드

tlaverdure/laravel-echo-server

예, 이것이 바로 여러분에게 필요한 지식 저장소입니다.

PHP 자체는 WebSocket을 지원하지 않기 때문에 "서버" 데이터를 "클라이언트"로 보낼 수 있는 간접 계층이 필요합니다. 즉, 실시간 통신 구현은 크게 두 단계로 나눌 수 있습니다.

"Laravel" -> "간접 레이어"

"간접 레이어" - > (WebSocket을 통해) - > 간접 레이어에 어떤 구현을 사용하는지에 대해서는 나중에 논의하겠습니다.

2. 구성

위의 방송 시스템 문서에 따르면 먼저 다음 구성 작업을 수행해야 합니다.

(1) 먼저 config/broadcasting.php 또는 .env 파일을 수정합니다. 이 기능을 활성화하고 디버깅을 더 쉽게 하려면 브로드캐스트 기본 드라이버가 로그인지 확인하세요.

(2) Broadcast를 사용하려면 Laravel의 이벤트 시스템을 이해해야 하며, 이는 상호의존적입니다. 다음으로 "방송"할 수 있는 이벤트를 만듭니다.

app/Providers/EventServiceProvider.php를 수정하고 $listen 멤버 변수를 추가합니다.

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

여기서 이 이벤트의 이름을 OrderShipped(주문이 확정됨)로 지정합니다. php artisan event:generate를 실행하여 이벤트 클래스와 해당 리스너를 생성합니다.

이벤트 실행(예: 클라이언트에 브로드캐스트)을 비동기식으로 수정해야 하는 경우 위의 대기열 시스템 설명서를 참조하세요.

(3) 이벤트가 "브로드캐스트"되도록 하려면 이벤트 클래스가 ShouldBroadcast 인터페이스를 상속하도록 해야 합니다.

app/Events/OrderShipped.php를 열고 클래스 정의를 다음과 같이 수정합니다.

class OrderShipped implements ShouldBroadcast

(4) ShouldBroadcast 인터페이스에는 프레임워크에 이 이벤트가 어떤 "채널"인지 알리는 데 사용되는 BroadcastOn 메서드의 구현이 필요합니다. 로 보냈습니다.

Laravel의 방송 시스템에서는 사용자 이름을 사용하여 서로 다른 채널을 구분할 수 있으므로 서로 다른 사용자가 서로 다른 채널을 사용하여 서로 다른 메시지를 얻을 수 있고 서로 다른 클라이언트와 개별적으로 통신할 수 있습니다.

물론 채널 이름을 임의로 지정할 수도 있지만 일정한 규칙을 갖는 것이 가장 좋습니다.

Artisan 명령어로 생성된 이벤트 코드를 사용했기 때문에 파일 하단에서 이미 BroadcastOn 메소드 정의를 볼 수 있습니다. 약간 수정합니다:

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

여기서 채널 이름을 orderStatus로 지정하고 반환합니다. 즉, 이 이벤트가 방송되면 orderStatus라는 채널로 방송됩니다.

이 채널은 "공개 채널"입니다. 누구나 이 채널을 모니터링하고 방송 메시지를 받을 수 있습니다. Laravel은 또한 권한 확인 후 성공적으로 모니터링할 수 있는 "비공개 채널"을 제공합니다. 그것에 대해서는 나중에 이야기하겠습니다.

(5) 기본적으로 Laravel은 데이터 없이 방송의 "메시지 이름"으로 "이벤트 이름"을 사용합니다. 이벤트 클래스 내에 멤버 변수를 추가하고 생성자를 수정하여 클라이언트에 데이터를 보낼 수 있습니다.

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

(6) 위와 같이 기본적으로 방송의 기본 메커니즘을 확립했습니다. 다음으로 "이벤트를 트리거"(즉, 브로드캐스트 전송)할 수 있는 인터페이스가 필요합니다.

routes/api.php에 다음 코드를 추가하세요:

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

(7) 그게 다입니다! Postman을 열고 http://***/api/ship?id=1000을 입력한 후 보냅니다.

storage/logs/laravel.log를 열면 몇 줄을 더 찾을 수 있습니다:

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

축하합니다. 브로드캐스트 로그 드라이버를 성공적으로 구성했습니다.

3. Broadcast

이전 섹션에서는 브로드캐스트 드라이버로 로그를 사용했는데, 이는 브로드캐스트 메시지가 로그에 기록된다는 의미입니다. 그렇다면 실제로 클라이언트와 통신하는 방법은 무엇일까요? 이를 위해서는 처음에 언급한 "간접 레이어"를 사용해야 합니다.

(1) 먼저 드라이버를 로그에서 푸셔로 변경합니다.

Redis 설치 단계를 저장하기 위해 HTTP 프로토콜을 사용하여 호환 가능한 "로컬 푸셔 서버"(즉, 간접 레이어)에 직접 푸시하겠습니다.

(2) Laravel에는 내장형 Broadcast Pusher 드라이버가 없으므로 Composer를 사용하여 Pusher PHP SDK를 설치해야 합니다.

composer require pusher/pusher-php-server

(3) AppProvidersBroadcastServiceProvider를 등록합니다.

Laravel 5.6의 경우 config/app.php의 공급자 배열에서 해당 주석의 주석 처리를 제거하기만 하면 됩니다.

(4) 다음으로 서버와 클라이언트 간의 통신을 위한 "간접 레이어"를 설치하고 구성해야 합니다.

우리는 공식 문서 권장 사항인 tlaverdure/laravel-echo-server를 사용합니다. Node.js + Socket.IO를 사용하여 구현된 WebSocket 서버입니다.

Pusher HTTP API와 호환되므로 위와 같이 Redis 대신에 Driver를 Pusher로 직접 수정할 수 있습니다.

npm install -g laravel-echo-server

구성 파일을 초기화하고 프롬프트에 따라 입력하세요.

laravel-echo-server init

laravel-echo-server.json을 열고 일부 주요 구성 항목이 올바른지 확인하세요.

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

复制两个值: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,
    ],
],

顾名思义,将 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 内添加:

<meta name="csrf-token" content="{{ csrf_token() }}">

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

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

<script src="//{{ Request::getHost() }}:6001/socket.io/socket.io.js"></script>

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

import Echo from 'laravel-echo'

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

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

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

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

npm install
npm run dev

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

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

<script src="{{ asset(&#39;/js/app.js&#39;) }}"></script>

(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(&#39;routes/channels.php&#39;);
}

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(&#39;orderStatus&#39;, function ($user, $value) {
    return true; // or false
});

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

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

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

public function broadcastOn()
{
    return new PrivateChannel(&#39;orderStatus&#39;); // 私有频道
}

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

Echo.private(`orderStatus`) // 私有频道
    .listen(&#39;OrderShipped&#39;, (e) => {
        console.log(e);
    });

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

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

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

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

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

public function broadcastOn()
{
    return new PrivateChannel(&#39;orderStatus-&#39; . $this->userId); // 动态命名私有频道
}

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

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

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

再次打开浏览器测试即可发现:在本例中,若客户端收听的频道不匹配当前用户 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, [&#39;private-&#39;, &#39;presence-&#39;]) &&
        ! $request->user()) {
        throw new AccessDeniedHttpException;
    }
    $channelName = Str::startsWith($request->channel_name, &#39;private-&#39;)
                        ? Str::replaceFirst(&#39;private-&#39;, &#39;&#39;, $request->channel_name)
                        : Str::replaceFirst(&#39;presence-&#39;, &#39;&#39;, $request->channel_name);
    return parent::verifyUserCanAccessChannel(
        $request, $channelName
    );
}

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

第一种

直接注释 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 ?: [&#39;middleware&#39; => [&#39;web&#39;]];
    $this->app[&#39;router&#39;]->group($attributes, function ($router) {
        $router->post(&#39;/broadcasting/auth&#39;, &#39;\\&#39;.BroadcastController::class.&#39;@authenticate&#39;);
    });
}

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

Broadcast::routes([&#39;middleware&#39; => [&#39;yourMiddleware&#39;])

并在中间件内进行用户认证;如未登录,照常抛出 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);
}

如上可知,它使用 $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
});

修改 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框架及实战视频教程

2017년에 권장되는 최신 Laravel 비디오 튜토리얼 5개

위 내용은 Laravel 프레임워크에 내장된 Broadcast 기능이 클라이언트와 실시간 통신을 달성하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.