zoukankan      html  css  js  c++  java
  • [深入浅出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 中处理网络事件了。
     
    本文示例代码就是这样做的,源码请查看:
     

    二,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开发
    标签: iosnetwork

  • 相关阅读:
    一次数据库的整理的sql语句
    网页交互及xml的一些属性对程序的影响
    google编程挑战赛Round1的前两道题
    修改Windows帐户密码,导致Sql Server 2000无法启动。
    在虚拟主机中无法实现缩放等交互
    动态控件创建的一些经验
    文章
    如何解决 SQL Server 2000 中的连接问题
    待看
    I帧,P帧,B帧简介
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3021182.html
Copyright © 2011-2022 走看看