PHP服務端推送技術Long Polling

服務端推送技術應用越來越普遍,應用範圍也越來越寬廣,技術解決方案也越來越成熟且豐富。很多SNS網站的chat功能就有用到了Long Polling技術。比如fackebook, kaixin001。

Long Polling原理其實很簡單,也很討巧。與Polling相比,Long Polling客戶端也許不會馬上收到來自服務端的響應,需要等待一些時間(直到有新消息,或者連接timeout了等等)。同樣的,客戶端也不再需要定時向服務發送請求了,而是直到收到服務端響應之後,或者連接丟失之後,客戶端接着馬上請求客戶端。這裏,我打個比方,傳統的Polling一般是由C向S詢問:”有我的信件嗎?”。S接到詢問之後,會立即查詢,並且把查詢結果告訴C,不管有沒有C的信件,要碼回覆:”嗯,你有X封信。”,要碼回覆:”沒,沒有你的信”.而Long Polling更像是這樣,C向S發出詢問:”有我的信件嗎?”,S開始查詢,如果有則回覆C:”嗯,有你x封信”。如果沒有,則不作任何回覆,而是讓C等着,自己一遍一遍地查詢是否有訂閱者的信。換句話說:當S收到C的查詢請求之後,Polling則只查詢一次,並且把查詢結果告訴C;而Long Polling收到請求之後,則會一遍一遍地查詢,直到有消息纔會響應C,不然一直hold Client。

Long Polling相較傳統的Polling而言,最大的實惠在於:減少了請求次數。舉個例子,假定一個用戶每2小時內,有可能收到2條新消息。如果採用傳通的Polling方式,每30秒發向服務端發送一次查詢請求的話。則在這2小時內,服務器需要處理240(60*60*2/30)次請求,其中至少有238次請求是沒有實際意義的。試想,如果是10000的併發量的話,這種浪費是很驚人的。相較而方,Long Polling沒有那麼浪費服務器資源來處理這些沒有實際意義的請求。

Polling

傳統的Polling實現方式比較單一,由客戶端javascript腳本定時發送http請求。服務端腳本如下:

<?php
header("Expires: Sun, 19 Nov 1978 05:00:00 GMT");
header("Last-Modified: ". gmdate("D, d M Y H:i:s") ." GMT");
header("Cache-Control: store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", FALSE);
 
$msg = get_msg();
if ($msg) {
 echo $msg;
} else {
 echo '0';
}
?>

上面是一個傳統polling簡單的服務端腳本。很簡單,收到客戶端請求後,服務端馬上執行腳本查詢,並且立即響應客戶端。客戶端等待的時間很短,客戶端唯一要做的事情就是定時向服務端發出查詢請求。下面是請求時,通過tcpdump抓到的包:

1>17:49:03.533760 IP 192.168.0.98.4383 > devhome.http: S 3235664319:3235664319(0) win 65535 <mss 1460,nop,nop,sackOK>
2>17:49:03.534336 IP devhome.http > 192.168.0.98.4383: S 2018732723:2018732723(0) ack 3235664320 win 5840 <mss 1460,nop,nop,sackOK>
3>17:49:03.533841 IP 192.168.0.98.4383 > devhome.http: . ack 1 win 65535
4>17:49:03.534404 IP 192.168.0.98.4383 > devhome.http: P 1:781(780) ack 1 win 65535
5>17:49:03.534416 IP devhome.http > 192.168.0.98.4383: . ack 781 win 7020
6>17:49:03.535033 IP devhome.http > 192.168.0.98.4383: P 1:369(368) ack 781 win 7020
7>17:49:03.535110 IP devhome.http > 192.168.0.98.4383: F 369:369(0) ack 781 win 7020
8>17:49:03.535263 IP 192.168.0.98.4383 > devhome.http: . ack 370 win 65167
9>17:49:03.536105 IP 192.168.0.98.4383 > devhome.http: F 781:781(0) ack 370 win 65167
10>17:49:03.536111 IP devhome.http > 192.168.0.98.4383: . ack 782 win 7020

