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