美文网首页
CFNetwork入门

CFNetwork入门

作者: Stark_Dylan | 来源:发表于2016-12-30 17:57 被阅读613次

    title: CFNetwork
    date: 2016-08-31 16:01:14
    tags: CoreFoundation
    thumbnail: http://www.51wendang.com/pic/00a226f3c9fa6ed45d1d781c/1-817-png_6_0_0_300_110_360_270_892.5_1263-1200-0-0-1200.jpg


    CFNetwork

    存在于CoreFoundation中的一个地级别但高性能的网络框架。BSD套接字的扩展,CFNetwork物理上和理论上都基于BSD套接字。有大量的Cocoa框架依赖于CFNetwork

    CFNetwork更侧重与网络协议,Foundation则更倾向于API数据请求等,虽然框架也提供了一些操作,但是远不如CFNetwork丰富。在学习CFNetwork之前,需要先了解2个基础API框架: CFSocketCFStream

    CFSocket API

    套接字是网络通信的底层,一个套接字类似于电话的插孔,他允许链接到另外一个电话插孔并传输一些信息过去。最常见的套接字是BSD套接字。CFSocket是BSD套接字的一个抽象概念,在很小开销的情况下,几乎提供了全部BSD套接字的功能,并将套接字集成到一个Loop中。并且,CFSocket可以处理任何类型的套接字。

    CFStream API

    读写流,提供一种简单的方法进行媒体数据的交换,与设备无关。你可以为内存中、文件中或者网络中的数据创建流,并且你可以在不把数据加载到内存中的情况下使用流。流是一个字节序列串行传输的通信路径,流是单向的,通常情况下,为了双向通信,需要输入(CFReadStream)、输出流(CFWriteStream)。除了基于文件的流,你不能寻找一个流,一旦数据流被提供或者被消耗,就不能从流中重新取出。


    CFStream构建在CFSocket之上,在CFHTTPCFFTP之下。如图可以看出,尽管CFStream不是CFNetwork正式的部分,但它是几乎所有CFNetwork的基础。CFNetwork框架的层级设计:

    图1-1图1-1
    CFNetwork API

    CFNetwork又分成了几个单独的API,分别负责一个特定的的网络协议,这些API可以结合或分开使用,这取决于App的实际需要。

    CFFTP

    CFFTP使与FTP服务器通信更加便利。创建写入流与读取流,使用读写流,你可以进行的操作包括:

    • 从FTP服务器下载文件
    • 上传文件到FTP服务器
    • 获得FTP服务器下目录
    • 创建目录到FTP服务器
    CFHTTP

    发送和接受HTTP消息,CFFTP是FTP协议的抽象,CFHTTP是HTTP协议的抽象。超文本传输协议(HTTP)是一种客户端/服务端的请求/响应协议,客户端创建请求消息,请求消息被序列化,转换为原始字节流,发送字节流到服务器,服务器收到进行反序列化处理并响应。

    要创建一个HTTP请求,需指定一些基础的内容:

    • 请求的方法,比如GET、POST、HEAD等
    • URL 资源定位,比如http://www.apple.com
    • HTTP版本,比如1.0、2.0
    • 消息主题,字节流
    • 消息头

    消息创建后,需将其序列化后进行传递,序列化后一般的请求样式为:

    GET / HTTP/1.0\r\nUser-Agent: UserAgent\r\nContent-Length: 0\r\n\r\n
    
    CFHTTPAuthentication

    完成身份验证。

    CFHost

    获取主机信息,包括名称、地址、可达性信息等。获取信息的过程被称为解析

    所有的CFNetwork、CFHost都兼容IPv4与IPv6,使用CFHost,可以透明的使用代码对IPv4、IPv6进行处理。

    CFNetServices

    如果你想让你的应用使用Bonjour注册一个服务或发现服务可以使用CFNetServices。Bonjour是苹果零配置网络(ZEROCONF)的实现,它允许你发布、发现和解析网络服务。

    CFNetDiagnostics

    连接到网络的应用依赖于一个稳定的链接。如果网络不稳定,这将导致应用程序的问题。采用CFNetDiagnostics API,用户可以自己诊断如下网络问题:

    • 物理连接失败(例如,未插入电缆)
    • 网络故障(例如,DNS或DHCP服务器不再响应)
    • 配置失败(例如,代理配置不正确)

    由下至上的进行学习

    CFSocket

    官方文档

    #import <CFNetwork/CFNetwork.h>
    #import <sys/socket.h>
    #import <netinet/in.h>
    #import <arpa/inet.h>
    #import <unistd.h>
    

    进入第一个socket程序:

    1. 添加2个全局变量供下面
    CFSocketRef socket; // socket引用
    CFDataRef dataRef;  // 存储服务器地址信息
    
    1. 创建socket并发送、接收消息
        // 创建socket连接
        CFSocketContext context = {
            0,                          // 结构体的版本,必须为0
            (__bridge void *)(self),    // 一个任意指针的数据,可以用在创建时CFSocket对象相关联。这个指针被传递给所有的上下文中定义的回调。
            NULL,                       // 一个定义在上面指针中的retain的回调, 可以为NULL
            NULL,
            NULL
        };
        
        // 创建socket引用
        socket = CFSocketCreate(
                                kCFAllocatorDefault,  // 为新对象分配内存,可以为nil
                                PF_INET, // 协议族,如果为0或者负数,则默认为PF_INET
                                SOCK_STREAM, // 套接字类型,如果协议族为PF_INET,则它会默认为SOCK_STREAM,
                                IPPROTO_TCP, // 套接字协议,如果协议族是PF_INET且协议是0或者负数,它会默认为IPPROTO_TCP
                                kCFSocketConnectCallBack, // 触发回调函数的socket消息类型,具体见Callback Types
                                TCPServerConnectCallBack, // 上面情况下触发的回调函数
                                &context // 一个持有CFSocket结构信息的对象,可以为nil
                                );
    

    实现callBack方法

    static void
    TCPServerConnectCallBack(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) {
        if ( data != NULL ) {
            // 当socket为kCFSocketConnectCallBack时,失败时回调失败会返回一个错误代码指针,其他情况返回NULL
            NSLog(@"连接失败");
            return;
        }
        UIViewController * vc = (__bridge UIViewController *) info;
        [vc performSelector:@selector(sendMessage) withObject:nil];
        [vc performSelector:@selector(readStream) withObject:nil];
    }
    
    1. 创建服务器地址信息
        // 创建服务端信息
        struct sockaddr_in addr4; // IPv4, sockaddr_in6
        memset(&addr4, 0, sizeof(addr4));
        addr4.sin_len = sizeof(addr4);
        addr4.sin_family = AF_INET;
        addr4.sin_port = htons(18800);
        addr4.sin_addr.s_addr = inet_addr([localHost UTF8String]);
        dataRef = CFDataCreate(kCFAllocatorDefault, (UInt8 *)&addr4, sizeof(addr4));
    
    1. 连接
    if ( socket ) {
            CFSocketError e = CFSocketConnectToAddress(socket, dataRef, -1);
            
            if ( e ) {
                NSLog(@"Error!");
                return;
            }
            
            CFRunLoopRef runLoopRef = CFRunLoopGetCurrent();
            CFRunLoopSourceRef runLoopSourcesRef = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket, 0);
            CFRunLoopAddSource(runLoopRef, runLoopSourcesRef, kCFRunLoopCommonModes);
            CFRelease(runLoopSourcesRef);
        } else {
            NSLog(@"连接失败");
        }
    
    1. 接收与发送消息
    - (void) readStream {
        char buffer[1024];
        while (recv(CFSocketGetNative(socket), //与本机关联的Socket 如果已经失效返回-1:INVALID_SOCKET
                    buffer, sizeof(buffer), 0)) {
            NSLog(@"%@", [NSString stringWithUTF8String:buffer]);
        }
    }
    
    - (void)sendMessage {
        NSString *stringTosend = @"你好";
        CFSocketError e = CFSocketSendData(socket, dataRef, CFDataCreate(kCFAllocatorDefault, (UInt8 *)[stringTosend UTF8String], sizeof([stringTosend UTF8String])), 1);
        if ( e ) {
            
        }
    }
    
    NSString * localHost = @"120.27.139.39"; // 该地址为测试IP地址, 仅供测试连接使用
    

    以上步骤没问题的话,可以成功的连接到服务器并发送一条消息。

    参考文档

    CFStream

    尝试对文件的读取,文件直接存在于项目工程目录下,通过NSBundle来加载。

    • 创建读入流
        // 创建读入流
        NSString *pdfPath = [[NSBundle mainBundle]
                             pathForResource:@"File" ofType:@"txt"];
        NSURL *pdfUrl = [NSURL fileURLWithPath:pdfPath];
        CFReadStreamRef myReadStream = CFReadStreamCreateWithFile(kCFAllocatorDefault, (CFURLRef)pdfUrl);
    

    读取文件内容

        if (!CFReadStreamOpen(myReadStream)) {
            CFStreamError myErr = CFReadStreamGetError(myReadStream);
            // 发生了错误
            if (myErr.domain == kCFStreamErrorDomainPOSIX) {
                // Interpret myErr.error as a UNIX errno.
            } else if (myErr.domain == kCFStreamErrorDomainMacOSStatus) {
                // Interpret myErr.error as a MacOS error code.
                OSStatus macError = (OSStatus)myErr.error;
                NSLog(@"%d", macError);
            }
        } else {
            NSLog(@"打开成功");
            CFIndex numBytesRead;
            do {
                UInt8 buf[1024 * 1024]; // define myReadBufferSize as desired
                numBytesRead = CFReadStreamRead(myReadStream, buf, sizeof(buf));
                if( numBytesRead > 0 ) {
                    NSLog(@"%s", buf);
                } else if( numBytesRead < 0 ) {
                    CFStreamError error = CFReadStreamGetError(myReadStream);
                    NSLog(@"%ld %d", error.domain, error.error);
                } else {
                    NSLog(@"去读结束");
                }
            } while( numBytesRead > 0 );
            
            NSLog(@"读取完毕");
            CFReadStreamClose(myReadStream);
            CFRelease(myReadStream);
            myReadStream = NULL;
        }
    

    正常执行,会在控制台打印工程目录下File.txt文件的内容。

    • 创建写入流
        NSString * document = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES).lastObject;
        NSString * p = [document stringByAppendingPathComponent:@"a.txt"];
        
        if ( ![[NSFileManager defaultManager] fileExistsAtPath:p] ) {
            [[NSFileManager defaultManager] createFileAtPath:p contents:nil attributes:nil];
        }
        
        CFWriteStreamRef myWriteStream =
        CFWriteStreamCreateWithFile(kCFAllocatorDefault, (CFURLRef)[NSURL fileURLWithPath:p]);
    

    开始写入操作

       if (!CFWriteStreamOpen(myWriteStream)) {
            CFStreamError myErr = CFWriteStreamGetError(myWriteStream);
            // An error has occurred.
            if (myErr.domain == kCFStreamErrorDomainPOSIX) {
                // Interpret myErr.error as a UNIX errno.
            } else if (myErr.domain == kCFStreamErrorDomainMacOSStatus) {
                // Interpret myErr.error as a MacOS error code.
                OSStatus macError = (OSStatus)myErr.error;
                // Check other error domains.
                NSLog(@"%d", macError);
            }
        }
        
        NSLog(@"%ld",CFWriteStreamGetStatus(myWriteStream));
        
        const char * buf = "World !";
        CFIndex bufLen = (CFIndex)strlen(buf);
        
        if ( CFWriteStreamCanAcceptBytes(myWriteStream) ) {
            NSLog(@"可以接受字节");
            CFIndex bytesWritten = CFWriteStreamWrite(myWriteStream, (UInt8 *)buf, (CFIndex)bufLen);
            NSLog(@"%ld", bytesWritten);
        } else {
            NSLog(@"不可以接受字节");
        }
        
        CFWriteStreamClose(myWriteStream);
        CFRelease(myWriteStream);
        myWriteStream = NULL;
    

    如果正常运行的话, 会在项目本沙箱地址Library中存在a.txt并且内容为World !

    官方文档

    CFHTTP

    创建一个Request

        CFStringRef bodyString = CFSTR("Hello");
        
        CFStringRef headerFieldName = CFSTR("X-My-Favorite-Field");
        CFStringRef headerFieldValue = CFSTR("Dreams");
        
        CFStringRef url = CFSTR("http://www.apple.com");
        CFURLRef myURL = CFURLCreateWithString(kCFAllocatorDefault, url, NULL);
        
        CFStringRef requestMethod = CFSTR("GET");
        CFHTTPMessageRef myRequest =
        CFHTTPMessageCreateRequest(kCFAllocatorDefault, requestMethod, myURL,
                                   kCFHTTPVersion1_1);
        
        CFDataRef bodyDataExt = CFStringCreateExternalRepresentation(kCFAllocatorDefault, bodyString, kCFStringEncodingUTF8, 0);
        CFHTTPMessageSetBody(myRequest, bodyDataExt);
        CFHTTPMessageSetHeaderFieldValue(myRequest, headerFieldName, headerFieldValue);
        CFDataRef mySerializedRequest = CFHTTPMessageCopySerializedMessage(myRequest);
        
        CFRelease(myRequest);
        CFRelease(myURL);
        CFRelease(url);
        CFRelease(mySerializedRequest);
        myRequest = NULL;
        mySerializedRequest = NULL;
    

    mySerializedRequest即为序列化后的Request内容。

    (lldb) po [[NSString alloc] initWithData:(NSData *)mySerializedRequest encoding:NSUTF8StringEncoding]
    GET / HTTP/1.1
    X-My-Favorite-Field: Dreams
    
    Hello
    

    通过lldb打印可以看到内容。

    创建请求并发送

        CFReadStreamRef myReadStream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, myRequest);
        CFReadStreamOpen(myReadStream);
    
        CFHTTPMessageRef myResponse = (CFHTTPMessageRef)CFReadStreamCopyProperty(myReadStream, kCFStreamPropertyHTTPResponseHeader);
        CFStringRef myStatusLine = CFHTTPMessageCopyResponseStatusLine(myResponse);
        UInt32 myErrCode = CFHTTPMessageGetResponseStatusCode(myResponse);
    

    其中CFReadStreamCreateForHTTPRequest类似的API已经弃用,苹果希望使用NSURLSession。

    官方文档

    Communicating with Authenticating HTTP Servers

    官方文档

    CFFTP

    官方文档

    网络诊断

    CFNetDiagnosticDiagnoseProblemInteractively()
    

    注:文中内容90%来自官方文档。

    相关文章

      网友评论

          本文标题:CFNetwork入门

          本文链接:https://www.haomeiwen.com/subject/ahnmvttx.html