[深刻淺出Cocoa]iOS網絡編程之CFNetwork


ios network

目錄(?)[+]html

[深刻淺出Cocoa]iOS網絡編程之CFNetworkios

羅朝輝 (http://blog.csdn.net/kesalin/)git

本文遵循「署名-非商業用途-保持一致」創做公用協議github

 

一,CFNetwork 簡介

首先來回顧下。在前文《[深刻淺出Cocoa]iOS網絡編程之Socket》中,提到iOS網絡編程層次模型分爲三層:編程

  • Cocoa層:NSURL,Bonjour,Game Kit,WebKit網絡

  • Core Foundation層:基於 C 的 CFNetwork 和 CFNetServicesapp

  • OS層:基於 C 的 BSD socket異步

前文講的是最底層的 socket,本文將介紹位於 Core Foundation 中的 CFNetwork。CFNetwork 只是對 BSD socket 的進行了輕量級的封裝,但在 iOS 中使用 CFNetwork 有一個顯著的好處,那就是 CFNetwork 與系統級別的設置(如:天線設置)以及 run-loop 結合得很好。每個線程都有本身的 run-loop,所以咱們能夠 CFNetwork 當中事件源加入到 run-loop 中,這樣就能夠在線程的 run-loop 中處理網絡事件了。BTW,大名鼎鼎的 ASIHttpRequest 庫就是基於 CFNetwork 封裝的。socket

本文示例代碼就是這樣作的,源碼請查看:函數

https://github.com/kesalin/iOSSnippet/tree/master/KSNetworkDemo

 

二,CFNetwork API 簡介

CFNetwork 接口是基於 C 的,下面的接口用於建立一對 socket stream,一個用於讀取,一個用於寫入:

void CFStreamCreatePairWithSocketToHost(CFAllocatorRef alloc, CFStringRef host, UInt32 port, CFReadStreamRef *readStream, CFWriteStreamRef *writeStream);

該函數使用 host 以及 port,CFNetwork 會將該 host 轉換爲 IP 地址,並轉換爲網絡字節順序。若是咱們只須要一個 socket stream,咱們能夠將另一個設置爲 NULL。還有另外兩個「重載」的建立 socket sream的接口:CFStreamCreatePairWithSocket 和 CFStreamCreatePairWithPeerSocketSignature,在這裏就不一一介紹了。

注意:在使用這些 socket stream 以前,必須顯式地調用其 open 函數:

Boolean CFReadStreamOpen(CFReadStreamRef stream);Boolean CFWriteStreamOpen(CFWriteStreamRef stream);

但與 socket 不一樣的是,這兩個接口是異步的,當成功 open 以後,若是調用方設置了獲取 kCFStreamEventOpenCompleted 事件的標誌的話就會其調用回調函數。

而該回調函數及其參數設置是經過以下接口進行的:

Boolean CFReadStreamSetClient(CFReadStreamRef stream, CFOptionFlags streamEvents, CFReadStreamClientCallBack clientCB, CFStreamClientContext *clientContext);Boolean CFWriteStreamSetClient(CFWriteStreamRef stream, CFOptionFlags streamEvents, CFWriteStreamClientCallBack clientCB, CFStreamClientContext *clientContext);

該函數用於設置回調函數及相關參數。經過 streamEvents 標誌來設置咱們對哪些事件感興趣;clientCB 是一個回調函數,當事件標誌對應的事件發生時,該回調函數就會被調用;clientContext 是用於傳遞參數到回調函數中去。

當設置好回調函數以後,咱們能夠將 socket stream 當作事件源調度到 run-loop 中去,這樣 run-loop 就能分發該 socket stream 的網絡事件了。

void CFReadStreamScheduleWithRunLoop(CFReadStreamRef stream, CFRunLoopRef runLoop, CFStringRef runLoopMode);void CFWriteStreamScheduleWithRunLoop(CFWriteStreamRef stream, CFRunLoopRef runLoop, CFStringRef runLoopMode);

注意,在咱們再也不關心該 socket stream 的網絡事件時,記得要調用以下接口將 socket stream 從 run-loop 的事件源中移除。

void CFReadStreamUnscheduleFromRunLoop(CFReadStreamRef stream, CFRunLoopRef runLoop, CFStringRef runLoopMode);void CFWriteStreamUnscheduleFromRunLoop(CFWriteStreamRef stream, CFRunLoopRef runLoop, CFStringRef runLoopMode);

當咱們將 socket stream 的網絡事件調度到 run-loop 以後,咱們就能在回調函數中相應各類事件,好比 kCFStreamEventHasBytesAvailable 讀取數據:

Boolean CFReadStreamHasBytesAvailable(CFReadStreamRef stream);CFIndex CFReadStreamRead(CFReadStreamRef stream, UInt8 *buffer, CFIndex bufferLength);

或 kCFStreamEventCanAcceptBytes 寫入數據:

Boolean CFWriteStreamCanAcceptBytes(CFWriteStreamRef stream);CFIndex CFWriteStreamWrite(CFWriteStreamRef stream, const UInt8 *buffer, CFIndex bufferLength);

最後,咱們調用 close 方法關閉 socket stream:

void CFReadStreamClose(CFReadStreamRef stream);void CFWriteStreamClose(CFWriteStreamRef stream);


三,客戶端示例代碼

與 socket 演示相似,在這裏我只演示客戶端示例。一樣,咱們也在一個後臺線程中啓動網絡操做:

[html] view plaincopyprint?

  1.    NSURL * url = [NSURL URLWithString:[NSString stringWithFormat:@"%@:%@", serverHost, serverPort]];  

  2.    NSThread * backgroundThread = [[NSThread alloc] initWithTarget:self  

  3.                                                          selector:@selector(loadDataFromServerWithURL:)  

  4.                                                            object:url];  

  5. [backgroundThread start];  

而後在 loadDataFromServerWithURL 中建立 socket 流,並設置其回調函數,將其加入到 run-loop 的事件源中,而後啓動之:

[cpp] view plaincopyprint?

  1. - (void)loadDataFromServerWithURL:(NSURL *)url  

  2. {  

  3.     NSString * host = [url host];  

  4.     NSInteger port = [[url port] integerValue];  

  5.       

  6.     // Keep a reference to self to use for controller callbacks  

  7.     //  

  8.     CFStreamClientContext ctx = {0, (__bridge void *)(self), NULL, NULL, NULL};  

  9.       

  10.     // Get callbacks for stream data, stream end, and any errors  

  11.     //  

  12.     CFOptionFlags registeredEvents = (kCFStreamEventHasBytesAvailable | kCFStreamEventEndEncountered | kCFStreamEventErrorOccurred);  

  13.       

  14.     // Create a read-only socket  

  15.     //  

  16.     CFReadStreamRef readStream;  

  17.     <strong>CFStreamCreatePairWithSocketToHost</strong>(kCFAllocatorDefault, (__bridge CFStringRef)host, port, &readStream, NULL);  

  18.       

  19.     // Schedule the stream on the run loop to enable callbacks  

  20.     //  

  21.     if (<strong>CFReadStreamSetClient</strong>(readStream, registeredEvents, socketCallback, &ctx)) {  

  22.         <strong>CFReadStreamScheduleWithRunLoop</strong>(readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);  

  23.           

  24.     }  

  25.     else {  

  26.         [self networkFailedWithErrorMessage:@"Failed to assign callback method"];  

  27.         return;  

  28.     }  

  29.       

  30.     // Open the stream for reading  

  31.     //  

  32.     if (<strong>CFReadStreamOpen</strong>(readStream) == NO) {  

  33.         [self networkFailedWithErrorMessage:@"Failed to open read stream"];  

  34.           

  35.         return;  

  36.     }  

  37.       

  38.     CFErrorRef error = <strong>CFReadStreamCopyError</strong>(readStream);  

  39.     if (error != NULL) {  

  40.         if (CFErrorGetCode(error) != 0) {  

  41.             NSString * errorInfo = [NSString stringWithFormat:@"Failed to connect stream; error '%@' (code %ld)", (__bridge NSString*)CFErrorGetDomain(error), CFErrorGetCode(error)];  

  42.             [self networkFailedWithErrorMessage:errorInfo];  

  43.         }  

  44.           

  45.         CFRelease(error);  

  46.           

  47.         return;  

  48.     }  

  49.       

  50.     NSLog(@"Successfully connected to %@", url);  

  51.       

  52.     // Start processing  

  53.     //  

  54.     <strong>CFRunLoopRun</strong>();  

  55. }  


參考前面的接口說明,相信你不難理解上面的代碼。前面惟一沒有提到的接口就是 CFReadStreamCopyError,該接口用於獲取當前的錯誤信息,若是沒有錯誤則返回 NULL。

CFErrorRef CFReadStreamCopyError(CFReadStreamRef stream);CFErrorRef CFWriteStreamCopyError(CFWriteStreamRef stream);

此外,咱們還能夠調用以下接口獲取 socket stream 的當前狀態:

CFStreamStatus CFReadStreamGetStatus(CFReadStreamRef stream);CFStreamStatus CFWriteStreamGetStatus(CFWriteStreamRef stream);

在上面的代碼中,咱們設置了當有數據能夠讀取,流到達結尾處時以及錯誤發生時調用回調函數 socketCallback:

[html] view plaincopyprint?

  1. void socketCallback(CFReadStreamRef stream, CFStreamEventType event, void * myPtr)  

  2. {  

  3.     KSCFNetworkViewController * controller = (__bridge KSCFNetworkViewController *)myPtr;  

  4.       

  5.     switch(event) {  

  6.         case kCFStreamEventHasBytesAvailable: {  

  7.             // Read bytes until there are no more  

  8.             //  

  9.             while (<strong>CFReadStreamHasBytesAvailable</strong>(stream)) {  

  10.                 UInt8 buffer[kBufferSize];  

  11.                 int numBytesRead = <strong>CFReadStreamRead</strong>(stream, buffer, kBufferSize);  

  12.                   

  13.                 [controller didReceiveData:[NSData dataWithBytes:buffer length:numBytesRead]];  

  14.             }  

  15.               

  16.             break;  

  17.         }  

  18.               

  19.         case kCFStreamEventErrorOccurred: {  

  20.             CFErrorRef error = <strong>CFReadStreamCopyError</strong>(stream);  

  21.             if (error != NULL) {  

  22.                 if (CFErrorGetCode(error) != 0) {  

  23.                     NSString * errorInfo = [NSString stringWithFormat:@"Failed while reading stream; error '%@' (code %ld)", (__bridge NSString*)CFErrorGetDomain(error), CFErrorGetCode(error)];  

  24.                       

  25.                     [controller networkFailedWithErrorMessage:errorInfo];  

  26.                 }  

  27.                   

  28.                 CFRelease(error);  

  29.             }  

  30.               

  31.               

  32.             break;  

  33.         }  

  34.               

  35.         case kCFStreamEventEndEncountered:  

  36.             // Finnish receiveing data  

  37.             //  

  38.             [controller didFinishReceivingData];  

  39.               

  40.             // Clean up  

  41.             //  

  42.             <strong>CFReadStreamClose</strong>(stream);  

  43.             <strong>CFReadStreamUnscheduleFromRunLoop</strong>(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);  

  44.             <strong>CFRunLoopStop</strong>(CFRunLoopGetCurrent());  

  45.               

  46.             break;  

  47.               

  48.         default:  

  49.             break;  

  50.     }  

  51. }  


上面的代碼也很好理解,當有數據能夠讀取時,讀取之,而後更新 UI;當流到達結尾處時,關閉流,執行清理工做;當錯誤發送時,報告錯誤信息。


四,擴展

雖然上面的代碼只演示瞭如何使用 CFNetwork 的 CFReadStream 來讀取數據,寫入數據使用 CFWriteStream,其工做流程也是同樣的。在這裏就再也不介紹了。更多《深刻淺出Cocoa》系列文章,敬請訪問CSDN專欄:http://blog.csdn.net/column/details/cocoa.html

相關文章
相關標籤/搜索