2013年12月30日

在iOS app中以Objective-C實作Long Polling客戶端 - 伺服器端以Atmosphere為例

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方法中設定一些參數:

  1. X-Atmosphere-Transport

    設定使用long-polling或是websocket。在本例中使用long-polling。

  2. X-Atmosphere-tracking-id

    trackingid是Atmosphere用來識別long polling客戶端的。 第一次發送HTTP Request時填0即可,Atmosphere會產生一個trackingid放在HTTP Response的Header並立即回傳。記錄起來並放在下一次Http Request的Get參數即可。

  3. 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分別負責:

  1. startLongPolling

    初始化需要用到的參數,並呼叫doLongPolling以執行第一次long polling。

  2. doLongPolling

    每一次long polling時,送出HTTP Request與接收HTTP Response時所需要做的工作

  3. 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];
}