iOS開發之CFNetwork框架使用 原

iOS開發之CFNetwork框架使用

一、引言

    在iOS應用開發中,CFNetwork框架其實並不是非常常用的,相對NSURLSession框架而言,這是一個相對底層的網絡工作框架。官方文檔中的下圖描述了CFNetwork在整個網絡體系中的位置:

CFNetwork與CoreFoundation關係密切,其實基於CoreFoundation框架的,結構如下圖所示:

本篇博客中不會過多的設計CoreFoundation框架中的內容,主要總結和介紹CFNetwork的相關內容與簡單應用。

二、使用CFNetwork進行簡單的網絡請求

      CFNetwork是使用C語言實現的一套網絡訪問框架,進行一個簡單的網絡請求示例代碼如下:

//創建請求方法字符串    
CFStringRef method = CFSTR("GET");
//創建請求URL字符串
CFStringRef urlStr = CFSTR("http://www.baidu.com");
//創建請求URL對象
CFURLRef url = CFURLCreateWithString(kCFAllocatorDefault, urlStr, NULL);
//創建HTTP消息對象
CFHTTPMessageRef request = CFHTTPMessageCreateRequest(kCFAllocatorDefault, method, url, kCFHTTPVersion1_1);
//進行請求頭的設置
CFHTTPMessageSetHeaderFieldValue(request, CFSTR("key"), CFSTR("Value"));
//創建讀取流對象
CFReadStreamRef readStream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, request);
//定義讀取流上下文
CFStreamClientContext ctxt = {0, (__bridge void *)(self), NULL, NULL, NULL};
//設置讀取的客服端 即回調相關
CFReadStreamSetClient(readStream, kCFStreamEventHasBytesAvailable|kCFStreamEventEndEncountered|kCFStreamEventOpenCompleted|kCFStreamEventCanAcceptBytes|kCFStreamEventErrorOccurred, myCallBack, &ctxt);
//將讀取流加入runloop中
CFReadStreamScheduleWithRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
//開啓流
printf("%d",CFReadStreamOpen(readStream));

實現myCallBack回調函數如下:

void myCallBack (CFReadStreamRef stream,CFStreamEventType type,void *clientCallBackInfo){
    //流中有可讀數據的回調  
    if (type == kCFStreamEventHasBytesAvailable) {      
        //將流中的數據存入到數組中
        UInt8 buff [1024];
        CFReadStreamRead(stream, buff, 1024);
        printf("%s",buff);
    //流打開完成的回調
    }else if(type==kCFStreamEventOpenCompleted){
        NSLog(@"open");
    //流異常的回調
    }else if (type==kCFStreamEventErrorOccurred){
        NSLog(@"error:%@",CFErrorCopyDescription( CFReadStreamCopyError(stream)));
    //可以接收寫數據時調用
    }else if (type==kCFStreamEventCanAcceptBytes){
        NSLog(@"kCFStreamEventCanAcceptBytes");
    //讀取結束回調
    }else if(type==kCFStreamEventEndEncountered){
        NSLog(@"end");
        //關閉流
        CFReadStreamClose(stream);
        //將流從runloop中移除
        CFReadStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
    }
}

上面演示了簡單的GET請求,如果使用的請求方法爲POST,則可以進行請求體的設置,上面示例代碼中,CFStringRef、CFURLRef、CFReadStreamRef等相關的類爲CoreFoundation框架中的,這裏暫不深究,簡單使用即可。後面我們將詳細的探討CFNetwork中相關類的使用。

三、CFHTTPMessageRef詳解

    在基於C的框架中,類對象都是使用結構體指針描述的,CFHTTPMessageRef是HTTP消息的封裝,其可以是一個HTTP請求,也可以是一個HTTP回執。與其相關的方法解析如下:

//返回CGHTTPMessageRef的類型ID
CFTypeID CFHTTPMessageGetTypeID(void);
//創建一個HTTP請求消息
/*
alloc爲內存管理器 一般使用默認的kCFAllocatorDefault
requestMethod爲請求方法
url爲請求的路徑
httpVersion爲請求的HTTP版本
HTTP版本定義如下:
kCFHTTPVersion1_0
kCFHTTPVersion1_1
kCFHTTPVersion2_0
*/
CFHTTPMessageRef CFHTTPMessageCreateRequest(CFAllocatorRef __nullable alloc, CFStringRef requestMethod, CFURLRef url, CFStringRef httpVersion);
//創建一個HTTP回執消息
/*
alloc內存管理器
statusCode 請求回執狀態碼
statusDescription 請求回執狀態描述
httpVersion HTTP版本號
*/
CFHTTPMessageRef CFHTTPMessageCreateResponse(CFAllocatorRef  __nullable alloc,CFIndex         statusCode,CFStringRef     __nullable statusDescription,CFStringRef     httpVersion);
//創建一個空的HTTP消息 
/*
isRequest 如果傳入kCFBooleanTrue 則爲請求類型 否則爲回執類型
*/
CFHTTPMessageRef CFHTTPMessageCreateEmpty(CFAllocatorRef __nullable alloc, Boolean isRequest);
//複製一個HTTP消息
CFHTTPMessageRef CFHTTPMessageCreateCopy(CFAllocatorRef __nullable alloc, CFHTTPMessageRef message);
//判斷一個HTTP消息是請求 還是 回執
Boolean CFHTTPMessageIsRequest(CFHTTPMessageRef message);
//獲取HTTP版本
CFStringRef CFHTTPMessageCopyVersion(CFHTTPMessageRef message);
//獲取消息體內容 請求體或者回執體
CFDataRef CFHTTPMessageCopyBody(CFHTTPMessageRef message);
//設置消息體內容
void CFHTTPMessageSetBody(CFHTTPMessageRef message, CFDataRef bodyData);
//獲取某個消息頭內容
CFStringRef CFHTTPMessageCopyHeaderFieldValue(CFHTTPMessageRef message, CFStringRef headerField);
//獲取所有消息頭字段
CFDictionaryRef CFHTTPMessageCopyAllHeaderFields(CFHTTPMessageRef message);
//設置消息頭
void CFHTTPMessageSetHeaderFieldValue(CFHTTPMessageRef message, CFStringRef headerField, CFStringRef __nullable value);
//向空消息中追加序列化的數據
Boolean CFHTTPMessageAppendBytes(CFHTTPMessageRef message, const UInt8 *newBytes, CFIndex numBytes);
//返回消息頭數據是否準備完成
Boolean CFHTTPMessageIsHeaderComplete(CFHTTPMessageRef message);
//將一個消息對象序列化成數據
CFDataRef CFHTTPMessageCopySerializedMessage(CFHTTPMessageRef message);
/*=================下面這些方法針對於請求類型的消息=====================*/
//獲取消息中的url
CFURLRef CFHTTPMessageCopyRequestURL(CFHTTPMessageRef request);
//獲取消息的請求方法
CFStringRef CFHTTPMessageCopyRequestMethod(CFHTTPMessageRef request);
//添加認證信息
Boolean CFHTTPMessageAddAuthentication(CFHTTPMessageRef   request,CFHTTPMessageRef   __nullable authenticationFailureResponse,CFStringRef        username,CFStringRef        password,CFStringRef        __nullable authenticationScheme,Boolean            forProxy);
/*=================下面這些方法針對於繪製類型的消息=====================*/
//獲取回執狀態碼
CFIndex CFHTTPMessageGetResponseStatusCode(CFHTTPMessageRef response);
//獲取回執狀態行信息
CFStringRef CFHTTPMessageCopyResponseStatusLine(CFHTTPMessageRef response);

四、進行請求與回調處理

    CFHTTPMessageRef的主要用途是構建出HTTP的請求或回執對象,請求的相關發起與回調方法都封裝在CFHTTPStream.h這個頭文件中,解析如下:

//通過一個HTTP請求創建一個讀取流對象
CFReadStreamRef CFReadStreamCreateForHTTPRequest(CFAllocatorRef __nullable alloc, CFHTTPMessageRef request);
//通過一個HTTP請求創建讀取流對象 但是請求的body會被忽略 取requestBody作爲請求體
CFReadStreamRef CFReadStreamCreateForStreamedHTTPRequest(CFAllocatorRef __nullable alloc, CFHTTPMessageRef requestHeaders, CFReadStreamRef requestBody);
//設置讀取流是否自動重定向
void CFHTTPReadStreamSetRedirectsAutomatically(CFReadStreamRef httpStream, Boolean shouldAutoRedirect);