第1、2、3行,TCP三次握手,建立連接。
第4行,由192.168.0.98向服務端devhome發送httpd請求。
第5行,由服務端devhome確認收到了來自客戶端192.168.0.98的http請求。
第6行,服務器響devhom響應客戶端192.168.0.98剛纔發的httpd請求。注意:特別注意一下第一列時間截,http請求與http響應的時間間隔很短,才0.001s
第7、8、9、10共4行,TCP四次揮手,斷開連接。由服務端主動斷開連接。

Long Polling

Long Polling較之Polling稍微有些不一樣,Long Polling持續執行,以此延遲對客戶端的響應。請查看代碼:

<?php
header("Expires: Sun, 19 Nov 1978 05:00:00 GMT");
header("Last-Modified: ". gmdate("D, d M Y H:i:s") ." GMT");
header("Cache-Control: store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", FALSE);
//在$timeout之後,關閉連接,並且要求客戶3秒後重新請求
for ($i = 0, $timeout = 60; $i < $timeout; $i++ ) {
 $msg = get_msg();
 if ($msg) {
     echo json_encode(array('t' => 'info' , 'c' => $msg));
     flush();
     exit(0);
 }  
 usleep(3000000);
}
echo json_encode(array('t' => 'refresh', 'c' => 3000));
flush();
?>

上面是Long Polling服務端代碼。語意也很明瞭,如果有$msg,則會馬上響應客戶端請求,並且關閉該TCP連接。如果在$timeout之內,沒有$msg,則會讓客戶端一直保持該TCP連接,不中斷(關閉)。直到超過了$timeout(具體時間主要取決於$timeout * $usleep_time),服務端會要求客戶端重新請求(重新建立TCP連接),同時關閉當前TCP連接。下面是通過 tcpdump抓到的包:

1>18:39:46.449563 IP 192.168.0.98.4407 > devhome.http: S 174149200:174149200(0) win 65535 <mss 1460,nop,nop,sackOK>
2>18:39:46.449587 IP devhome.http > 192.168.0.98.4407: S 938669730:938669730(0) ack 174149201 win 5840 <mss 1460,nop,nop,sackOK>
3>18:39:46.449692 IP 192.168.0.98.4407 > devhome.http: . ack 1 win 65535
4>18:39:46.450308 IP 192.168.0.98.4407 > devhome.http: P 1:793(792) ack 1 win 65535
5>18:39:46.450320 IP devhome.http > 192.168.0.98.4407: . ack 793 win 7128
6>18:42:46.521749 IP devhome.http > 192.168.0.98.4407: P 1:377(376) ack 793 win 7128
7>18:42:46.521825 IP devhome.http > 192.168.0.98.4407: P 377:412(35) ack 793 win 7128
8>18:42:46.521859 IP devhome.http > 192.168.0.98.4407: F 412:412(0) ack 793 win 7128
9>18:42:46.521997 IP 192.168.0.98.4407 > devhome.http: . ack 412 win 65124
10>18:42:46.522021 IP 192.168.0.98.4407 > devhome.http: . ack 413 win 65124
11>18:42:46.522965 IP 192.168.0.98.4407 > devhome.http: F 793:793(0) ack 413 win 65124
12>18:42:46.522970 IP devhome.http > 192.168.0.98.4407: . ack 794 win 7128

第1、2、3行,TCP三次握手,建立連接。
第4行,由192.168.0.98向服務端192.168.0.6發送http請求。
第5行,由192.168.0.6確認收到192.168.0.98剛剛發送的請求。
第6、7行,服務器host_6響應3分鐘前客戶端192.168.0.98發出的http請求。注意,第一列的時間截,第5行與第6行之差爲3分鐘。這與服務端腳本,客戶端監控是相呼應的。
最後4行,TCP四次揮手,斷開連接,同樣由服務端devhome發起。

由上圖可見(httpwatch繪製),正好驗證了,響應客戶端host_98的http請求,在3分鐘之後。這也說明了,Long Polling與Polling的區別在於,客戶端有可能需要等待更長時間才能收到服務端的響應。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章