[深入淺出Cocoa]iOS網路編程之CFNetwork

來源:互聯網
上載者:User

[深入淺出Cocoa]iOS網路編程之CFNetwork

羅朝輝 (http://www.cnblogs.com/kesalin/)

本文遵循“署名-非商業用途-保持一致”創作公用協議 一,CFNetwork 簡介首先來回顧下。在前文《[深入淺出Cocoa]iOS網路編程之Socket》中,提到iOS網路編程層次模型分為三層:
  • Cocoa層:NSURL,Bonjour,Game Kit,WebKit
  • Core Foundation層:基於 C 的 CFNetwork 和 CFNetServices
  • 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 封裝的。 本文範例程式碼就是這樣做的,源碼請查看: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 在使用之前就如原生 socket 一樣,必須顯式地調用其 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 示範類似,在這裡我只示範用戶端樣本。同樣,我們也在一個後台線程中啟動網路操作:

    NSURL * url = [NSURL URLWithString:[NSString stringWithFormat:@"%@:%@", serverHost, serverPort]];    NSThread * backgroundThread = [[NSThread alloc] initWithTarget:self                                                          selector:@selector(loadDataFromServerWithURL:)                                                            object:url];    [backgroundThread start];

然後在 loadDataFromServerWithURL 中建立 socket 流,並設定其回呼函數,將其加入到 run-loop 的事件來源中,然後啟動之:

- (void)loadDataFromServerWithURL:(NSURL *)url{    NSString * host = [url host];    NSInteger port = [[url port] integerValue];        // Keep a reference to self to use for controller callbacks    //    CFStreamClientContext ctx = {0, (__bridge void *)(self), NULL, NULL, NULL};        // Get callbacks for stream data, stream end, and any errors    //    CFOptionFlags registeredEvents = (kCFStreamEventHasBytesAvailable | kCFStreamEventEndEncountered | kCFStreamEventErrorOccurred);        // Create a read-only socket    //    CFReadStreamRef readStream;    CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (__bridge CFStringRef)host, port, &readStream, NULL);        // Schedule the stream on the run loop to enable callbacks    //    if (CFReadStreamSetClient(readStream, registeredEvents, socketCallback, &ctx)) {        CFReadStreamScheduleWithRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);            }    else {        [self networkFailedWithErrorMessage:@"Failed to assign callback method"];        return;    }        // Open the stream for reading    //    if (CFReadStreamOpen(readStream) == NO) {        [self networkFailedWithErrorMessage:@"Failed to open read stream"];                return;    }        CFErrorRef error = CFReadStreamCopyError(readStream);    if (error != NULL) {        if (CFErrorGetCode(error) != 0) {            NSString * errorInfo = [NSString stringWithFormat:@"Failed to connect stream; error '%@' (code %ld)", (__bridge NSString*)CFErrorGetDomain(error), CFErrorGetCode(error)];            [self networkFailedWithErrorMessage:errorInfo];        }                CFRelease(error);                return;    }        NSLog(@"Successfully connected to %@", url);        // Start processing    //    CFRunLoopRun();}

參考前面的介面說明,相信你不難理解上面的代碼。前面唯一沒有提到的介面就是 CFReadStreamCopyError,該介面用於擷取當前的錯誤資訊,如果沒有錯誤則返回 NULL。

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

此外,我們還可以調用如下介面擷取 socket stream 的目前狀態:

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

在上面的代碼中,我們設定了當有資料可以讀取,流到達結尾處時以及錯誤發生時調用回呼函數 socketCallback:

void socketCallback(CFReadStreamRef stream, CFStreamEventType event, void * myPtr){    KSCFNetworkViewController * controller = (__bridge KSCFNetworkViewController *)myPtr;        switch(event) {        case kCFStreamEventHasBytesAvailable: {            // Read bytes until there are no more            //            while (CFReadStreamHasBytesAvailable(stream)) {                UInt8 buffer[kBufferSize];                int numBytesRead = CFReadStreamRead(stream, buffer, kBufferSize);                                [controller didReceiveData:[NSData dataWithBytes:buffer length:numBytesRead]];            }                        break;        }                    case kCFStreamEventErrorOccurred: {            CFErrorRef error = CFReadStreamCopyError(stream);            if (error != NULL) {                if (CFErrorGetCode(error) != 0) {                    NSString * errorInfo = [NSString stringWithFormat:@"Failed while reading stream; error '%@' (code %ld)", (__bridge NSString*)CFErrorGetDomain(error), CFErrorGetCode(error)];                                        [controller networkFailedWithErrorMessage:errorInfo];                }                                CFRelease(error);            }                        break;        }                    case kCFStreamEventEndEncountered:            // Finnish receiveing data            //            [controller didFinishReceivingData];                        // Clean up            //            CFReadStreamClose(stream);            CFReadStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);            CFRunLoopStop(CFRunLoopGetCurrent());                        break;                    default:            break;    }}

上面的代碼也很好理解,當有資料可以讀取時,讀取之,然後更新 UI;當流到達結尾處時,關閉流,執行清理工作;當錯誤發送時,報告錯誤資訊。

 

四,擴充

雖然上面的代碼只示範了如何使用 CFNetwork 的 CFReadStream 來讀取資料,寫入資料使用 CFWriteStream,其工作流程也是一樣的。在這裡就不再介紹了。

  

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.