五、關於請求的證書驗證

    有時,客戶端在向服務端進行請求時收到狀態爲401的回執,這時往往表明需要客戶端提供用戶憑證,在CFNetWork框架中,用戶憑證與證書驗證相關方法封裝在CFHTTPAuthentication.h頭文件中。解析如下:

//獲取CFHTTPAuthentication類ID
CFTypeID CFHTTPAuthenticationGetTypeID(void);
/*
通過一個401或者407的請求回執創建一個 用戶認證對象
*/
CFHTTPAuthenticationRef CFHTTPAuthenticationCreateFromResponse(CFAllocatorRef __nullable alloc, CFHTTPMessageRef response);
//獲取一個用戶認證對象是否有效
Boolean CFHTTPAuthenticationIsValid(CFHTTPAuthenticationRef auth, CFStreamError * __nullable error);
//獲取某個用戶認證對象是否是某個請求的
Boolean CFHTTPAuthenticationAppliesToRequest(CFHTTPAuthenticationRef auth, CFHTTPMessageRef request);
//獲取某個用戶認證是否必須有序進行
Boolean CFHTTPAuthenticationRequiresOrderedRequests(CFHTTPAuthenticationRef auth);
//使用給定的用戶名和密碼執行證書驗證方法
Boolean CFHTTPMessageApplyCredentials(CFHTTPMessageRef request,CFHTTPAuthenticationRef auth,CFStringRef  __nullable username,CFStringRef  __nullable	password,CFStreamError *   __nullable	error);
/*
此方法和上面方法作用一致 不同的是使用字典來進行用戶名和密碼的設置 字段的鍵如下:
kCFHTTPAuthenticationUsername 用戶名鍵
kCFHTTPAuthenticationPassword 密碼鍵
kCFHTTPAuthenticationAccountDomain 賬戶域
*/
Boolean CFHTTPMessageApplyCredentialDictionary(CFHTTPMessageRef request,CFHTTPAuthenticationRef   auth,CFDictionaryRef dict,CFStreamError * __nullable  error);
//返回用戶憑證的賬戶域
CFStringRef CFHTTPAuthenticationCopyRealm(CFHTTPAuthenticationRef auth);
//返回一組賬戶域
CFArrayRef CFHTTPAuthenticationCopyDomains(CFHTTPAuthenticationRef auth);
//返回用戶憑證的驗證方法
CFStringRef CFHTTPAuthenticationCopyMethod(CFHTTPAuthenticationRef auth);
//獲取用戶憑證驗證是否需要用戶名和密碼
Boolean CFHTTPAuthenticationRequiresUserNameAndPassword(CFHTTPAuthenticationRef auth);
//獲取是否需要賬戶域
Boolean CFHTTPAuthenticationRequiresAccountDomain(CFHTTPAuthenticationRef auth);

六、進行FTP協議的數據交換

    CFNetWork框架也支持與FTP協議的服務端進行數據交互,方法解析如下:

//根據URL創建FTP讀取流對象 用來進行文件下載
CFReadStreamRef CFReadStreamCreateWithFTPURL(CFAllocatorRef __nullable alloc, CFURLRef ftpURL);
//解析文件或目錄的格式化數據
CFIndex CFFTPCreateParsedResourceListing(CFAllocatorRef __nullable alloc, const UInt8 *buffer, CFIndex bufferLength, CFDictionaryRef __nullable *  __nullable parsed);
//根據URL創建一個FTP寫入流對象 用來進行文件上傳
CFWriteStreamRef CFWriteStreamCreateWithFTPURL(CFAllocatorRef __nullable alloc, CFURLRef ftpURL);

對於FTP寫入和讀取流來說,可以使用CFReadStreamSetProperty()函數或者CFWriteStreamSetProperty()函數來進行屬性的設置,可設置的屬性列舉如下:

