EasySwoole websocket和http服務(wù)并存使用

1. 安裝socket包

composer require easyswoole/socket


2.注冊服務(wù)

public static function mainServerCreate(EventRegister $register)
{
        //創(chuàng)建一個 Dispatcher 配置
        $conf = new \EasySwoole\Socket\Config();

        //設(shè)置Dispatcher為WebSocket 模式
        $conf->setType(\EasySwoole\Socket\Config::WEB_SOCKET);

        try {
            $conf->setParser(new WebSocketParser());//設(shè)置解析器對象
            $dispatch = new Dispatcher($conf);      //創(chuàng)建Dispatcher對象并注入config對象
        } catch (\Exception $e) {
        }

        //給server注冊相關(guān)事件在WebSocket模式下onMessage事件必須注冊 并且交給Dispatcher對象處理
        $register->set(EventRegister::onMessage, function (\swoole_websocket_server $server, \swoole_websocket_frame $frame) use ($dispatch) {
            $dispatch->dispatch($server, $frame->data, $frame);
        });

        $websocketEvent = new WebSocketEvent();
        //自定義握手事件
        $register->set(EventRegister::onHandShake, function (\swoole_http_request $request, \swoole_http_response $response) use ($websocketEvent) {
            $websocketEvent->onHandShake($request, $response);
        });

        //自定義關(guān)閉事件
        $register->set(EventRegister::onClose, function (\swoole_server $server, int $fd, int $reactorId) use ($websocketEvent) {
            $websocketEvent->onClose($server, $fd, $reactorId);
        });
}


3.配置服務(wù)類型

