欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Cocoa iOS网络编程--CFNetwork

程序员文章站 2024-01-15 08:11:10
...

一,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 之前,必须显式地调用其 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 plain copy print?
   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 plain copy print?
- (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 plain copy print?
01.void socketCallback(CFReadStreamRef stream, CFStreamEventType event, void * myPtr)  
02.{  
03.    KSCFNetworkViewController * controller = (__bridge KSCFNetworkViewController *)myPtr;  
04.      
05.    switch(event) {  
06.        case kCFStreamEventHasBytesAvailable: {  
07.            // Read bytes until there are no more  
08.            //  
09.            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

原文:http://blog.csdn.net/kesalin/article/details/8801156