「手把手」通过laravel-echo主动向服务端发送消息,实现在线状态管理

「手把手」利用laravel-echo主动向服务端发送消息,实现在线状态管理

之前在网上翻了半天,也没有找到关于如何 通过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已经定义好的控制器:BeyondCode\LaravelWebSockets\WebSockets\WebSocketHandler。现在我们要实现自己的控制器,来做在线状态管理,所以就需要改变网页默认连接的 websocket 地址和后台控制器。

修改前端监听地址

打开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变量一定要用 “/” 开头,否则会报错的

技巧:这里再提一个小知识点吧,pusher默认会在连接 websocket 时发送http://sockjs.pusher.com/pusher请求,如果看着别扭(我不喜欢在网站中出现自己控制范围外的事情),可以在初始化Echo的参数中加一个enabledTransports变量,值是['ws', 'wss']。这样pusher就会将http://sockjs.pusher.com/pusher请求替换为你自己的 websocket 连接地址:

        window.Echo = new Echo({
            .
            .
            // 加上这个参数后,就不会再向 http://sockjs.pusher.com/pusher 发送请求了
            enabledTransports: ['ws', 'wss'],
        });

后端添加新的路由

因为前端改变了监听地址。对应的后端,也需要新增一个对应的路由。我们打开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('socekt-status', [
            'socket_id' => $connection->socketId,
            'status' => '在线',
        ], 60);
        $message = PusherMessageFactory::createForMessage($message, $connection, $this->channelManager);

        $message->respond();
    }

    public function onClose(ConnectionInterface $connection)
    {
        $this->channelManager->removeFromAllChannels($connection);

        var_dump('close');
        // 浏览器主动断开连接时,设置当前连接状态为“离线”,不设置过期时间
        \Cache::put('socekt-status', [
            'socket_id' => $connection->socketId,
            'status' => '离线',
        ]);
    }

然后修改下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>/hello页面直接关闭,也就是点标签页上的「叉子」。再刷新下http://<your.host>/login页面:

「手把手」利用laravel-echo主动向服务端发送消息,实现在线状态管理

本文尽量简化过程,减少无关操作,也将实现步骤尽可能详细的展现出来看,希望能对大家有所帮助。

本作品采用《CC 协议》,转载必须注明作者和本文链接
再见了妈妈今晚我就要远航,别为我担心我有快乐和智慧的桨~
本帖由系统于 5个月前 自动加精
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 12

刚好需要这个干货 、感谢老哥解答。

5个月前 评论
小李世界

laravel-echo-server 对应的发送,怎么弄呢?

5个月前 评论
LiamHao (楼主) 5个月前

我之前用的办法是在js里面做关闭事件,在关闭的时候触发接口,看来这个更可靠呀

5个月前 评论
LiamHao (楼主) 5个月前
GGabriel 5个月前
小李世界

用 pusher 不会卡吗?用 Algolia 会卡

5个月前 评论
LiamHao (楼主) 5个月前
小李世界 (作者) 5个月前
LiamHao (楼主) 5个月前
小李世界 (作者) 5个月前

这个不知道可以在api接口使用不,token验证可行?

5个月前 评论
臭鼬 (作者) 5个月前
臭鼬 (作者) 5个月前
fallsleep 3个月前
LiamHao (楼主) 5个月前
臭鼬 (作者) 5个月前
LiamHao (楼主) 5个月前
臭鼬 (作者) 5个月前
臭鼬 (作者) 3个月前
LiamHao (楼主) 3个月前

您好,有demo源吗?

5个月前 评论
LiamHao (楼主) 5个月前
旦架哨 (作者) 5个月前

搞了几天laravel-websockets 感觉这玩意依然是个残疾,满足不了做聊天的需求,用laravel-echo也残缺功能,哎头大

5个月前 评论
91it 5个月前
臭鼬 (作者) 5个月前

mark 了,晚点仔细研究下。正好可能用得上

5个月前 评论

@臭鼬 对于加入房间,离开房间的事件,文档 里已经说了,那就再给你写一下吧:

<script type="text/javascript">

    window.ChattingRoom = new Echo({
    ...
    });

    ChattingRoom.join('chatting-room')
        .here((users) => {
            // 当前用户加入频道时触发,返回当前频道中在线的人员列表
        })
        .joining((user) => {
            // 其他人加入频道时会触发,返回加入人的信息
        })
        .leaving((user) => {
            // 其他人离开频道时会触发,返回离开人的信息
        })
        .listenForWhisper('NewWhisper', (e) => {
            // 服务端将 config/websockets.php 中 enable_client_messages 设为 true 才能开启聊 .whisper() 天功能。 
        })
        .listen('hi_girl', (e) => {
            // 监听服务端推送事件
        });

    $('button').click((event) => {
        // 必须保证后端 config/websockets.php 中 apps.*.enable_client_messages 值为 true
        // 默认配置值为 false 表示禁用悄悄话功能,说是会有安全问题
        // 调用 whisper 方法前必须要指定频道
        ChattingRoom.join('chatting-room')
            .whisper('NewWhisper', {
                name: '{{ Auth::user()->name }}',
                message: $('input').val(),
            });
     });
</script>
5个月前 评论
臭鼬 5个月前

:+1: 谢谢楼主,搞定。

5个月前 评论
LiamHao (楼主) 5个月前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!