概述
在laravel中優雅地使用長鏈接的方式。
服務端只需要廣播一個事件,客戶端就可以收到該事件的廣播。
總體就是:服務端事件->socket->瀏覽器。
其中服務端就用laravel的event事件和廣播相關功能完成。
其中socket部分由依賴包laravel-echo-server服務完成。
其中瀏覽器也就是前端,由依賴包socket.io-client和laravel-echo完成。
最後達到的效果就是:在laravel程序中需要廣播給client端的東西寫成事件,並在業務中觸發。
客戶端js部分,訂閱對應頻道,以及對應服務端的事件名,然後處理收到的該事件的內容。
這樣,就可以很方便的在laravel中開發長鏈接相關功能!
初始化配置
這裏使用的框架版本爲5.8,將使用redis作爲廣播的驅動,所以請在你本機運行redis(或者有遠程redis地址也可以)。
-
默認laravel關閉了廣播服務,需要在config/app.php文件中取消註釋。
App\Providers\BroadcastServiceProvider::class
-
laravel默認給redis驅動設置了前綴,需要在config/database.php文件中註釋掉。
// 'options' => [ // 'cluster' => env('REDIS_CLUSTER', 'predis'), // 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'), // ],
-
修改.env配置文件,將廣播驅動設置爲redis。
BROADCAST_DRIVER=redis
-
安裝依賴
如果首次運行npm,需要先運行npm install安裝laravel相關npm依賴。
如果npm下載慢可以用阿里的鏡像http://npm.taobao.org/,安裝阿里的cnpm後使用cnpm安裝。安裝負責監聽處理socket的nodejs包:
npm install -g laravel-echo-server安裝前端的事件處理包:
npm install --save socket.io-client
npm install --save laravel-echo安裝predis
composer require predis/predis -
運行socket服務
laravel-echo-server init // 首次運行請執行該初始化命令,會在項目根目錄生成laravel-echo-server.json配置文件laravel-echo-server start // 啓動
公共頻道例子
執行命令創建一個事件處理器:php artisan make:event TestEvent
編輯事件類,實現ShouldBroadcast接口類:implements ShouldBroadcast。
<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
class TestEvent implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $message;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct($test)
{
//
$this->message = $test;
}
// 廣播頻道
public function broadcastOn()
{
// return new PrivateChannel('channel-name');
return new Channel('test_public_channel');
}
// 廣播內容
public function broadcastWith()
{
return [
'data' => $this->message
];
}
// 廣播事件名稱,默認爲 App\Events\TestEvent
public function broadcastAs()
{
return 'TestEvent';
}
// 決定是否廣播的條件
public function broadcastWhen()
{
if ('test') {
return true;
}
}
}
在前端收聽廣播:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="csrf-token" content="{{ csrf_token() }}">
<script src="//{{ Request::getHost() }}:6001/socket.io/socket.io.js"></script>
</head>
<body>
<script type="module">
import Echo from '/static/echo/echo.js';
// 連接socket服務
window.Echo = new Echo({
broadcaster: 'socket.io',
host: 'http://'+window.location.hostname + ':6001'
});
// 訂閱公共頻道並監聽事件
window.Echo.channel('test_public_channel')
.listen('.TestEvent', (e) => { // 注意,如果在laravel事件中用broadcastAs方法設置了廣播事件名稱,則這裏監聽的事件名稱前要加"."。
console.log(e);
});
</script>
</body>
</html>
通過瀏覽器訪問該視圖,可見其成功連接了laravel-echo-server服務:
通過laravel-echo-server的控制檯也可以看到加入了頻道:
然後在業務中觸發該事件,由該事件向頻道里發送廣播,客戶端收到廣播後在console中打印出廣播內容:
某某Controller:
public function test(Request $request)
{
broadcast(new \App\Events\TestEvent("123321"));
}
然後通過瀏覽器或cli,訪問這個方法,訪問後在瀏覽器可見收到了socket消息:
如果服務端推送時想放到隊列中非阻塞執行,也很簡單,只需要在TestEvent事件類中指定事件使用哪個隊列:
public $broadcastQueue = 'TestBroadcastQueue';
隊列驅動在config/queue.php中設置,我這裏使用的redis,然後在cli運行隊列:
php artisan queue:work --queue=TestBroadcastQueue
再次觸發TestEvent廣播事件,通過隊列發送效果:
如果想定時發送,那就在業務中不直接調用事件進行廣播,而是添加到job並設定延時,由job執行時調用事件進行廣播。
私有頻道例子
私有頻道需要用戶被授權之後才能監聽。
客戶端向服務端發起一個監聽請求,由服務端決定該用戶是否可監聽該頻道。
接下來我們創建一個消息盒子,服務端發送給指定用戶的消息實時推送給指定用戶。
由於我這邊是新框架沒有用戶登錄代碼,所以先初始化一下:
php artisan migrate
php artisan make:auth
然後瀏覽器訪問/home註冊一個用戶。
接下來在路由中增加私有頻道路由,處理用戶監聽請求的授權判斷:
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('message-box-{id}', function ($user, $userid) {
return (int) $user->id === (int) $userid;
});
創建一個事件廣播:
php artisan make:event MessageBoxEvent
<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
class MessageBoxEvent implements ShouldBroadcastNow
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $message;
public $userid;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct($userid, $test)
{
$this->userid = $userid;
$this->message = $test;
}
// 廣播頻道
public function broadcastOn()
{
return new PrivateChannel('message-box-'.$this->userid);
}
// 廣播內容
public function broadcastWith()
{
return [
'data' => $this->message
];
}
// 廣播事件名稱,默認爲 App\Events\MessageBoxEvent
public function broadcastAs()
{
return 'MessageBoxEvent';
}
// 決定是否廣播的條件
public function broadcastWhen()
{
if ('test') {
return true;
}
}
}
視圖增加監聽私有頻道代碼:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="csrf-token" content="{{ csrf_token() }}">
<script src="/static/jquery/jquery-1.10.1.min.js"></script>
<script src="//{{ Request::getHost() }}:6001/socket.io/socket.io.js"></script>
</head>
<body>
<script type="module">
import Echo from '/static/echo/echo.js';
// 連接socket服務
window.Echo = new Echo({
broadcaster: 'socket.io',
host: 'http://' + window.location.hostname + ':6001'
});
// 訂閱公共頻道並監聽事件
window.Echo.channel('test_public_channel')
.listen('.TestEvent', (e) => { // 注意,如果在laravel事件中用broadcastAs方法設置了廣播事件名稱,則這裏監聽的事件名稱前要加"."。
console.log(e);
});
// 訂閱私有頻道並監聽事件
window.Echo.private('message-box-{{ Auth::user()->id}}')
.listen('.MessageBoxEvent', (e) => { // 注意,如果在laravel事件中用broadcastAs方法設置了廣播事件名稱,則這裏監聽的事件名稱前要加"."。
console.log(e);
});
</script>
</body>
</html>
然後某Controller中的某方法調用該事件:
broadcast(new \App\Events\MessageBoxEvent(\Auth::user()->id,"測試發送給單獨用戶的消息"));
瀏覽器訪問觸發後效果:
laravel-echo-server控制檯輸出:
參考資料:
https://learnku.com/laravel/t/13101/using-laravel-echo-server-to-build-real-time-applications
https://learnku.com/index.php/docs/laravel/5.8/broadcasting/3914