在PHP服務中使用Websocket

WebSocket

WebSocket 協議在2008年誕生,2011年成爲國際標準。全部瀏覽器都已經支持了。javascript

它的最大特色就是,服務器能夠主動向客戶端推送信息,客戶端也能夠主動向服務器發送信息,是真正的雙向平等對話,屬於服務器推送技術的一種。php

爲何須要 WebSocket?

需求是:用戶停留頁面 15 分鐘,且沒有任何操做,則彈出登錄窗口,讓用戶從新登錄。html

通常這樣的需求實現多爲長鏈接輪詢,會有瀏覽器的卡頓、服務端消耗及不容易維護等問題。前端

後來發現 websocket 這樣的通信方式,主要有如下優勢:java

  • 創建在 TCP 協議之上,服務器端的實現比較容易。
  • 手時不容易屏蔽,能經過各類 HTTP 代理服務器。
  • 數據格式比較輕量,性能開銷小,通訊高效。
  • 能夠發送文本,也能夠發送二進制數據。
  • 沒有同源限制,客戶端能夠與任意服務器通訊。
  • 協議標識符是ws(若是加密,則爲wss),服務器網址就是 URL。

目標已經選定,那麼如何實現呢?laravel

PHP 已經有很是好用的異步網絡通訊框架 swoole,節省了本身實現 websocket 服務的時間。個人使用的是 laravel 框架,最終選擇了 laravel-swoole 擴展。c++

安裝配置

引入 laravel-swoole 擴展包 wiki 。啓用 websocket.enabled 及其餘相應的配置,經過下面的命名能夠很是方便的管理服務:git

php artisan swoole:http {start|stop|restart|reload|infos}

修改配置文件中的默認 handler 配置爲自定義的類:主要是爲了自定義 websocket 的生命週期中的一些回調。github

/*
    |--------------------------------------------------------------------------
    | Websocket handler for onOpen and onClose callback
    | Replace this handler if you want to customize your websocket handler
    |--------------------------------------------------------------------------
    */
    'handler' => \App\Listeners\Swoole\WebsocketHandler::class,

    /*
    |--------------------------------------------------------------------------
    | Default frame parser
    | Replace it if you want to customize your websocket payload
    |--------------------------------------------------------------------------
    */
    'parser' => SwooleTW\Http\Websocket\SocketIO\SocketIOParser::class,

當配置完成後,會在 routes 目錄中添加一個名爲 websocket.php 的文件。能夠很是方便像定義 laravel 路由同樣,定義各類事件。例如:web

//Websocket::on('open', function ($websocket, Request $request) {
//    Log::info('websocket','open 111 +' . $websocket->getSender());
//});
//
//Websocket::on('connect', function ($websocket, Request $request) {
//    Log::info('websocket','Connected ++ 222' . $websocket->getSender());
//    // called while socket on connect.
//});
//
//Websocket::on('disconnect', function ($websocket) {
//    Log::info('websocket','Disconnected ++ 333' . $websocket->getSender());
//    // called while socket on disconnect
//});

// 在 UserController 中的 checkLogin 方法上會帶有$websocket, $data這兩個參數。
Websocket::on('loginCheck', "App\Http\Controllers\Api\UserController@checkLogin");

Websocket::on('logout', "App\Http\Controllers\Api\UserController@sendLogout");

使用

控制器:

public function checkLogin($websocket, $data)
{
    if (empty($data['holding'])) {
        $websocket->emit('message', ['code' => self::HTTP_UNPROCESSABLE_ENTITY, 'message' => "參數錯誤"]);

        return false;
    }
    $flag = true;
    $step = 1;
    while ($flag) {
        $step++;
        if ( ! $this->validateLoginStatus($data['holding'])) {
            $websocket->emit('message', ['code' => self::HTTP_UNAUTHORIZED, 'message' => "登錄超時"]);
            unset($step);
            $flag = false;
        }else {
            if($step === 1) {
                $websocket->emit('message', ['code' => self::HTTP_OK, 'message' => "success"]);
            }
        }
        sleep(1);
    }
}

前端調用

這裏必定要注意數據包的結構,以前就踩了比較多的坑,API docs 才找到正確的結構:

var websocket = new WebSocket("ws://127.0.0.1:1215");
    websocket.onopen = function (evt) {
        console.log("已鏈接websocket服務器");
        // 這裏比較關鍵,通道創建後,能夠進很是方便的進行輪詢
        setInterval(function() {
           if (websocket.bufferedAmount == 0)
              var data = {"holding": "eyJLQNDqj0y473pCJ6zjMTUyOTk5NzU1MgnVMQ==$d84XkeMCv7umajhMRiU"};
              websocket.send(encodeMessage('loginCheck', data));
        }, 50);
    };
    // 監聽消息體
    websocket.onmessage = function (evt) {
        console.log(decodeMessage(evt.data))
    };
    // 監聽關閉消息
    websocket.onclose = function (evt) {
        console.log("websocket close");
    };
    //監聽鏈接錯誤信息
    websocket.onerror = function (evt) {
        console.log(evt);
    };

    function decodeMessage(str) {
        return JSON.parse(str.substring(2))[1] || [];
    }

    function encodeMessage(event, data) {
        return JSON.stringify([
            event,
            data
        ])
    }

Swoole擴展安裝

由於 swoole 的安裝依賴 phpsockets 模塊的開啓。

  • 安裝 swoole

中間報錯,須要安裝如下依賴:

yum -y install gcc postgresql-devel gcc-c++

下載 swoole 擴展源碼,安裝 安裝步驟 進行安裝便可。

性能監控

查看當前

$ netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'

TIME_WAIT 814
CLOSE_WAIT 1
FIN_WAIT1 1
ESTABLISHED 634
SYN_RECV 2
LAST_ACK 1

經常使用的三個狀態是:ESTABLISHED 表示正在通訊,TIME_WAIT 表示主動關閉,CLOSE_WAIT 表示被動關閉。

clipboard.png

刪除進程

查看進程數

$ ps -eaf |grep "swoole" | grep -v "grep"| awk '{print $2}'|wc -l

批量刪除進程:

$ ps -eaf |grep "swoole" | grep -v "grep"| awk '{print $2}'|xargs kill -9

重啓服務。

參考文章

阮一峯WebSocket教程

相關文章
相關標籤/搜索