[深入淺出Cocoa]iOS網絡編程之CFNetwork
羅朝輝 (http://blog.csdn.net/kesalin/)
一,CFNetwork 簡介
- Cocoa層:NSURL,Bonjour,Game Kit,WebKit
- Core Foundation層:基於 C 的 CFNetwork 和 CFNetServices
- OS層:基於 C 的 BSD socket
二,CFNetwork API 簡介
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 函數:
但與 socket 不同的是,這兩個接口是異步的,當成功 open 之後,如果調用方設置了獲取 kCFStreamEventOpenCompleted 事件的標誌的話就會其調用回調函數。
而該回調函數及其參數設置是通過如下接口進行的:
該函數用於設置回調函數及相關參數。通過 streamEvents 標誌來設置我們對哪些事件感興趣;clientCB 是一個回調函數,當事件標誌對應的事件發生時,該回調函數就會被調用;clientContext 是用於傳遞參數到回調函數中去。
當設置好回調函數之後,我們可以將 socket stream 當做事件源調度到 run-loop 中去,這樣 run-loop 就能分發該 socket stream 的網絡事件了。
注意,在我們不再關心該 socket stream 的網絡事件時,記得要調用如下接口將 socket stream 從 run-loop 的事件源中移除。
當我們將 socket stream 的網絡事件調度到 run-loop 之後,我們就能在回調函數中相應各種事件,比如 kCFStreamEventHasBytesAvailable 讀取數據:
或 kCFStreamEventCanAcceptBytes 寫入數據:
最後,我們調用 close 方法關閉 socket 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。
此外,我們還可以調用如下接口獲取 socket 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,其工作流程也是一樣的。在這裏就不再介紹了。更多《深入淺出Cocoa》系列文章,敬請訪問CSDN專欄:http://blog.csdn.net/column/details/cocoa.html