CFNetwork背景簡介
CFNetwork是ISO中一個比較底層的網(wǎng)絡(luò)框架,C語言編寫,可以控制一些更底層的東西,如各種常用網(wǎng)絡(luò)協(xié)議、socket通訊等,我們通常使用的NSURL則更傾向于API數(shù)據(jù)請求等,雖然框架也提供了一些操作,但是遠不如CFNetwork豐富。CFNetwork已經(jīng)接近于UNIX系統(tǒng)的socket通信了,使用CFHttpMessageRef進行HTTP連接的好處就是控制的粒度更細了,例如你可以設(shè)置SSL連接的PeerName,證書驗證的方式,還可以控制每個響應(yīng)包的接收。不過CFNetwork本質(zhì)上還是應(yīng)用層上的封裝的通用API。使用者可以不用關(guān)心底層協(xié)議的實際細節(jié)。下圖是CFNetwork在iOS系統(tǒng)中的位置(圖片來源于官方文檔)。

由上圖可以看出目前iOS的網(wǎng)絡(luò)編程分四層:
WebKit:屬于Cocoa層,蘋果很多地方用到的頁面渲染引擎WKWebview;
NSURL:也屬于Cocoa層,對各類URL請求的封裝(NSURLRequest);
CFNetwork:屬于Core Foundation層,基于C的封裝,同樣的還有CFNetServices(write/readstream);
BSD sockets:屬于OS層,也是基于C的封裝;
CFNetwork結(jié)構(gòu)

