[深入浅出Cocoa]iOS网络编程之CFNe…

本文详细介绍CFNetwork的基础概念及使用方法,包括如何利用CFReadStream进行网络数据读取,并结合run-loop处理网络事件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一,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 封装的。

本文示例代码就是这样做的,源码请查看:
 

二,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, CFReadStreamClientCallBa ck clientCB, CFStreamClientContext *clientContext);
 
   
Boolean CFWriteStreamSetClient(CFWriteStreamRef stream, CFOptionFlags streamEvents, CFWriteStreamClientCallB ack 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 演示类似,在这里我只演示客户端示例。同样,我们也在一个后台线程中启动网络操作:

 

  1.    NSURL url [NSURL URLWithString:[NSString stringWithFormat:@"%@:%@", serverHost, serverPort]];  
  2.    NSThread backgroundThread [[NSThread alloc] initWithTarget:self  
  3.                                                          selector:@selector(loadDataFromServerWithURL:)  
  4.                                                            object:url];  
  5. [backgroundThread start];  
    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 的事件源中,然后启动之:

 

  1. (void)loadDataFromServerWithURL:(NSURL *)url  
  2.  
  3.     NSString host [url host];  
  4.     NSInteger port [[url port] integerValue];  
  5.       
  6.     // Keep reference to self to use for controller callbacks   
  7.     //   
  8.     CFStreamClientContext ctx {0, (__bridge void *)(self), NULL, NULL, NULL};  
  9.       
  10.     // Get callbacks for stream data, stream end, and any errors   
  11.     //   
  12.     CFOptionFlags registeredEvents (kCFStreamEventHasBytesAvailable kCFStreamEventEndEncountered kCFStreamEventErrorOccurred);  
  13.       
  14.     // Create read-only socket   
  15.     //   
  16.     CFReadStreamRef readStream;  
  17.     CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (__bridge CFStringRef)host, port, &readStream, NULL);  
  18.       
  19.     // Schedule the stream on the run loop to enable callbacks   
  20.     //   
  21.     if (CFReadStreamSetClient(readStream, registeredEvents, socketCallback, &ctx))  
  22.         CFReadStreamScheduleWithRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);  
  23.           
  24.      
  25.     else  
  26.         [self networkFailedWithErrorMessage:@"Failed to assign callback method"];  
  27.         return;  
  28.      
  29.       
  30.     // Open the stream for reading   
  31.     //   
  32.     if (CFReadStreamOpen(readStream) == NO)  
  33.         [self networkFailedWithErrorMessage:@"Failed to open read stream"];  
  34.           
  35.         return;  
  36.      
  37.       
  38.     CFErrorRef error CFReadStreamCopyError(readStream);  
  39.     if (error != NULL)  
  40.         if (CFErrorGetCode(error) != 0)  
  41.             NSString errorInfo [NSString stringWithFormat:@"Failed to connect stream; error '%@' (code %ld)", (__bridge NSString*)CFErrorGetDomain(error), CFErrorGetCode(error)];  
  42.             [self networkFailedWithErrorMessage:errorInfo];  
  43.          
  44.           
  45.         CFRelease(error);  
  46.           
  47.         return;  
  48.      
  49.       
  50.     NSLog(@"Successfully connected to %@", url);  
  51.       
  52.     // Start processing   
  53.     //   
  54.     CFRunLoopRun();  
  55.  
- (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:

 

  1. void socketCallback(CFReadStreamRef stream, CFStreamEventType event, void myPtr)  
  2.  
  3.     KSCFNetworkViewControllecontroller (__bridge KSCFNetworkViewControlle*)myPtr;  
  4.       
  5.     switch(event)  
  6.         case kCFStreamEventHasBytesAvailable:  
  7.             // Read bytes until there are no more  
  8.             //  
  9.             while (<</span>STRONG>CFReadStreamHasBytesAvailable</</span>STRONG>(stream))  
  10.                 UInt8 buffer[kBufferSize];  
  11.                 int numBytesRead <</span>STRONG>CFReadStreamRead</</span>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 <</span>STRONG>CFReadStreamCopyError</</span>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.             <</span>STRONG>CFReadStreamClose</</span>STRONG>(stream);  
  43.             <</span>STRONG>CFReadStreamUnscheduleFromRunLoop</</span>STRONG>(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);  
  44.             <</span>STRONG>CFRunLoopStop</</span>STRONG>(CFRunLoopGetCurrent());  
  45.               
  46.             break;  
  47.               
  48.         default:  
  49.             break;  
  50.      
  51.  
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》系列文章,敬请访问优快云专栏:http://blog.youkuaiyun.com/column/details/cocoa.html  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值