Laravel 中使用 swoole 項目實戰開發案例二 (後端主動分場景給界面推送消息)

八重櫻:Laravel 中使用 swoole 項目實戰開發案例一 (創建 swoole 和前端通訊)​zhuanlan.zhihu.com!(https://zhstatic.zhihu.com/assets/zhihu/editor/zhihu-card-default.svg)php

個人官方羣點擊此處。獲取更多的swoole學習資料以及視頻源碼筆記。html

需求分析

咱們假設有一個需求,我在後端點擊按鈕 1,首頁彈出 「後端觸發了按鈕 1」。後端點了按鈕 2,列表頁彈出 「後端觸發了按鈕 2」。作到根據不一樣場景推送到不一樣頁面。前端

代碼思路

  • Swoole fd
    客戶端瀏覽器打開或者刷新界面,在 swoole 服務會生成一個進程句柄fd,每次瀏覽器頁面有打開連接 websocket 的 js 代碼,便會生成,每次刷新的時候,會關閉以前打開的fd,從新生成一個新的,關閉界面的時候會生成一個新的。swoole 的fd生成規則是從 1 開始遞增。
  • Redis Hash 存儲 fd
    咱們創建一個 key 爲swoole:fdsredis 哈希類型數據,fd爲 hash 的字段,每一個字段的值咱們存儲前端 websocket 請求的 url 參數信息 (根據業務複雜度本身靈活變通,我在項目中會在 url 帶上 sessionId)。每次連接打開 swoole 服務的時候咱們存儲其信息,每次關閉頁面時候咱們清除其字段。在 redis 存儲以下

  • 觸發分場景推送
    在界面上當進行了觸發操做的時候,經過後臺 curl 請求 swoole http 服務,swoole http 服務根據你向我傳遞的參數分發給對應的邏輯處理。如 curl 請求127.0.0.1:9502page=back&func=pushHomeLogic&token=123456咱們能夠根據傳入的func參數,在後臺分發給對應邏輯處理。如分發給pushHomeLogic方法。在其裏面實現本身的邏輯。爲防止過多的ifelse以及foreach 操做,咱們採用的是閉包,call_user_func等方法實現以下
public function onRequest($request,$response)
{
    if ($this->checkAccess("", $request)) {
        $param = $request->get;
        // 分發處理請求邏輯
        if (isset($param['func'])) {
            if (method_exists($this,$param['func'])) {
                call_user_func([$this,$param['func']],$request);
            }
        }
    }
}// 往首頁推送邏輯處理
public function pushHomeLogic($request)
{
    $callback = function (array $aContent,int $fd,SwooleDemo $oSwoole)use($request) {
        if ($aContent && $aContent['page'] == "home") {
            $aRes['message'] = "後端按了按鈕1";
            $aRes['code'] = "200";
            $oSwoole::$server->push($fd,xss_json($aRes));
        }
    };
    $this->eachFdLogic($callback);
}

完整代碼

swool 腳本代碼邏輯jquery

<?php

namespace App\Console\Commands;

use Closure;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Redis;

class SwooleDemo extends Command
{
    // 命令名稱
    protected $signature = 'swoole:demo';
    // 命令說明
    protected $description = '這是關於swoole websocket的一個測試demo';
    // swoole websocket服務
    private static $server = null;

    public function __construct()
    {
        parent::__construct();
    }

    // 入口
    public function handle()
    {
        $this->redis = Redis::connection('websocket');
        $server = self::getWebSocketServer();
        $server->on('open',[$this,'onOpen']);
        $server->on('message', [$this, 'onMessage']);
        $server->on('close', [$this, 'onClose']);
        $server->on('request', [$this, 'onRequest']);
        $this->line("swoole服務啓動成功 ...");
        $server->start();
    }

    // 獲取服務
    public static function getWebSocketServer()
    {
        if (!(self::$server instanceof \swoole_websocket_server)) {
            self::setWebSocketServer();
        }
        return self::$server;
    }
    // 服務處始設置
    protected static function setWebSocketServer():void
    {
        self::$server  = new \swoole_websocket_server("0.0.0.0", 9502);
        self::$server->set([
            'worker_num' => 1,
            'heartbeat_check_interval' => 60,    // 60秒檢測一次
            'heartbeat_idle_time' => 121,        // 121秒沒活動的
        ]);
    }

    // 打開swoole websocket服務回調代碼
    public function onOpen($server, $request)
    {
        if ($this->checkAccess($server, $request)) {
            self::$server->push($request->fd,xss_json(["code"=>200,"message"=>"打開swoole服務成功"]));
        }
    }
    // 給swoole websocket 發送消息回調代碼
    public function onMessage($server, $frame)
    {

    }
    // http請求swoole websocket 回調代碼
    public function onRequest($request,$response)
    {
        if ($this->checkAccess("", $request)) {
            $param = $request->get;
            // 分發處理請求邏輯
            if (isset($param['func'])) {
                if (method_exists($this,$param['func'])) {
                    call_user_func([$this,$param['func']],$request);
                }
            }
        }
    }

    // websocket 關閉回調代碼
    public function onClose($serv,$fd)
    {
        $this->redis->hdel('swoole:fds', $fd);
        $this->line("客戶端 {$fd} 關閉");
    }

    // 校驗客戶端鏈接的合法性,無效的鏈接不容許鏈接
    public function checkAccess($server, $request):bool
    {
        $bRes = true;
        if (!isset($request->get) || !isset($request->get['token'])) {
            self::$server->close($request->fd);
            $this->line("接口驗證字段不全");
            $bRes = false;
        } else if ($request->get['token'] != 123456) {
            $this->line("接口驗證錯誤");
            $bRes = false;
        }
        $this->storeUrlParamToRedis($request);
        return $bRes;
    }

    // 將每一個界面打開websocket的url 存儲起來
    public function storeUrlParamToRedis($request):void
    {
        // 存儲請求url帶的信息
        $sContent = json_encode(
            [
                'page' => $request->get['page'],
                'fd' => $request->fd,
            ], true);
        $this->redis->hset("swoole:fds", $request->fd, $sContent);
    }

    /**
     * @param $request
     * @see 循環邏輯處理
     */
    public function eachFdLogic(Closure $callback = null)
    {
        foreach (self::$server->connections as $fd) {
            if (self::$server->isEstablished($fd)) {
                $aContent = json_decode($this->redis->hget("swoole:fds",$fd),true);
                $callback($aContent,$fd,$this);
            } else {
                $this->redis->hdel("swoole:fds",$fd);
            }
        }
    }
    // 往首頁推送邏輯處理
    public function pushHomeLogic($request)
    {
        $callback = function (array $aContent,int $fd,SwooleDemo $oSwoole)use($request) {
            if ($aContent && $aContent['page'] == "home") {
                $aRes['message'] = "後端按了按鈕1";
                $aRes['code'] = "200";
                $oSwoole::$server->push($fd,xss_json($aRes));
            }
        };
        $this->eachFdLogic($callback);
    }
    // 往列表頁推送邏輯處理
    public function pushListLogic($request)
    {
        $callback = function (array $aContent,int $fd,SwooleDemo $oSwoole)use($request) {
            if ($aContent && $aContent['page'] == "list") {
                $aRes['message'] = "後端按了按鈕2";
                $aRes['code'] = "200";
                $oSwoole::$server->push($fd,xss_json($aRes));
            }
        };
        $this->eachFdLogic($callback);
    }

    // 啓動websocket服務
    public function start()
    {
        self::$server->start();
    }
}

控制器代碼web

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Redis;
class TestController extends Controller
{
    // 首頁
    public function home()
    {
        return view("home");
    }
    // 列表
    public function list()
    {
        return view("list");
    }
    // 後端控制
    public function back()
    {
        if (request()->method() == 'POST') {
           $this->curl_get($this->getUrl());
           return json_encode(['code'=>200,"message"=>"成功"]);
        } else {
            return view("back");
        }

    }
    // 獲取要請求swoole websocet服務地址
    public function getUrl():string
    {
        // 域名 端口 請求swoole服務的方法
        $sBase = request()->server('HTTP_HOST');
        $iPort = 9502;
        $sFunc = request()->post('func');
        $sPage = "back";
        return $sBase.":".$iPort."?func=".$sFunc."&token=123456&page=".$sPage;
    }
    // curl 推送
    public function curl_get(string $url):string
    {
        $ch_curl = curl_init();
        curl_setopt ($ch_curl, CURLOPT_TIMEOUT_MS, 3000);
        curl_setopt($ch_curl, CURLOPT_SSL_VERIFYPEER, 0);
        curl_setopt ($ch_curl, CURLOPT_HEADER,false);
        curl_setopt($ch_curl, CURLOPT_HTTPGET, 1);
        curl_setopt($ch_curl, CURLOPT_RETURNTRANSFER,true);
        curl_setopt ($ch_curl, CURLOPT_URL,$url);
        $str  = curl_exec($ch_curl);
        curl_close($ch_curl);
        return $str;
    }
}

頁面 js 代碼

  • 後端控制頁
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>後端界面</title>
<meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
</head>
<body>
<button class="push" data-func="pushHomeLogic">按鈕1</button>
<button class="push" data-func="pushListLogic">按鈕2</button>
</body>
<script src="{{ asset("/vendor/tw/global/jQuery/jquery-2.2.3.min.js")}} "></script>
<script>
$(function () {
    $(".push").on('click',function(){
        var func = $(this).attr('data-func').trim();
        ajaxGet(func)
    })
    function ajaxGet(func) {
        url = "{{route('back')}}";
        token = "{{csrf_token()}}";
        $.ajax({
            url: url,
            type: 'post',
            dataType: "json",
            data:{func:func,_token:token},
            error: function (data) {
                alert("服務器繁忙, 請聯繫管理員!");
                return;
            },
            success: function (result) {

            },
        })
    }

})
</script>
</html>

首頁ajax

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>swoole首頁</title>
<meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
</head>
<body>
<h1>這是首頁</h1>
</body>
<script>
var ws;//websocket實例
var lockReconnect = false;//避免重複鏈接
var wsUrl = 'ws://{{$_SERVER["HTTP_HOST"]}}:9502?page=home&token=123456';

function initEventHandle() {
    ws.onclose = function () {
        reconnect(wsUrl);
    };
    ws.onerror = function () {
        reconnect(wsUrl);
    };
    ws.onopen = function () {
        //心跳檢測重置
        heartCheck.reset().start();
    };
    ws.onmessage = function (event) {
        //若是獲取到消息,心跳檢測重置
        //拿到任何消息都說明當前鏈接是正常的
        var data = JSON.parse(event.data);
        if (data.code == 200) {
            console.log(data.message)
        }
        heartCheck.reset().start();
    }
}
createWebSocket(wsUrl);
/**
 * 建立連接
 * @param url
 */
function createWebSocket(url) {
    try {
        ws = new WebSocket(url);
        initEventHandle();
    } catch (e) {
        reconnect(url);
    }
}
function reconnect(url) {
    if(lockReconnect) return;
    lockReconnect = true;
    //沒鏈接上會一直重連,設置延遲避免請求過多
    setTimeout(function () {
        createWebSocket(url);
        lockReconnect = false;
    }, 2000);
}
//心跳檢測
var heartCheck = {
    timeout: 60000,//60秒
    timeoutObj: null,
    serverTimeoutObj: null,
    reset: function(){
        clearTimeout(this.timeoutObj);
        clearTimeout(this.serverTimeoutObj);
        return this;
    },
    start: function(){
        var self = this;
        this.timeoutObj = setTimeout(function(){
            //這裏發送一個心跳,後端收到後,返回一個心跳消息,
            //onmessage拿到返回的心跳就說明鏈接正常
            ws.send("heartbeat");
            self.serverTimeoutObj = setTimeout(function(){//若是超過必定時間還沒重置,說明後端主動斷開了
                ws.close();//若是onclose會執行reconnect,咱們執行ws.close()就好了.若是直接執行reconnect 會觸發onclose致使重連兩次
            }, self.timeout);
        }, this.timeout);
    },
    header:function(url) {
        window.location.href=url
    }

}
</script>
</html>

列表頁面

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>swoole列表頁</title>
<meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
</head>
<body>
<h1>swoole列表頁</h1>
</body>
<script>
var ws;//websocket實例
var lockReconnect = false;//避免重複鏈接
var wsUrl = 'ws://{{$_SERVER["HTTP_HOST"]}}:9502?page=list&token=123456';

function initEventHandle() {
    ws.onclose = function () {
        reconnect(wsUrl);
    };
    ws.onerror = function () {
        reconnect(wsUrl);
    };
    ws.onopen = function () {
        //心跳檢測重置
        heartCheck.reset().start();
    };
    ws.onmessage = function (event) {
        //若是獲取到消息,心跳檢測重置
        //拿到任何消息都說明當前鏈接是正常的
        var data = JSON.parse(event.data);
        if (data.code == 200) {
            console.log(data.message)
        }
        heartCheck.reset().start();
    }
}
createWebSocket(wsUrl);
/**
 * 建立連接
 * @param url
 */
function createWebSocket(url) {
    try {
        ws = new WebSocket(url);
        initEventHandle();
    } catch (e) {
        reconnect(url);
    }
}
function reconnect(url) {
    if(lockReconnect) return;
    lockReconnect = true;
    //沒鏈接上會一直重連,設置延遲避免請求過多
    setTimeout(function () {
        createWebSocket(url);
        lockReconnect = false;
    }, 2000);
}
//心跳檢測
var heartCheck = {
    timeout: 60000,//60秒
    timeoutObj: null,
    serverTimeoutObj: null,
    reset: function(){
        clearTimeout(this.timeoutObj);
        clearTimeout(this.serverTimeoutObj);
        return this;
    },
    start: function(){
        var self = this;
        this.timeoutObj = setTimeout(function(){
            //這裏發送一個心跳,後端收到後,返回一個心跳消息,
            //onmessage拿到返回的心跳就說明鏈接正常
            ws.send("heartbeat");
            self.serverTimeoutObj = setTimeout(function(){//若是超過必定時間還沒重置,說明後端主動斷開了
                ws.close();//若是onclose會執行reconnect,咱們執行ws.close()就好了.若是直接執行reconnect 會觸發onclose致使重連兩次
            }, self.timeout);
        }, this.timeout);
    },
    header:function(url) {
        window.location.href=url
    }

}
</script>
</html>

界面效果

後臺控制點擊按鈕 1

後端界面點擊按鈕 2

但願以上內容能幫助到你們,加入個人官方羣點擊此處。獲取更多的swoole學習資料以及視頻源碼筆記。redis

相關文章
相關標籤/搜索