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

  • 相关阅读:
    链表--判断一个链表是否为回文结构
    矩阵--“之”字形打印矩阵
    二叉树——平衡二叉树,二叉搜索树,完全二叉树
    链表--反转单向和双向链表
    codeforces 490C. Hacking Cypher 解题报告
    codeforces 490B.Queue 解题报告
    BestCoder19 1001.Alexandra and Prime Numbers(hdu 5108) 解题报告
    codeforces 488A. Giga Tower 解题报告
    codeforces 489C.Given Length and Sum of Digits... 解题报告
    codeforces 489B. BerSU Ball 解题报告
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3021182.html
Copyright © 2011-2022 走看看