1. 程式人生 > >jsonp長輪詢方式下對連線的管理

jsonp長輪詢方式下對連線的管理

隨著Web網際網路產品即時性要求的提高,瀏覽器端“服務端推”技術越來越被重視和應用起來。新浪微博,人人網,webqq,阿里旺旺網頁版,趕集網im,58同城im等等都在使用服務端推的技術。在websocket出現之前,真正意義上的瀏覽器端“伺服器推”技術是不存在。而所謂的Comet技術,也只不過是一種拿本來不是幹這事的東東,移作他用的trick行為。Comet的方式主要有兩種,iframe流和長輪詢。今天我要說的jsonp方式就是長輪詢方式的其中一種。

先說一下,長輪詢的主要兩種方式。

第一種是ajax長輪詢。它的優點是對連線的控制完善,可以識別http響應狀態碼進而捕獲各種網路錯誤,通過abort關閉連線,timeout設定超時,如果需要跨域通訊,可以通過巢狀iframe設定document.domain的方式實現。缺點是在這種方式下跨域,只能是當前域名的一個子域名。

另一種方式就是jsonp長輪詢。它的優點是可以實現與任意域名進行跨域通訊。缺點是對連線控制力不佳,不僅無法獲取http狀態碼,也很難捕獲網路錯誤(注:onerror可以捕獲錯誤,但在IE6、7、8和opera<11.6下不支援該事件),更糟糕的是,你無法在瀏覽器端方便地關閉連線,更不同提超時重連了。貌似缺點比優點多多了。

說實在地,如果不需要進行跨主域名通訊,實在不建議使用jsonp長輪詢,而且這種情況也很少。不過如果你真的需要這種方式或者你對標題的內容感興趣的話,就可以繼續往下看了。大笑

解決jsonp長輪詢的錯誤處理

來看看jQuery的jsonp外掛是如何實現的。以下是重點程式碼擷取

opera && opera.version() < 11.60 ?// onerror is not supported: do not set as async and assume in-order execution. // Add a trailing script to emulate the event ((scriptAfter = $( STR_SCRIPT_TAG )[ 0 ] ).text = "document.getElementById('" + script.id + "')." + STR_ON_ERROR + "()") : // onerror is supported: set the script as async to avoid requests blocking each others
( script[ STR_ASYNC ] = STR_ASYNC ); // Internet Explorer: event/htmlFor trick if ( oldIE ) { script.htmlFor = script.id; script.event = STR_ON_CLICK; } // Attached event handlers script[ STR_ON_LOAD ] = script[ STR_ON_ERROR ] = script[ STR_ON_READY_STATE_CHANGE ] = function ( result ) { // Test readyState if it exists if ( !script[ STR_READY_STATE ] || !/i/.test( script[ STR_READY_STATE ] ) ) { try { script[ STR_ON_CLICK ] && script[ STR_ON_CLICK ](); } catch( _ ) {} result = lastValue; lastValue = 0; result ? notifySuccess( result[ 0 ] ) : notifyError( STR_ERROR ); } };

可以看出來,jsonp外掛中做了三種相容性處理。1.script標籤支援標準onerror事件的瀏覽器。2.老版本IE(IE8及之前版本)。3.Opera瀏覽器(版本<11.60)

對於第一種,可以直接使用script標籤的onerror事件捕獲錯誤。對於第二種,老版本IE,使用readyState不包含字母i檢查,即非loading和interact狀態的其他狀態下,如果jsonp返回資料的引用裡有資料,說明成功返回了,呼叫成功回撥,否則呼叫錯誤回撥。其中

if ( oldIE ) {
	script.htmlFor = script.id;
	script.event = STR_ON_CLICK;
}

這一段,是一段trick。用htmlFor和event屬性來保證在readyState == loaded || readyState == complete時,script中的程式碼已經執行。因為我們不能完全依賴onreadystatechange中事件的執行順序。(關於htmlFor和event的用法,請參閱http://msdn.microsoft.com/en-us/library/ie/ms533746(v=vs.85).aspx

因此要加上

try {

	script[ STR_ON_CLICK ] && script[ STR_ON_CLICK ]();

} catch( _ ) {}

手動觸發script標籤的onclick事件,來保證script中的內容得以執行。

對於第三種情況,即Opera版本小於11.6的瀏覽器。

使用阻塞式指令碼載入的方式,去模擬onerror事件。什麼意思呢。就是說,不設定script的async屬性,這樣指令碼載入時會阻塞後續指令碼的執行,在script標籤之後再加一個inline的script標籤,當第一個script標籤正在載入中時,第二個inline的script是不會執行的。只有等到第一個script標籤載入結束(成功或者失敗)之後,第二個inline的script標籤中的內容才會得以執行,因此,可以用這種方法來模擬onerror事件。

ok,至此,所有瀏覽器的jsonp錯誤處理都被考慮到了。

解決jsonp長輪詢的連線無法通過js關閉的問題

jsonp外掛並沒有解決另外一個問題,關閉連線的問題。jsonp外掛並沒有真正關閉連線,比如設定超時的時候,jsonp外掛只是讓它靜默地失敗,連線此時依然是存在的。

考慮一種情況,某個支援長輪詢的server,同一個使用者ID下只允許建立一條連線,而此時由於某種變化,導致我們希望重新建立一個連線,但此時第一個連線已然存在,我們我們無法將其關閉,也就無法重新建立另外一個新的連線。這種問題當然可以由服務端去解決。比如服務端檢測到之前連線存在,就自動將其斷掉,或者客戶端傳送一個重新建立連線的請求,服務端收到請求後斷開之前的連線,然後重新建立新的連線。但有時候,我們還是希望可以在瀏覽器端直接處理。

在不斷實驗的過程中,我發現無論是將script標籤移除,還是重置script標籤的src,都無法斷開連線。但頁面重新整理肯定是可以斷開連線的。但是如果頁面重新重新整理,就會丟失頁面狀態,並且頁面重新整理需要時間,這會影響使用者體驗。因此,我換了一種思路,可以將jsonp的script標籤放到iframe裡,要斷開連線的時候,重新整理這個iframe就可以了。這就是我的實現思路。程式碼就不用上了。原理很簡單。就是用一個代理頁面,載入到iframe中,設定document.domain允許js跨域訪問。當需要關閉連線的時候,呼叫document.location.reload();