[深刻浅出Cocoa]iOS网络编程之CFNetworkios
罗朝辉 (http://blog.csdn.net/kesalin/)git
本文遵循“署名-非商业用途-保持一致”创做公用协议github
首先来回顾下。在前文《[深刻浅出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 接口是基于 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?
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 的事件源中,而后启动之:
[cpp] view plaincopyprint?
- (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;
<strong>CFStreamCreatePairWithSocketToHost</strong>(kCFAllocatorDefault, (__bridge CFStringRef)host, port, &readStream, NULL);
// Schedule the stream on the run loop to enable callbacks
//
if (<strong>CFReadStreamSetClient</strong>(readStream, registeredEvents, socketCallback, &ctx)) {
<strong>CFReadStreamScheduleWithRunLoop</strong>(readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
}
else {
[self networkFailedWithErrorMessage:@"Failed to assign callback method"];
return;
}
// Open the stream for reading
//
if (<strong>CFReadStreamOpen</strong>(readStream) == NO) {
[self networkFailedWithErrorMessage:@"Failed to open read stream"];
return;
}
CFErrorRef error = <strong>CFReadStreamCopyError</strong>(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
//
<strong>CFRunLoopRun</strong>();
}
参考前面的接口说明,相信你不难理解上面的代码。前面惟一没有提到的接口就是 CFReadStreamCopyError,该接口用于获取当前的错误信息,若是没有错误则返回 NULL。
CFErrorRef CFReadStreamCopyError(CFReadStreamRef stream);CFErrorRef CFWriteStreamCopyError(CFWriteStreamRef stream);
此外,咱们还能够调用以下接口获取 socket stream 的当前状态:
CFStreamStatus CFReadStreamGetStatus(CFReadStreamRef stream);CFStreamStatus CFWriteStreamGetStatus(CFWriteStreamRef stream);
在上面的代码中,咱们设置了当有数据能够读取,流到达结尾处时以及错误发生时调用回调函数 socketCallback:
[html] view plaincopyprint?
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 (<strong>CFReadStreamHasBytesAvailable</strong>(stream)) {
UInt8 buffer[kBufferSize];
int numBytesRead = <strong>CFReadStreamRead</strong>(stream, buffer, kBufferSize);
[controller didReceiveData:[NSData dataWithBytes:buffer length:numBytesRead]];
}
break;
}
case kCFStreamEventErrorOccurred: {
CFErrorRef error = <strong>CFReadStreamCopyError</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 networkFailedWithErrorMessage:errorInfo];
}
CFRelease(error);
}
break;
}
case kCFStreamEventEndEncountered:
// Finnish receiveing data
//
[controller didFinishReceivingData];
// Clean up
//
<strong>CFReadStreamClose</strong>(stream);
<strong>CFReadStreamUnscheduleFromRunLoop</strong>(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
<strong>CFRunLoopStop</strong>(CFRunLoopGetCurrent());
break;
default:
break;
}
}
上面的代码也很好理解,当有数据能够读取时,读取之,而后更新 UI;当流到达结尾处时,关闭流,执行清理工做;当错误发送时,报告错误信息。
虽然上面的代码只演示了如何使用 CFNetwork 的 CFReadStream 来读取数据,写入数据使用 CFWriteStream,其工做流程也是同样的。在这里就再也不介绍了。更多《深刻浅出Cocoa》系列文章,敬请访问CSDN专栏:http://blog.csdn.net/column/details/cocoa.html