「手把手」利用laravel-echo主動向服務端傳送訊息,實現線上狀態管理

LiamHao 發表於 2021-03-28
Laravel

之前在網上翻了半天,也沒有找到關於如何 通過laravel-echo主動傳送訊息laravel-websockets中自定義控制器 的文章或教程。無奈之下只能翻laravel-echolaravel-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()的資料,那就說明新的路由和控制器已經連通了:

「手把手」利用laravel-echo主動向服務端傳送訊息,實現線上狀態管理

注意: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);

是不是很簡單,我們再來小測一下,看看服務端接收到的資料是什麼樣子的:

「手把手」利用laravel-echo主動向服務端傳送訊息,實現線上狀態管理

後端已經接收到資料了。做到這裡,想必有些基礎的朋友應該已經可以做自己想做的事情了。

這裡我們就不做太複雜了資料庫操作了,還是老樣子,以最簡單的方式,用 快取 做記錄吧。我們在App\Http\Controllers\MyWebsocketHandleronMessage()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中記錄的線上狀態資訊:

「手把手」利用laravel-echo主動向服務端傳送訊息,實現線上狀態管理

我們再來試下斷開 websocekt 連線時,是否會顯示離線。將http://<your.host>/login頁面直接關閉,也就是點標籤頁上的「叉子」。再重新整理下http://<your.host>/hello頁面:

「手把手」利用laravel-echo主動向服務端傳送訊息,實現線上狀態管理

本文儘量簡化過程,減少無關操作,也將實現步驟儘可能詳細的展現出來看,希望能對大家有所幫助。

本作品採用《CC 協議》,轉載必須註明作者和本文連結