2011-09-19 35 views
5

這裏聽是我的問題:我有一個腳本(我們稱之爲comet.php)whic由AJAX客戶端腳本requsted,等待變化發生這樣的:如何實現事件在PHP

while(no_changes){ 
    usleep(100000); 
    //check for changes 
} 

我不喜歡這個太多,它不是非常可擴展的,它是(imho)「糟糕的做法」 我想用信號量(?)或反正併發編程 技術來改善這種行爲。你能給我一些關於如何處理這個問題的提示嗎? (我知道,這不是一個簡短的答案,但起點就足夠了。)

編輯LibEvent怎麼辦?

+0

你想達到什麼目的?通常的方法是讓客戶端定期調用腳本來檢查更改。爲什麼你想要在服務器端做到這一點有什麼原因嗎? – JJJ

+3

PHP實際上不是您應該用於COMET的語言。使用節點。js或其他異步工作的東西(例如python龍捲風或greenlet)。通過使用運行在基於線程/進程的web服務器上的PHP,您有很大的開銷。 – ThiefMaster

+0

@Juhana,原因是爲了避免定期檢查變化並制定「反向ajax」解決方案。 @thiefMaster我知道那裏有一些COMET服務器解決方案,但我真的認爲php後端是可能的,只要我用PHP寫企業登錄,最好不要用另一種避免代碼的語言重寫複製。你能解釋一下爲什麼一個PHP彗星後端會造成重頭戲嗎? – ArtoAle

回答

12

您可以使用ZeroMQ解決此問題。

ZeroMQ是一個庫,它提供超級充電插座,用於將事物(線程,進程甚至獨立機器)​​連接在一起。

我假設你試圖將數據從服務器推送到客戶端。那麼,一個好方法就是使用EventSource APIpolyfills available)。

client.js

通過連接到的EventSource stream.php。

var stream = new EventSource('stream.php'); 

stream.addEventListener('debug', function (event) { 
    var data = JSON.parse(event.data); 
    console.log([event.type, data]); 
}); 

stream.addEventListener('message', function (event) { 
    var data = JSON.parse(event.data); 
    console.log([event.type, data]); 
}); 

router.php

這是一個長期運行的進程偵聽傳入的消息,並將其發送出去給任何人聽。

​​

stream.php

連接到網站的每個用戶得到他自己的stream.php。該腳本長時間運行並等待來自路由器的任何消息。一旦獲得新消息,它將以EventSource格式輸出此消息。

<?php 

$context = new ZMQContext(); 

$sock = $context->getSocket(ZMQ::SOCKET_SUB); 
$sock->setSockOpt(ZMQ::SOCKOPT_SUBSCRIBE, ""); 
$sock->connect("tcp://127.0.0.1:5556"); 

set_time_limit(0); 
ini_set('memory_limit', '512M'); 

header("Content-Type: text/event-stream"); 
header("Cache-Control: no-cache"); 

while (true) { 
    $msg = $sock->recv(); 
    $event = json_decode($msg, true); 
    if (isset($event['type'])) { 
     echo "event: {$event['type']}\n"; 
    } 
    $data = json_encode($event['data']); 
    echo "data: $data\n\n"; 
    ob_flush(); 
    flush(); 
} 

要發送消息給所有用戶,只需將它們發送到路由器。路由器然後將該消息分發給所有收聽的流。這裏有一個例子:

<?php 

$context = new ZMQContext(); 

$sock = $context->getSocket(ZMQ::SOCKET_PUSH); 
$sock->connect("tcp://127.0.0.1:5555"); 

$msg = json_encode(array('type' => 'debug', 'data' => array('foo', 'bar', 'baz'))); 
$sock->send($msg); 

$msg = json_encode(array('data' => array('foo', 'bar', 'baz'))); 
$sock->send($msg); 

這應該證明你不需要node.js來做實時編程。 PHP可以很好地處理它。

除此之外,socket.io是一個很好的方法。你可以很容易地通過ZeroMQ連接到你的PHP代碼。

