一,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的接口:CFStreamCreatePairWithSo
注意:在使用这些 socket stream 之前,必须显式地调用其 open 函数:
但与 socket 不同的是,这两个接口是异步的,当成功 open 之后,如果调用方设置了获取
而该回调函数及其参数设置是通过如下接口进行的:
该函数用于设置回调函数及相关参数。通过
当设置好回调函数之后,我们可以将 socket stream 当做事件源调度到 run-loop 中去,这样 run-loop 就能分发该 socket stream 的网络事件了。
注意,在我们不再关心该 socket stream 的网络事件时,记得要调用如下接口将 socket stream 从 run-loop 的事件源中移除。
当我们将 socket stream 的网络事件调度到 run-loop 之后,我们就能在回调函数中相应各种事件,比如
或
最后,我们调用 close 方法关闭 socket stream:
三,客户端示例代码
与 socket 演示类似,在这里我只演示客户端示例。同样,我们也在一个后台线程中启动网络操作:
-
NSURL * url = [NSURL URLWithString:[NSString stringWithFormat:@"%@:%@", serverHost, serverPort]]; -
NSThread * backgroundThread = [[NSThread alloc] initWithTarget:self -
selector:@selector(loadDataFromServerWithUR L:) -
object:url]; - [backgroundThread
start];
NSURL * url = [NSURL URLWithString:[NSString stringWithFormat:@"%@:%@", serverHost, serverPort]]; NSThread * backgroundThread = [[NSThread alloc] initWithTarget:self selector:@selector(loadDataFromServerWithURL:) object:url]; [backgroundThread start];
然后在
- -
(void)loadDataFromServerWithUR L:(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 = (kCFStreamEventHasBytesAv ailable | kCFStreamEventEndEncount ered | kCFStreamEventErrorOccur red); -
-
// Create a read-only socket -
// -
CFReadStreamRef readStream; -
CFStreamCreatePairWithSo cketToHost(kCFAllocatorDefault, (__bridge CFStringRef)host, port, &readStream, NULL); -
-
// Schedule the stream on the run loop to enable callbacks -
// -
if (CFReadStreamSetClient(readStream, registeredEvents, socketCallback, &ctx)) { -
CFReadStreamScheduleWith RunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); -
-
} -
else { -
[self networkFailedWithErrorMe ssage:@"Failed to assign callback method"]; -
return; -
} -
-
// Open the stream for reading -
// -
if (CFReadStreamOpen(readStream) == NO) { -
[self networkFailedWithErrorMe ssage:@"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 networkFailedWithErrorMe ssage:errorInfo]; -
} -
-
CFRelease(error); -
-
return; -
} -
-
NSLog(@"Successfully connected to %@", url); -
-
// Start processing -
// -
CFRunLoopRun(); - }
- (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 = (kCFStreamEventHasBytesAv ailable | kCFStreamEventEndEncount ered | kCFStreamEventErrorOccur red); // Create a read-only socket // CFReadStreamRef readStream; CFStreamCreatePairWithSo cketToHost(kCFAllocatorDefault, (__bridge CFStringRef)host, port, &readStream, NULL); // Schedule the stream on the run loop to enable callbacks // if (CFReadStreamSetClient(readStream, registeredEvents, socketCallback, &ctx)) { CFReadStreamScheduleWith RunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); } else { [self networkFailedWithErrorMe ssage:@"Failed to assign callback method"]; return; } // Open the stream for reading // if (CFReadStreamOpen(readStream) == NO) { [self networkFailedWithErrorMe ssage:@"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 networkFailedWithErrorMe ssage:errorInfo]; } CFRelease(error); return; } NSLog(@"Successfully connected to %@", url); // Start processing // CFRunLoopRun(); }
参考前面的接口说明,相信你不难理解上面的代码。前面唯一没有提到的接口就是
此外,我们还可以调用如下接口获取 socket stream 的当前状态:
在上面的代码中,我们设置了当有数据可以读取,流到达结尾处时以及错误发生时调用回调函数
- void
socketCallback(CFReadStreamRef stream, CFStreamEventType event, void * myPtr) - {
-
KSCFNetworkViewControlle r * controller = (__bridge KSCFNetworkViewControlle r *)myPtr; -
-
switch(event) { -
case kCFStreamEventHasBytesAv ailable: { -
// Read bytes until there are no more -
// -
while (<</span>STRONG>CFReadStreamHasBytesAvai lable</</span>STRONG>(stream)) { -
UInt8 buffer[kBufferSize]; -
int numBytesRead = <</span>STRONG>CFReadStreamRead</</span>STRONG>(stream, buffer, kBufferSize); -
-
[controller didReceiveData:[NSData dataWithBytes:buffer length:numBytesRead]]; -
} -
-
break; -
} -
-
case kCFStreamEventErrorOccur red: { -
CFErrorRef error = <</span>STRONG>CFReadStreamCopyError</</span>STRONG>(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 networkFailedWithErrorMe ssage:errorInfo]; -
} -
-
CFRelease(error); -
} -
-
-
break; -
} -
-
case kCFStreamEventEndEncount ered: -
// Finnish receiveing data -
// -
[controller didFinishReceivingData]; -
-
// Clean up -
// -
<</span>STRONG>CFReadStreamClose</</span>STRONG>(stream); -
<</span>STRONG>CFReadStreamUnscheduleFr omRunLoop</</span>STRONG>(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); -
<</span>STRONG>CFRunLoopStop</</span>STRONG>(CFRunLoopGetCurrent()); -
-
break; -
-
default: -
break; -
} - }
void socketCallback(CFReadStreamRef stream, CFStreamEventType event, void * myPtr) { KSCFNetworkViewController * controller = (__bridge KSCFNetworkViewControlle r *)myPtr; switch(event) { case kCFStreamEventHasBytesAv ailable: { // Read bytes until there are no more // while (CFReadStreamHasBytesAvai lable(stream)) { UInt8 buffer[kBufferSize]; int numBytesRead = CFReadStreamRead(stream, buffer, kBufferSize); [controller didReceiveData:[NSData dataWithBytes:buffer length:numBytesRead]]; } break; } case kCFStreamEventErrorOccur red: { 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 networkFailedWithErrorMe ssage:errorInfo]; } CFRelease(error); } break; } case kCFStreamEventEndEncount ered: // Finnish receiveing data // [controller didFinishReceivingData]; // Clean up // CFReadStreamClose(stream); CFReadStreamUnscheduleFr omRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); CFRunLoopStop(CFRunLoopGetCurrent()); break; default: break; } }
上面的代码也很好理解,当有数据可以读取时,读取之,然后更新 UI;当流到达结尾处时,关闭流,执行清理工作;当错误发送时,报告错误信息。
四,扩展
虽然上面的代码只演示了如何使用 CFNetwork 的 CFReadStream 来读取数据,写入数据使用 CFWriteStream,其工作流程也是一样的。在这里就不再介绍了。更多《深入浅出Cocoa》系列文章,敬请访问优快云专栏:http://blog.youkuaiyun.com/column/details/cocoa.html