return [
    'SERVER_NAME' => "YZGWCanteenServer",
    'MAIN_SERVER' => [
        'LISTEN_ADDRESS' => '0.0.0.0',
        'PORT'           => 9501,
        'SERVER_TYPE'    => EASYSWOOLE_WEB_SOCKET_SERVER, //可選為 EASYSWOOLE_SERVER  EASYSWOOLE_WEB_SERVER EASYSWOOLE_WEB_SOCKET_SERVER
        'SOCK_TYPE'      => SWOOLE_TCP,
        'RUN_MODEL'      => SWOOLE_PROCESS,
        'SETTING'        => [
            'worker_num'    => 8,
            'reload_async'  => true,
            'max_wait_time' => 3
        ],
        'TASK'           => [
            'workerNum'     => 4,
            'maxRunningNum' => 128,
            'timeout'       => 15
        ]
    ],


4.自定義時事件

<?php
/**
 * Description:this is description
 * User:gan
 * Date:2021/9/23
 * Time:11:42 上午
 */

namespace App\WebSocket;
/**
 * Class WebSocketEvent
 * 此類是 WebSocet 中一些非強制的自定義事件處理
 * @package App\WebSocket
 */
class WebSocketEvent
{
    /**
     * 握手事件
     * 所有客戶端建立連接時觸發(fā)的方法
     * @param \swoole_http_request $request
     * @param \swoole_http_response $response
     * @return bool
     */
    public function onHandShake(\swoole_http_request $request, \swoole_http_response $response)
    {
        $time = time();
        echo "{$request->fd}創(chuàng)建連接事件 : {$time} \n";
        /** 此處自定義握手規(guī)則 返回 false 時中止握手 */
        if (!$this->customHandShake($request, $response)) {
            $response->end();
            return false;
        }

        /** 此處是  RFC規(guī)范中的WebSocket握手驗證過程 必須執(zhí)行 否則無法正確握手 */
        if ($this->secWebsocketAccept($request, $response)) {
            $response->end();
            return true;
        }
        $response->end();
        return false;
    }

    /**
     * 自定義握手事件
     *
     * @param \swoole_http_request $request
     * @param \swoole_http_response $response
     * @return bool
     */
    protected function customHandShake(\swoole_http_request $request, \swoole_http_response $response): bool
    {
        /**
         * 這里可以通過 http request 獲取到相應(yīng)的數(shù)據(jù)
         * 進行自定義驗證后即可
         * (注) 瀏覽器中 JavaScript 并不支持自定義握手請求頭 只能選擇別的方式 如get參數(shù)
         */
        $headers = $request->header;
        $cookie  = $request->cookie;
        // if (如果不滿足我某些自定義的需求條件卷拘,返回false蛔琅,握手失敗) {
        //    return false;
        // }
        return true;
    }


    /**
     * 關(guān)閉事件
     * 所有客戶端關(guān)閉時觸發(fā)的方法
     * @param \swoole_server $server
     * @param int $fd
     * @param int $reactorId
     */
    public function onClose(\swoole_server $server, int $fd, int $reactorId)
    {
        /** @var array $info */
        $info = $server->getClientInfo($fd);


        /**
         * 判斷此fd 是否是一個有效的 websocket 連接
         * 參見 https://wiki.swoole.com/wiki/page/490.html
         */
        if ($info && $info['websocket_status'] === WEBSOCKET_STATUS_FRAME) {
            $time = time();
            echo "{$fd}觸發(fā)關(guān)閉事件 : {$time} \n";
            print_r($info);
            /**
             * 判斷連接是否是 server 主動關(guān)閉
             * 參見 https://wiki.swoole.com/wiki/page/p-event/onClose.html
             */
            if ($reactorId < 0) {
                echo "server close \n";
            }
        }
    }

    /**
     * RFC規(guī)范中的WebSocket握手驗證過程
     * 以下內(nèi)容必須強制使用
     *
     * @param \swoole_http_request $request
     * @param \swoole_http_response $response
     * @return bool
     */
    protected function secWebsocketAccept(\swoole_http_request $request, \swoole_http_response $response): bool
    {
        // ws rfc 規(guī)范中約定的驗證過程
        if (!isset($request->header['sec-websocket-key'])) {
            // 需要 Sec-WebSocket-Key 如果沒有拒絕握手
            var_dump('shake fai1 3');
            return false;
        }

        if (
            0 === preg_match('#^[+/0-9A-Za-z]{21}[AQgw]==$#', $request->header['sec-websocket-key'])
            || 16 !== strlen(base64_decode($request->header['sec-websocket-key']))
        ) {
            //不接受握手
            //var_dump('shake fai1 4');
            return false;
        }

        $key = base64_encode(sha1($request->header['sec-websocket-key'] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));

        $headers = array(
            'Upgrade'               => 'websocket',
            'Connection'            => 'Upgrade',
            'Sec-WebSocket-Accept'  => $key,
            'Sec-WebSocket-Version' => '13',
            'KeepAlive'             => 'off',
        );

        if (isset($request->header['sec-websocket-protocol'])) {
            $headers['Sec-WebSocket-Protocol'] = $request->header['sec-websocket-protocol'];
        }

        // 發(fā)送驗證后的header
        foreach ($headers as $key => $val) {
            $response->header($key, $val);
        }

        // 接受握手 還需要101狀態(tài)碼以切換狀態(tài)
        $response->status(101);
        //var_dump('shake success at fd :' . $request->fd);
        return true;
    }
}


5.自定義解析器

<?php
/**
 * Description:this is description
 * User:ligan
 * Date:2021/9/23
 * Time:11:42 上午
 */

namespace App\WebSocket;

use EasySwoole\Socket\AbstractInterface\ParserInterface;
use EasySwoole\Socket\Bean\Caller;
use EasySwoole\Socket\Bean\Response;
use EasySwoole\Socket\Client\WebSocket as WebSocketClient;

class WebSocketParser implements ParserInterface
{
    /**
     * 解碼上來的消息
     * @param string $raw 消息內(nèi)容
     * @param WebSocketClient $client 當(dāng)前的客戶端
     * @return Caller|null
     */
    public function decode($raw, $client): ?Caller
    {
        $caller = new Caller;
        // 聊天消息 {"controller":"broadcast","action":"roomBroadcast","params":{"content":"111"}}
        if ($raw !== 'PING') {
            $payload = json_decode($raw, true);
            $class   = isset($payload['controller']) ? $payload['controller'] : 'index';
            $action  = isset($payload['action']) ? $payload['action'] : 'actionNotFound';
            $params  = isset($payload['params']) ? (array)$payload['params'] : [];

            $controllerClass = "\\App\\WebSocket\\Controller\\" . ucfirst($class);
            if (!class_exists($controllerClass)) $controllerClass = "\\App\\WebSocket\\Controller\\Index";

            $caller->setClient($caller);
            $caller->setControllerClass($controllerClass);
            $caller->setAction($action);
            $caller->setArgs($params);
        } else {
            // 設(shè)置心跳執(zhí)行的類和方法
            $caller->setControllerClass(\App\WebSocket\Controller\Index::class);
            $caller->setAction('heartbeat');
        }
        return $caller;
    }

    /**
     * 打包下發(fā)的消息
     * @param Response $response 控制器返回的響應(yīng)
     * @param WebSocketClient $client 當(dāng)前的客戶端
     * @return string|null
     */
    public function encode(Response $response, $client): ?string
    {
        return $response->getMessage();
    }
}


6.業(yè)務(wù)代碼

Base.php

<?php
/**
 * Description:this is description
 * User:ligan
 * Date:2021/9/23
 * Time:11:43 上午
 */

namespace App\WebSocket\Controller;

use EasySwoole\Socket\AbstractInterface\Controller;
use EasySwoole\Socket\Client\WebSocket as WebSocketClient;
use Exception;

/**
 * 基礎(chǔ)控制器
 * Class Base
 * @package App\WebSocket\Controller
 */
class Base extends Controller
{
    protected function actionNotFound(?string $actionName)
    {
        echo "您的請求 {$actionName} 不存在 ... \n";
    }

    protected function afterAction(?string $actionName)
    {
        echo "請求之后執(zhí)行 \n";
    }
}

Index.php

<?php
/**
 * Description:this is description
 * User:ligan
 * Date:2021/9/23
 * Time:11:43 上午
 */

namespace App\WebSocket\Controller;

class Index extends Base
{

    /**
     * 心跳執(zhí)行的方法
     * User:ligan
     * Date:2021/9/23
     * Time:11:57 上午
     */
    public function heartbeat()
    {
        $this->response()->setMessage("PONG"); // 推送消息
    }

    public function index()
    {
        $fd   = $this->caller()->getClient()->getFd();                           // 請求用戶的fd
        $data = $this->caller()->getArgs();                                      // 獲取請求參數(shù)
        $this->response()->setMessage('your fd is ' . $fd . json_encode($data)); // 推送消息
    }
}

服務(wù)端業(yè)務(wù)如果想要推送到客戶端缘厢,根據(jù)fd的映射關(guān)系蕾域,然后直接push到指定的fd帚桩。

public function index()
    {
        //TODO: $fd需要查詢map關(guān)系瑟匆,redis等存儲
        $fd     = 1;
        $server = ServerManager::getInstance()->getSwooleServer();
        $fdInfo = $server->getClientInfo($fd);
        if ($fdInfo["websocket_status"]) {
            $server->push($fd, "hello http to websocket!");
        }
    }

如何遍歷全部鏈接

use EasySwoole\EasySwoole\ServerManager;
$server = ServerManager::getInstance()->getSwooleServer();
$start_fd = 0;
while(true)
{
    $conn_list = $server->getClientList($start_fd, 10);
    if ($conn_list===false or count($conn_list) === 0)
    {
        echo "finish\n";
        break;
    }
    $start_fd = end($conn_list);
    var_dump($conn_list);
    foreach($conn_list as $fd)
    {
        $server->send($fd, "broadcast");
    }
}

https://wiki.swoole.com/wiki/page/p-connection_list.html

如何獲取鏈接信息

use EasySwoole\EasySwoole\ServerManager;
$server = ServerManager::getInstance()->getSwooleServer();
$fdinfo = $server->getClientInfo($fd);

https://wiki.swoole.com/wiki/page/p-connection_info.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末处渣,一起剝皮案震驚了整個濱河市芦昔,隨后出現(xiàn)的幾起案子诱贿,更是在濱河造成了極大的恐慌,老刑警劉巖咕缎,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件珠十,死亡現(xiàn)場離奇詭異,居然都是意外死亡凭豪,警方通過查閱死者的電腦和手機宵睦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來墅诡,“玉大人壳嚎,你說我怎么就攤上這事∧┰纾” “怎么了烟馅?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長然磷。 經(jīng)常有香客問我郑趁,道長,這世上最難降的妖魔是什么姿搜? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任寡润,我火速辦了婚禮捆憎,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘梭纹。我一直安慰自己躲惰,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布变抽。 她就那樣靜靜地躺著础拨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪绍载。 梳的紋絲不亂的頭發(fā)上诡宗,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天,我揣著相機與錄音击儡,去河邊找鬼塔沃。 笑死,一個胖子當(dāng)著我的面吹牛阳谍,可吹牛的內(nèi)容都是我干的芳悲。 我是一名探鬼主播,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼边坤,長吁一口氣:“原來是場噩夢啊……” “哼名扛!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起茧痒,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤肮韧,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后旺订,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體弄企,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年区拳,在試婚紗的時候發(fā)現(xiàn)自己被綠了拘领。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡樱调,死狀恐怖约素,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情笆凌,我是刑警寧澤圣猎,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站乞而,受9級特大地震影響送悔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一欠啤、第九天 我趴在偏房一處隱蔽的房頂上張望荚藻。 院中可真熱鬧,春花似錦洁段、人聲如沸应狱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽侦香。三九已至落塑,卻和暖如春纽疟,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背憾赁。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工污朽, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人龙考。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓蟆肆,卻偏偏與公主長得像,于是被迫代替她去往敵國和親晦款。 傳聞我的和親對象是個殘疾皇子炎功,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,927評論 2 355

推薦閱讀更多精彩內(nèi)容