+2

請注意:我最近做了一些基準測試。在某些時候,這並不是很好。我試着在幾百個連接的客戶端的短時間內發送幾千條消息。如果您使用的是Apache,由於線程之間的上下文切換量會減慢。在某些節點或nginx-push-stream模塊將是更好的工具。儘管如此,它可能與PHP和它的工作。 – igorw

+1

只是喜歡這個簡單易用的例子以及非常具有描述性的例子。 6年後,仍然是最新的簡單答案。謝謝! –

4

這真的取決於你在你的服務器端腳本中做什麼。在某些情況下,你沒有選擇,只能做上面你在做的事情。

但是,如果你正在做的事情涉及到一個函數的阻塞,直到發生什麼事情,你可以使用它來避免賽車而不是usleep()調用(這是恕我直言的一部分,將被視爲「壞習慣「)。

假設您正在等待來自文件或其他類型流的數據。你可以這樣做:

while (($str = fgets($fp)) === FALSE) continue; 
// Handle the event here 

真的,PHP是做這種事情的錯誤語言。但有些情況(我知道是因爲我自己處理過),PHP是唯一的選擇。

+1

實際上,我想通過刪除noop來減少CPU開銷,同時(這就是爲什麼有usleep()調用) – ArtoAle

+2

@ArtoAle你可以'在PHP中沒有某種循環的情況下等待事件 - 它沒有像例如的事件處理程序JavaScript的。但是通過上面所述的方法,你讓'fgets()'調用等待,而不是檢查 - 睡眠 - 檢查 - 睡眠 - 檢查......在你的例子中,如果事件發生在'usleep() 「你必須等待睡眠結束才能處理它。通過使用(阻塞)檢查作爲循環條件,一旦事件發生,'fgets()'立即返回,您可以立即處理它。 – DaveRandom

+0

好的,你是對的。關鍵是,與[系統V IPC](http://www.php.net/manual/en/intro.sem.php)我可以使用阻塞呼叫信號量獲得(這將導致您提出的相同的解決方案) 問題是我真的不知道如何使用信號量產生類似於事件的應用程序行爲 – ArtoAle

2

就像我喜歡PHP一樣,我必須說PHP不是這項任務的最佳選擇。 Node.js對於這類事情來說要好得多,而且它的規模非常好。如果你有JS知識,實現起來也很簡單。

現在,如果您不想浪費CPU週期,則必須創建一個PHP腳本,該腳本將連接到某個端口上的某種服務器。指定的服務器應該偵聽所選端口上的連接,並且每X時間檢查您想要檢查的任何內容(例如,新郵件的數據庫條目),然後將郵件分發給每個連接的客戶端,以使新條目準備就緒。

現在,在PHP中實現這個事件隊列架構並不困難,但是要花5分鐘時間才能完成Node.js和Socket.IO,而不用擔心它是否能在大多數情況下工作瀏覽器。

0

我同意這裏的一致意見,PHP不是這裏最好的解決方案。您真的需要專注於realtime technologies以解決從服務器向客戶端傳輸數據的異步問題。這聽起來像你正在嘗試實現HTTP-Long輪詢,這對於解決跨瀏覽器問題並不是一件容易的事情。 Comet產品的開發人員已經多次解決這個問題,所以我建議你看一下Comet解決方案,或者更好的WebSocket解決方案,併爲舊版瀏覽器提供後備支持。

我建議您讓PHP執行它擅長的Web應用程序功能,併爲您的實時,均勻的異步功能選擇專用解決方案。

0

看你需要一個實時庫。

一個例子是棘輪http://socketo.me/

該負責酒館分在http://socketo.me/docs/wamp

討論這裏的限制是,PHP也需要啓動可變數據的一個部分。

換句話說,這不會讓你在MySQL更新時神奇地訂閱。但是,如果您可以編輯MySQL設置代碼,那麼您可以在其中添加發布部分。