好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

iOS网络编程之CFNetwork

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

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

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

罗朝辉 ( http://www.cnblogs.com/kesalin/ )

本文遵循“ 署名-非商业用途-保持一致 ”创作公用协议

 

一,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 中处理网络事件了。

 

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

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 在使用之前就如原生 socket 一样,必须显式地调用其 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 演示类似,在这里我只演示客户端示例。同样,我们也在一个后台线程中启动网络操作:

    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。

 CFErrorRef CFReadStreamCopyError(CFReadStreamRef stream);

CFErrorRef CFWriteStreamCopyError(CFWriteStreamRef stream); 

此外,我们还可以调用如下接口获取 socket stream 的当前状态:

 CFStreamStatus CFReadStreamGetStatus(CFReadStreamRef stream);

CFStreamStatus CFWriteStreamGetStatus(CFWriteStreamRef 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开发

标签:  ios ,  network

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于iOS网络编程之CFNetwork的详细内容...

  阅读:38次