使用php+swoole對client數據實時更新

上一篇提到了swoole的基本使用,如今經過幾行基本的語句來實現比較複雜的邏輯操做:php

先說一下業務場景。咱們目前的大多數應用都是以服務端+接口+客戶端的方式去協調工做的,這樣的好處在於不管是處在何種終端的狀況下,均可以完美的和服務端兼容。這樣就輕鬆實現了MVC各個部分的真正解耦。可是提升程序的友好性仍是有不少路要走,其中一個你們都會遇到的就是數據實時更新的問題。好比一個用戶在手機上作了添加操做,這時候其餘的終端也應該及時顯示數據的變化狀況。這個對於手機來講還算好辦,由於如今的各類推送服務徹底能夠知足需求,當收到推送更新時,根據推送內容請求相應接口就能夠了。可是放到PC上就不是這麼回事了。瀏覽器和http協議的特殊性質不得不讓咱們另闢蹊徑。web

舉一個你們生活中都會遇到的場景:
某個週末你想要和女友去看一場電影,你在本身的pc上找到了某場的場次和座位。正當你要下單支付時,系統提示該座位已經售出,這時你不得不從新回到選座頁面從新挑選。那若是改進一下產品體驗,當有別的用戶已經購買某個座位的時候,瀏覽器會及時將座位標識已售出,這樣你就不用來回操做,節省操做時間。redis

** 針對上述的情景呢,這裏有一個系統間交互的流程圖:**json

http://images2015.cnblogs.com/blog/463190/201601/463190-20160109160700481-684120465.jpg{873*504}

  • 上面一行就是使用系統的當前用戶,他對數據進行了相關操做,同時返回操做結果

下面一行是其餘用戶也正在操做同一條數據。當①(綠色)返回結果時,web服務器會同時告訴當前用戶和redis隊列服務(①黃色),由於websocket服務實時監聽redis,這樣一旦有數據變化,websocket服務會及時感知。此時經過與瀏覽器創建的長鏈接進行通信並告知數據已經更新並從新加載。瀏覽器

另一種方式是當①(綠色)返回結果時不告訴redis隊列,而是直接通信websocket服務數據已經發生變化,再由websocket服務通知瀏覽器客戶端從新加載。由於此種方案比較簡單再加上swoole對一些操做的封裝比較便利,這裏就採用此種辦法服務器

  • 當websocket鏈接被打開時,向socket服務發送當前註冊id,由於只有這樣websocket服務才能定向的爲指定鏈接發送數據
ws.onopen = function(){
    console.log("socket鏈接已打開");
    $.post('/mercha/merchant/find',function(d){ //從web服務端獲取註冊id
        d = $.parseJSON(d);
        ws.send("merchantId_"+d.data.id);
    })
};
  • 當websocket服務收到註冊id時會將當前鏈接的id和由服務端傳來的商戶id對應關係寫入redis
$server->on('Message', function ($serv, $frame) {
    if(stripos($frame->data,'merchantId_') !== false){

        $fd = $redis->get($frame->data);
        if($fd != null){
            $fd = json_decode($fd,true);
        }

        //這裏的$frame->fd是指當前的websocket鏈接id,swoole會經過此id發送給對應的接收方,能夠理解爲手機號碼
        $fd[$frame->fd] = $frame->fd;
        $fd = json_encode($fd);
        $redis->set($frame->data,$fd);
    }
});
  • 由於swoole_websocket_server 繼承自 swoole_http_server ,這樣就能夠經過http的方式和websocket服務進行交互
$server->on('Request', function ($req, $respone) {

    if(isset($req->get['merchantId'])){
        global $server;
        global $redis;

        $merchantId = $req->get['merchantId'];
        $fd = $redis->get("merchantId_".$merchantId);
        if($fd != null){
            $fd = json_decode($fd,true);
        }

        $err = 0;
        if(is_array($fd)){
            foreach($fd as $f){
                $res = @$server->push($f, "refresh");
                if($res === false){
                    unset($fd[$f]);
                    $err++;
                }
            }

            if($err){
                $fd = json_encode($fd);
                $redis->set("merchantId_".$merchantId,$fd);
            }
        }
    }

    $respone->end("success");
});

** 須要強調的一點是監聽http請求的server並不具有push方法,因此這裏經過全局變量的方式使用websocket的$server來向客戶端發送數據 **websocket

以上就是解決問題的大概思路了,文章最後會附上websocket的服務端源碼,由於業務稍多因此裏面集成了不少業務代碼,並且須要運行起來必需要先安裝swoole的擴展,安裝方式上篇文章會有說明,因此這裏僅供參考swoole

對於圖中另外一種使用redis的方式也是很好的,以前採用了redis發佈訂閱的模式基本能夠達到想要的效果。可是中間遇到一個問題redis的subscribe運行幾十秒後,就會拋出一個RedisException。緣由處在於,php的redis庫使用的subscribe是使用PHP內置的socket,而php.ini默認是設置了socket的超時時間是60秒,因此你們只要找到default_socket_timeout 這個配置項,把時間改長點就能夠了。或者在代碼中加入ini_set('default_socket_timeout', -1);socket

websocket服務端源碼post

相關文章
相關標籤/搜索