1. 程式人生 > >iOS開發之CFNetwork框架使用

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相關,這部分內容後面有時間再進行整理總結吧。

歡迎交流  琿少 QQ 316045346