之前在網上翻了半天,也沒有找到關於如何 通過laravel-echo
主動傳送訊息 和 在laravel-websockets
中自定義控制器 的文章或教程。無奈之下只能翻laravel-echo
和laravel-websockets
的原始碼了,小有收穫。在此為有需要的朋友指個方向,少踩一個坑。
書接上回《利用websocket實現手機掃碼登陸後,同步登陸資訊到web端頁面》,我們已經實現伺服器主動向網頁傳送訊息的功能了。但是,在使用laravel-echo
時,網頁想主動向伺服器傳送訊息該怎麼做呢?首先,最簡單的就是Ajax
非同步請求了,寫起來很容易。還有一種方式就是通過websocket
了,既然已經建立了 socket 連線,我們何不利用他來進行雙向通訊呢!這就是本文的重點內容:如何使用laravel-echo
通過websocket
進行反向通訊(這裡的「反向」指從網頁向伺服器傳送訊息)。
在上一篇 文章 的案例中,websocket 預設監聽的地址是http://<your.host>:<wsPort>/app/<key>
。url 中的/app/
部分是pusher
中規定的,目前還無法修改。<key> 指例項化Echo
時的引數「key」,同時也是.env
檔案中的PUSHER_APP_KEY
環境變數。後臺控制器是laravel-websockets
已經定義好的 websocket 控制器:BeyondCode\LaravelWebSockets\WebSockets\WebSocketHandler
。現在我們要實現自己的控制器,來做線上狀態管理,所以就需要改變 websocket 預設監聽的地址和後臺控制器。
開啟app/resources/views/hellow.blade.php
檢視檔案,在初始化Echo
中的引數部分加入wsPath
變數。此時 websocket 監聽的地址就會變為http://<your.host>:<wsPort><wsPath>/app/<key>
。
window.Echo = new Echo({
broadcaster: 'pusher',
key: 'joker',
// 在 socket 連結中設定 url 路徑
wsPath: '/liam/hao',
wsHost: location.hostname,
wsPort: 2020,
forceTLS: false,
});
注意:
wsPath
變數一定要用 “/” 開頭,否則會報錯的
因為前端改變了監聽路由,對應的後端,也需要新增一個對應的路由。我們開啟routes/web.php
檔案,加入以下新路由:
Route::get('/login', function () {
return view('login');
});
// 這個是 laravel-websockets 提供的門面方法,用來註冊自定義的 websocekt 路由
// WebSocketsRouter 繫結的實際物件是 BeyondCode\LaravelWebSockets\Server\Router 有興趣的可以瞅瞅
\BeyondCode\LaravelWebSockets\Facades\WebSocketsRouter::webSocket('/liam/hao/app/{appKey}', \App\Http\Controllers\MyWebsocketHandler::class);
上面的路由中繫結了一個App\Http\Controllers\MyWebsocketHandler
類,現在我們就來實現這個類:
> php artisan make:controller MyWebsocketHandler
Controller created successfully.
執行上面的命令後,我們會在app/Http/Controllers
資料夾中找到MyWebsocketHandler.php
檔案,我們來進行一些修改:
<?php
namespace App\Http\Controllers;
use BeyondCode\LaravelWebSockets\WebSockets\Messages\PusherMessageFactory;
use BeyondCode\LaravelWebSockets\WebSockets\WebSocketHandler;
use Ratchet\ConnectionInterface;
use Ratchet\RFC6455\Messaging\MessageInterface;
// 注意這裡要繼承自 WebSocketHandler
class MyWebsocketHandler extends WebSocketHandler
{
public function onMessage(ConnectionInterface $connection, MessageInterface $message)
{
var_dump(json_decode($message->getPayload()));
$message = PusherMessageFactory::createForMessage($message, $connection, $this->channelManager);
$message->respond();
}
public function onClose(ConnectionInterface $connection)
{
$this->channelManager->removeFromAllChannels($connection);
var_dump('close');
}
}
好了,我們現在來小測一下,看看新的路由有沒有生效。重啟laravel-websockets
的 http 服務。開啟瀏覽器的開發者工具,然後重新整理頁面。如果像下圖一樣,在命令列終端裡看到了我們var_dump()
的資料,那就說明新的路由和控制器已經連通了:
注意:MyWebsocketHandler 並不是一般的 Controller,他需要繼承自
BeyondCode\LaravelWebSockets\WebSockets\WebSocketHandler
。如果你需要控制更多邏輯,可直接實現Ratchet\WebSocket\MessageComponentInterface
介面,並自己實現onOpen()
、onClose()
、onMessage()
等方法。
重點來了哈,雖說是重點,但程式碼很簡單:
Echo.channel('abcdefg.'+uuid)
.listen('LoginedEvent', (e) => {
console.log(e);
var session_id = e.session_id;
location.href = location.origin+'/hello?session_id='+session_id;
});
// 我們在這裡放置一個定時器,每三秒鐘向伺服器傳送一條資料
setInterval(function(){
// 這裡新增一個向服務端傳送訊息的方法
// 第一個引數是事件名,這個可以隨意寫,不需要與 Laravel 中做對應
// 第二個引數是具體資料,這個就更隨意了
Echo.connector.pusher.send_event('hi_girl', {
my_name: 'LiamHao',
my_height: 180,
});
}, 3000);
是不是很簡單,我們再來小測一下,看看服務端接收到的資料是什麼樣子的:
後端已經接收到資料了。做到這裡,想必有些基礎的朋友應該已經可以做自己想做的事情了。
這裡我們就不做太複雜了資料庫操作了,還是老樣子,以最簡單的方式,用 快取 做記錄吧。我們在App\Http\Controllers\MyWebsocketHandler
的onMessage()
和onClose()
方法中分別加入記錄狀態的程式碼:
public function onMessage(ConnectionInterface $connection, MessageInterface $message)
{
var_dump(json_decode($message->getPayload()));
// 每當收到訊息時,設定當前連線狀態為“線上”,60 秒後過期
\Cache::put($connection->socketId, '線上', 60);
$message = PusherMessageFactory::createForMessage($message, $connection, $this->channelManager);
$message->respond();
}
public function onClose(ConnectionInterface $connection)
{
$this->channelManager->removeFromAllChannels($connection);
var_dump('close');
// 瀏覽器主動斷開連線時,設定當前連線狀態為“離線”,不設定過期時間
\Cache::put($connection->socketId, '離線');
}
然後修改下app/resources/views/login.blade.php
檢視檔案,在頁面中顯示連線的狀態:
<body>
<input type="text" name="username">
<input type="text" name="password">
<button>登陸</button>
<!-- 我們在這裡簡單的展示一下連線的狀態 -->
<h1>Websocket 連線列表</h1>
<!-- 用 blade 模板語法渲染資料,簡單的列印下資料 -->
<pre>{{ var_export(\Cache::get('socekt-status')) }}</pre>
</body>
大功告成,我們來看下效果。先開啟http://<your.host>/login
頁面,此時未顯示任何內容。這是正常的,因為Cache
中還沒有記錄任何資訊。接下來再開啟http://<your.host>/hello
頁面,看下瀏覽器開發者工具中 websocket 已連線成功。下面見證奇蹟的時刻到了,我們再將http://<your.host>/login
頁面重新整理一次,此時會看到Cache
中記錄的線上狀態資訊:
我們再來試下斷開 websocekt 連線時,是否會顯示離線。將http://<your.host>/hello
頁面直接關閉,也就是點標籤頁上的「叉子」。再重新整理下http://<your.host>/login
頁面:
本文儘量簡化過程,減少無關操作,也將實現步驟儘可能詳細的展現出來看,希望能對大家有所幫助。
本作品採用《CC 協議》,轉載必須註明作者和本文連結