上圖也是官方文檔的圖片,描述了CFNetwork的結(jié)構(gòu),下面逐一講解。
CFSocket API
Socket是網(wǎng)絡(luò)通訊的底層基礎(chǔ),兩個socket端口可以互發(fā)數(shù)據(jù)。我們通常使用的是BSD socket,CFSocket則是BSD socket的抽象,基本上實現(xiàn)了幾乎所有BSD socket的功能,并且還融入了run loop。
CFStream API
CFStream API提供了數(shù)據(jù)讀寫的方法,即讀寫流,使用它可以為內(nèi)存、文件、網(wǎng)絡(luò)(使用socket)的數(shù)據(jù)建立stream,我們進行網(wǎng)絡(luò)請求就是對數(shù)據(jù)的讀寫,CFStream提供API對兩種CFType對象提供抽象:CFReadStream and CFWriteStream。它同時也是CFHTTP和CFFTP的基礎(chǔ)。stream有一個很重要的特性就是一旦數(shù)據(jù)流被提供或者被消耗,就不能從流中重新取出。比如這樣
uint8_t d[1024] = {0};
//循環(huán)條件:流中是否有可用數(shù)據(jù)(被讀過的數(shù)據(jù)不可用了)
while ([self.inputStream hasBytesAvailable]) {
//讀取相應(yīng)長度的數(shù)據(jù)數(shù)據(jù)
NSInteger len = [self.inputStream read:d maxLength:1024];
//如果讀取到數(shù)據(jù),便將數(shù)據(jù)快拼接
if (len > 0 && !self.inputStream.streamError) {
[data appendBytes:(void *)d length:len];
} else {
break;
}
}
CFFTP API
對用FTP協(xié)議通信的封裝,能下載、上傳文件和目錄到FTP服務(wù)器。CFFTP建立的連接可以是同步或者異步,此次不做詳解。
CFHTTP API
是HTTP協(xié)議的抽象,主要對象是CFHTTPMessageRef(類似于我們通常的NSURLRequest)我們需要像構(gòu)建NSURLRequest那樣來構(gòu)建CFHTTPMessageRef,同樣包含一下幾個元素
-
必須元素
請求方法 (類型為CFStringRef):POST、GET、DELETE等..
請求的URL地址 (類型為CFURLRef):https://www.baidu.com
請求的HTTP版本(類型為CFStringRef):通常使用kCFHTTPVersion1_1
kCFAllocatorDefault:用于創(chuàng)建消息引用的指定默認的系統(tǒng)內(nèi)存分配器。
-
可選參數(shù)
- body體(類型為CFDataRef)
CFHTTPMessageSetBody(CFHTTPMessageRef message, CFDataRef bodyData) CF_AVAILABLE(10_1, 2_0);
- 消息頭部,如User-Agent等;
CFHTTPMessageSetHeaderFieldValue(CFHTTPMessageRef message, CFStringRef headerField, CFStringRef __nullable value) CF_AVAILABLE(10_1, 2_0);
CFNetwork請求過程
1:構(gòu)造并創(chuàng)建CFHTTPMessageRef對象
//構(gòu)造的方式上一步已講
CFHTTPMessageCreateRequest(CFAllocatorRef __nullable alloc, CFStringRef requestMethod, CFURLRef url, CFStringRef httpVersion) CF_AVAILABLE(10_1, 2_0);
2:使用CFHTTPMessageRef對象創(chuàng)建輸入流
//第一個參數(shù)傳默認
CFReadStreamCreateForHTTPRequest(CFAllocatorRef __nullable alloc, CFHTTPMessageRef request) CF_DEPRECATED(10_2, 10_11, 2_0, 9_0, "Use NSURLSession API for http requests");
3:適配SNI環(huán)境(一個 IP 地址上可以為不同域名分配使用不同的 SSL 證書;這同時意味著,共享 IP 的虛擬主機也可實現(xiàn) SSL/TLS 連接。)
因為配置sni環(huán)境的所有配置都是基于輸入流來操作,所以我們構(gòu)建完成輸入流之后來處理sni,像這樣
[self.inputStream setProperty:NSStreamSocketSecurityLevelNegotiatedSSL forKey:NSStreamSocketSecurityLevelKey];
//請求的URL的Host
NSDictionary *sslProperties = @{ (__bridge id) kCFStreamSSLPeerName : host };
[self.inputStream setProperty:sslProperties forKey:(__bridge_transfer NSString *) kCFStreamPropertySSLSettings];
4:打開輸入流
打開輸入流分為兩步
- 設(shè)置代理:[self.inputStream setDelegate:weakSelf]
- 加入當(dāng)前的runloop:
[_inputStream removeFromRunLoop:self.runloop forMode:[self runloopMode]];
- 調(diào)用Open方法
5:收到代理數(shù)據(jù)回調(diào)
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode;
其中分為幾個狀態(tài)
typedef NS_OPTIONS(NSUInteger, NSStreamEvent) {
NSStreamEventNone = 0,
NSStreamEventOpenCompleted = 1UL << 0,
NSStreamEventHasBytesAvailable = 1UL << 1,
NSStreamEventHasSpaceAvailable = 1UL << 2,
NSStreamEventErrorOccurred = 1UL << 3,
NSStreamEventEndEncountered = 1UL << 4
};
通常我們會關(guān)心NSStreamEventOpenCompleted、NSStreamEventHasBytesAvailable、NSStreamEventErrorOccurred、
由于數(shù)據(jù)是以流的形式回來,我們需要在在NSStreamEventHasBytesAvailable下取出數(shù)據(jù)然后做數(shù)據(jù)拼接,拼接好完整的數(shù)據(jù)才可使用,像這樣
case NSStreamEventHasBytesAvailable:
{
UInt8 buffer[BUFFER_SIZE]; //設(shè)置緩存區(qū)
NSInteger numBytesRead = 0;
NSInputStream *inputstream = (NSInputStream *) aStream;
// Read data
do {
numBytesRead = [inputstream read:buffer maxLength:sizeof(buffer)];
if (numBytesRead > 0) {
[self.resultData appendBytes:buffer length:numBytesRead];
}
} while (numBytesRead > 0);
}
break;
循環(huán)結(jié)束后我們的resultData就是完整的返回數(shù)據(jù)了。