CFNetwork的介紹和使用

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)中的位置(圖片來源于官方文檔)。


image.png

由上圖可以看出目前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)

image.png

上圖也是官方文檔的圖片,描述了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ù)了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容