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


上一篇提到了swoole的基本使用,現在通過幾行基本的語句來實現比較復雜的邏輯操作:

先說一下業務場景。我們目前的大多數應用都是以服務端+接口+客戶端的方式去協調工作的,這樣的好處在於不論是處在何種終端的情況下,都可以完美的和服務端兼容。這樣就輕松實現了MVC各個部分的真正解耦。但是提高程序的友好性還是有很多路要走,其中一個大家都會遇到的就是數據實時更新的問題。比如一個用戶在手機上做了添加操作,這時候其他的終端也應該及時顯示數據的變化情況。這個對於手機來說還算好辦,因為現在的各種推送服務完全可以滿足需求,當收到推送更新時,根據推送內容請求相應接口就可以了。但是放到PC上就不是這么回事了。瀏覽器和http協議的特殊性質不得不讓我們另辟蹊徑。

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

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

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的服務端源碼,因為業務稍多所以里面集成了很多業務代碼,而且需要運行起來必須要先安裝swoole的擴展,安裝方式上篇文章會有說明,所以這里僅供參考

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

websocket服務端源碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM