php實現即時通訊

來源:互聯網
上載者:User

如果英文ok的話,可以直接看這篇文章

實現即時通訊一般有兩種方式:
socket或comet。socket是比較好的解決方案,問題在於不是所有的瀏覽器都相容,伺服器端實現起來也稍微有點麻煩。相比之下,comet(基於HTTP長串連的"伺服器推")實現起來更加方便,而且相容所有的瀏覽器。所以這次就來說說comet的php實現。

comet也有好幾種實現方式,如iframe, http long request,二者的區別可以參考這篇文章。本文主要探討http long request實現即時通訊。

先說說http長連結是怎麼回事,通俗點講就是伺服器不是一收到請求就直接吐資料,而是在那憋啊憋,一直憋到憋不住了,才告訴你執行結果。

<?php$count = 10;for($i=0; $i<$count; $i++){// do something ...sleep(2);}echo '憋死我了';

至於憋多長時間,就看具體應用了,如果憋太久的話,伺服器資源的佔用也會是個問題。

現在我們就要通過這種方法來實現即時通訊(其實是准即時),先說一下原理:

1. 用戶端發起一個ajax長連結查詢,然後服務端就開始執行代碼,主要是檢查某個檔案是否被更新,如果沒有,睡一會(sleep),醒來接著檢查
2. 如果用戶端又發起了一個查詢連結(正常請求),服務端收到後,處理請求,處理完畢後更新某個特定檔案的modify time
3. 這時第一次ajax查詢的後台代碼還在執行,發現某個檔案被更新,說明來了新請求,輸出對應的結果
4. 第一次ajax查詢的callback被觸發,更新頁面,然後再發起一個新的ajax長連結

 

實戰用戶端
<html>    <head>        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">        <title>Comet Test</title>    </head>    <body>        <p><a class='customAlert' href="#">publish customAlert</a></p>        <p><a class='customAlert2' href="#">publish customAlert2</a></p>        <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.5/jquery.min.js" type="text/javascript"></script>        <script src="NovComet.js" type="text/javascript"></script>        <script type="text/javascript">NovComet.subscribe('customAlert', function(data){console.log('customAlert');//console.log(data);}).subscribe('customAlert2', function(data){console.log('customAlert2');//console.log(data);});$(document).ready(function() {$("a.customAlert").click(function(event) {NovComet.publish('customAlert');});$("a.customAlert2").click(function(event) {NovComet.publish('customAlert2');});NovComet.run();});        </script>    </body></html>

這段代碼說的是,有個NovComet的Object,註冊了customAlert和customAlert2事件,當頁面載入完成時,對兩個按鈕又加了監聽事件,當點擊時NovComet會發布customAlert或customAlert2事件,然後NovComet執行了run方法。

NovComet
//NovComet.jsNovComet = {    sleepTime: 1000,    _subscribed: {},    _timeout: undefined,    _baseurl: "comet.php",    _args: '',    _urlParam: 'subscribed',    subscribe: function(id, callback) {        NovComet._subscribed[id] = {            cbk: callback,            timestamp: NovComet._getCurrentTimestamp()        };        return NovComet;    },    _refresh: function() {        NovComet._timeout = setTimeout(function() {            NovComet.run()        }, NovComet.sleepTime);    },    init: function(baseurl) {        if (baseurl!=undefined) {            NovComet._baseurl = baseurl;        }    },    _getCurrentTimestamp: function() {        return Math.round(new Date().getTime() / 1000);    },    run: function() {        var cometCheckUrl = NovComet._baseurl + '?' + NovComet._args;        for (var id in NovComet._subscribed) {            var currentTimestamp = NovComet._subscribed[id]['timestamp'];            cometCheckUrl += '&' + NovComet._urlParam+ '[' + id + ']=' +               currentTimestamp;        }        cometCheckUrl += '&' + NovComet._getCurrentTimestamp();        $.getJSON(cometCheckUrl, function(data){            switch(data.s) {                case 0: // sin cambios                    NovComet._refresh();                    break;                case 1: // trigger                    for (var id in data['k']) {                        NovComet._subscribed[id]['timestamp'] = data['k'][id];                        NovComet._subscribed[id].cbk(data.k);                    }                    NovComet._refresh();                    break;            }        });    },    publish: function(id) {        var cometPublishUrl = NovComet._baseurl + '?' + NovComet._args;        cometPublishUrl += '&publish=' + id;        $.getJSON(cometPublishUrl);    }};

NovComet的run方法首先把之前註冊的幾個事件串成一個url,並且很狡猾地使用了"[]",類似:?subscribed[customAlert]=1300016814&subscribed[customAlert2]=1300016814&1300016825,這樣php收到後,就會得到$_GET[subscribed]數組,最後那個時間戳記是為了避免請求被緩衝。如果收到後台傳過來的資料data的s值為0,說明什麼也沒發生,隔1秒後繼續執行;如果data.s的值為1,說明NovComet的publish事件被觸發,則調用對應的callback。publish方法執行後,會構造一個類似:
?publish=customAlert 這樣一個url發送到後台。後台檢測到pubish參數,則擷取該參數的值,並更新對應檔案的mtime。

服務端
<?php// comet.phpinclude('NovComet.php');$comet = new NovComet();$publish = filter_input(INPUT_GET, 'publish', FILTER_SANITIZE_STRING);if ($publish != '') {    echo $comet->publish($publish);} else {    foreach (filter_var_array($_GET['subscribed'], FILTER_SANITIZE_NUMBER_INT) as $key => $value) {        $comet->setVar($key, $value);    }    echo $comet->run();}

如果收到publish參數,直接輸出,否則執行run方法,至於run是怎麼回事,且看下碼。

<?php// NovComet.phpclass NovComet {    const COMET_OK = 0;    const COMET_CHANGED = 1;    private $_tries;    private $_var;    private $_sleep;    private $_ids = array();    private $_callback = null;    public function  __construct($tries = 20, $sleep = 2)    {        $this->_tries = $tries;        $this->_sleep = $sleep;    }    public function setVar($key, $value)    {        $this->_vars[$key] = $value;    }    public function setTries($tries)    {        $this->_tries = $tries;    }    public function setSleepTime($sleep)    {        $this->_sleep = $sleep;    }    public function setCallbackCheck($callback)    {        $this->_callback = $callback;    }    const DEFAULT_COMET_PATH = "/dev/shm/%s.comet";    public function run() {        if (is_null($this->_callback)) {            $defaultCometPAth = self::DEFAULT_COMET_PATH;            $callback = function($id) use ($defaultCometPAth) {                $cometFile = sprintf($defaultCometPAth, $id);                return (is_file($cometFile)) ? filemtime($cometFile) : 0;            };        } else {            $callback = $this->_callback;        }        for ($i = 0; $i < $this->_tries; $i++) {            foreach ($this->_vars as $id => $timestamp) {                if ((integer) $timestamp == 0) {                    $timestamp = time();                }                $fileTimestamp = $callback($id);                if ($fileTimestamp > $timestamp) {                    $out[$id] = $fileTimestamp;                }                clearstatcache();            }            if (count($out) > 0) {                return json_encode(array('s' => self::COMET_CHANGED, 'k' => $out));            }            sleep($this->_sleep);        }        return json_encode(array('s' => self::COMET_OK));    }    public function publish($id)    {        return json_encode(touch(sprintf(self::DEFAULT_COMET_PATH, $id)));    }}

 

可以看到publish時,建立了一個以$id命名的檔案。run時,如果發現該$id檔案存在,且時間戳記大於之前儲存的該$id對應的時間戳記(通過setVar設定的),說明$id事件被觸發,處理完後把$id放到$out數組中,然後判斷一下$out數組是否為空白,如果不為空白,則輸出一段json。

如果一段時間內都沒有觸發事件(for迴圈執行完畢),也輸出一段json,告訴前端執行完了,但是沒有任何新情況。

說明
  • 可以在用戶端監聽/發布多個事件
  • 監聽事件時,可以傳一個callback,這樣收到資料時就會出發該callback
  • 當監聽事件時,會傳一個時間戳記
  • 當事件被publish時,會向後台發一個請求,並傳遞一個新的時間戳記
  • 服務端不會一直執行,如果指定時間內,沒有任何請求被觸發,則結束運行
  • 用戶端會重複上述過程(setTimeout & NovComet.run())

最後來一張圖說明一下

1. 運行一段時間後,沒有收到任何publish事件,服務端結束執行
2. 服務端返回一段json
3. 用戶端觸發了一個事件,服務端收到事件,返回一段新的json
4. callback被觸發
5. 用戶端進入下一次的ajax長連結查詢

 

轉自:http://blog.leezhong.com/tech/2011/03/21/php-comet.html

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.