kCFStreamPropertyFTPUserName //設置用戶名
kCFStreamPropertyFTPPassword //設置密碼
kCFStreamPropertyFTPUsePassiveMode //布爾值  設置是否被動模式
kCFStreamPropertyFTPResourceSize  //資源大小
kCFStreamPropertyFTPFileTransferOffset //記錄文件位置 用來斷點續傳
kCFStreamPropertyFTPAttemptPersistentConnection //是否重用連接
kCFStreamPropertyFTPProxy  //設置代理字典
kCFStreamPropertyFTPFetchResourceInfo //資源詳情字典
//下面爲代理字典中可以定義的鍵
kCFStreamPropertyFTPProxyHost  //代理主機
kCFStreamPropertyFTPProxyPort  //代理端口
kCFStreamPropertyFTPProxyUser  //代理用戶名
kCFStreamPropertyFTPProxyPassword //代理密碼
//下面是資源詳情字典中可以定義的鍵
kCFFTPResourceMode  //資源模式
kCFFTPResourceName //資源名
kCFFTPResourceOwne //資源所有者
kCFFTPResourceGroup //資源組
kCFFTPResourceLink  //資源鏈接
kCFFTPResourceSize  //資源尺寸
kCFFTPResourceType  //資源類型
kCFFTPResourceModDate //修改時間

七、主機地址相關操作

    CFNetWork中也封裝了與主機地址域名相關的操作方法,例如,我們可以通過域名進行DNS解析出IP地址,示例代碼如下:

#import <netinet/in.h>
#import <arpa/inet.h>
CFStringRef hostString = CFSTR("www.baidu.com");
CFHostRef host = CFHostCreateWithName(CFAllocatorGetDefault(), hostString);
CFHostStartInfoResolution(host, kCFHostAddresses, NULL);
CFArrayRef addresses = CFHostGetAddressing(host, NULL);
for (int i = 0; i<CFArrayGetCount(addresses); i++) {
    struct sockaddr_in * ip;
    ip = (struct sockaddr_in *)CFDataGetBytePtr(CFArrayGetValueAtIndex(addresses, i));
    printf("%s\n",inet_ntoa(ip->sin_addr));
}

CFHostRef對象操作相關方法解析如下:

//獲取類型ID
CFTypeID CFHostGetTypeID(void);
//根據域名創建CFHostRef對象
CFHostRef CFHostCreateWithName(CFAllocatorRef __nullable allocator, CFStringRef hostname);
/*
根據地址創建CFHostRef對象
addr參數爲sockaddr結構體數據
*/
CFHostRef CFHostCreateWithAddress(CFAllocatorRef __nullable allocator, CFDataRef addr);
//CFHostRef對象的複製
CFHostRef CFHostCreateCopy(CFAllocatorRef __nullable alloc, CFHostRef host);
//對指定主機進行信息預查找 返回值標明是否查找成功
Boolean CFHostStartInfoResolution(CFHostRef theHost, CFHostInfoType info, CFStreamError * __nullable error);
//獲取主機的地址列表 數組中爲sockaddr結構體數據
CFArrayRef CFHostGetAddressing(CFHostRef theHost, Boolean * __nullable hasBeenResolved);
//獲取主機名列表
CFArrayRef CFHostGetNames(CFHostRef theHost, Boolean * __nullable hasBeenResolved);
//獲取主機可達性信息
CFDataRef CFHostGetReachability(CFHostRef theHost, Boolean * __nullable hasBeenResolved);
//取消未完成的解析
/*
解析類型枚舉
typedef CF_ENUM(int, CFHostInfoType) {
  //地址
  kCFHostAddresses			  = 0,
  //主機名
  kCFHostNames				  = 1,
  //可達性信息
  kCFHostReachability		   = 2
};
*/
void CFHostCancelInfoResolution(CFHostRef theHost, CFHostInfoType info);
//設置客戶端回調
Boolean CFHostSetClient(CFHostRef theHost, CFHostClientCallBack __nullable clientCB, CFHostClientContext * __nullable clientContext);
//註冊進Runloop
void CFHostScheduleWithRunLoop(CFHostRef theHost, CFRunLoopRef runLoop, CFStringRef runLoopMode);
//從Runloop中註銷
void CFHostUnscheduleFromRunLoop(CFHostRef theHost, CFRunLoopRef runLoop, CFStringRef runLoopMode);

八、後續

    上面介紹的內容更多還是關於使用CFNetWork框架進行HTTP或FTP請求的相關方法,其實CFNetWork框架中還提供了複雜的Bonjour服務功能,其與CFNetService相關,這部分內容後面有時間再進行整理總結吧。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章