Long Polling是實現推送技術(Push technology)的一種方式,搭配伺服器端Atmosphere Framework,我們以完整的實例,說明如何用Objective-C實作Long Polling客戶端的細節。
Long Polling簡介
Long Polling是實現推送技術(Push technology)的一種方式。 所謂推送技術,此處指的是讓HTTP伺服器能主動對HTTP客戶端發送資料的一種技術。
Long Polling的運作方式為:當客戶端向伺服器發送HTTP Request後,若伺服器端當前沒有需要回傳的資料,則不立即回傳HTTP Response,直到伺服器端有資料需要回應給客戶端,或是此次請求已超過一段時間,才將HTTP Response回傳給客戶端。
因此,只要每當客戶端收到HTTP Response後,便立刻向伺服器再次發送HTTP Request,即可模擬由伺服器端主動送資料給客戶端的情境,實現推送技術。
實作Long Polling客戶端非常容易,誠如前言,只要發送一個HTTP Request,並在每次收到HTTP Response後,再次發送HTTP Request即可。以下以簡短的Pseudo Code說明:
function doLongPolling {
//送出HTTP Request,等待回應
receivedData = sendHTTPRequest();
//處理收到的HTTP Response
handleReceivedData(receivedData);
//建立新背景執行緒再次呼叫longPolling
executeNewThread(doLongPolling);
}
惟需特別注意的是:客戶端的請求超時時間應比伺服器端還長,以避免客戶端誤判為伺服器端無回應;此外,也不能忘了網路或伺服器發生錯誤時的處理。
Atmosphere簡介
Atmosphere是一套整合Long Polling, Websocket等非同步網頁應用程式的伺服器端框架(Framework)。針對Atmosphere撰寫客戶端也相當容易,只要了解Atmosphere所訂的protocol即可。
Atmosphere protocol基礎
向Atmosphere發送HTTP Request時,需要在Get方法中設定一些參數:
X-Atmosphere-Transport
設定使用long-polling或是websocket。在本例中使用long-polling。
X-Atmosphere-tracking-id
trackingid是Atmosphere用來識別long polling客戶端的。 第一次發送HTTP Request時填0即可,Atmosphere會產生一個trackingid放在HTTP Response的Header並立即回傳。記錄起來並放在下一次Http Request的Get參數即可。
X-Cache-Date
Atmosphere伺服器端在某些情況下可以設定使用Cache。 第一次發送HTTP Request時填0即可,Atmosphere會在每次的HTTP Response的Header回傳當前的cachedate。記錄起來並放在下一次Http Request的Get參數即可。
以上僅簡短介紹此篇文章會用到的Atmosphere protocol。
關於更完整的Atmosphere Framework介紹請參考Understanding the Atmosphere Framework!。
程式範例
以下將以較完整的實例,說明以Objective-C實作Long Polling客戶端的細節。
LongPollingClient class Declaration
首先,我們自訂一個類別,名為LongPollingClient:
@interface LongPollingClient : NSObject
@property (strong, nonatomic) NSString* trackingid;
@property (strong, nonatomic) NSString* cachedate;
-(void)startLongPolling;
-(void)doLongPolling;
-(void)retryAfterDelay;
@end
類別中以兩個property儲存Atmosphere回傳的tracking以及cachedate。
而另外有三個method分別負責:
startLongPolling
初始化需要用到的參數,並呼叫doLongPolling以執行第一次long polling。
doLongPolling
每一次long polling時,送出HTTP Request與接收HTTP Response時所需要做的工作
retryAfterDelay
發生問題時,延遲一段時間再執行long polling
startLongPolling method:
初始化trackingid與cachedate
self.trackingid = @"0";
self.cachedate = @"0";
以背景執行緒執行long polling
[self performSelectorInBackground:@selector(doLongPolling) withObject: nil];
doLongPolling method:
讀取當前的trackingid與cachedate。
NSString* trackingid = self.trackingid;
NSString* cachedate = self.cachedate;
建立請求物件
NSString* urlString = [NSString stringWithFormat:@"http://www.xxx.net/xxx/xxx?X-Atmosphere-tracking-id=%@&X-Atmosphere-Framework=2.0.6-javascript&X-Atmosphere-Transport=long-polling&X-Cache-Date=%@&X-atmo-protocol=true&socketSeq=0",trackingid,cachedate];
NSURL* requestUrl = [NSURL URLWithString:@""];
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:requestUrl];
設定請求超時時間為75秒
[request setValue:@75 forKey:@"timeoutInterval"];
送出HTTP Request
NSError* error = nil;
NSHTTPURLResponse* response = nil;
NSData* longpollResData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
網路錯誤時的處理,通常可以等待幾秒後再呼叫long polling
if(error != nil) {
//retry after delay ...
return;
}
有需要的話,可針對不同的HTTP Status Code進行處理
if(response.statusCode == 403) {
//DO something
}
由HTTP Response Header中讀取trackingid與cachedate
if([response respondsToSelector:@selector(allHeaderFields)]) {
NSDictionary * dic = [response allHeaderFields];
NSString* tid = [dic objectForKey:@"X-Atmosphere-tracking-id"];
NSString* cdate = [dic objectForKey:@"X-Cache-Date"];
//do something with trackingid and cachedate...
}
將收到的資料轉為字串,並進行處理
NSString* responseString = [[NSString alloc] initWithData:longpollResData encoding:NSUTF8StringEncoding];
//do something with responseString
以背景執行緒執行下次的HTTP Request
[self performSelectorInBackground:@selector(doLongPolling) withObject: nil];
retryAfterDelay method:
等待5秒後再執行long polling
[self performSelector:@selector(doLongPolling) withObject:nil afterDelay:5.0f];
使用LongPollingClient類別
完成了LongPollingClient類別後,若要在其他程式碼中使用此類別, 只要建立LongPollingClient的實例,並呼叫startLongPolling方法即可。 舉例來說,在某個UIViewController的viewDidLoad:
- (void)viewDidLoad
{
LongPollingClient* client = [[LongPollingClient alloc] init];
[client startLongPolling];
}
沒有留言:
張貼留言