前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >CFNetwork 的介绍和使用

CFNetwork 的介绍和使用

作者头像
molier
发布2022-11-02 14:08:10
1.4K0
发布2022-11-02 14:08:10
举报
文章被收录于专栏:Molier的小站

# CFNetwork 背景简介

CFNetwork 是 ISO 中一个比较底层的网络框架,C 语言编写,可以控制一些更底层的东西,如各种常用网络协议、socket 通讯等,我们通常使用的 NSURL 则更倾向于 API 数据请求等,虽然框架也提供了一些操作,但是远不如 CFNetwork 丰富。CFNetwork 已经接近于 UNIX 系统的 socket 通信了,使用 CFHttpMessageRef 进行 HTTP 连接的好处就是控制的粒度更细了,例如你可以设置 SSL 连接的 PeerName,证书验证的方式,还可以控制每个响应包的接收。不过 CFNetwork 本质上还是应用层上的封装的通用 API。使用者可以不用关心底层协议的实际细节。下图是 CFNetwork 在 iOS 系统中的位置 (图片来源于官方文档)。

image.png
image.png

由上图可以看出目前 iOS 的网络编程分四层:

  • WebKit:属于 Cocoa 层,苹果很多地方用到的页面渲染引擎 WKWebview;
  • NSURL:也属于 Cocoa 层,对各类 URL 请求的封装 (NSURLRequest);
  • CFNetwork:属于 Core Foundation 层,基于 C 的封装,同样的还有 CFNetServices (write/readstream);
  • BSD sockets:属于 OS 层,也是基于 C 的封装;

# CFNetwork 结构

image.png
image.png

上图也是官方文档的图片,描述了 CFNetwork 的结构,下面逐一讲解。

# CFSocket API

Socket 是网络通讯的底层基础,两个 socket 端口可以互发数据。我们通常使用的是 BSD socket,CFSocket 则是 BSD socket 的抽象,基本上实现了几乎所有 BSD socket 的功能,并且还融入了 run loop。

# CFStream API

CFStream API 提供了数据读写的方法,即读写流,使用它可以为内存、文件、网络(使用 socket)的数据建立 stream,我们进行网络请求就是对数据的读写,CFStream 提供 API 对两种 CFType 对象提供抽象:CFReadStream and CFWriteStream。它同时也是 CFHTTP 和 CFFTP 的基础。stream 有一个很重要的特性就是一旦数据流被提供或者被消耗,就不能从流中重新取出。比如这样

代码语言:javascript
复制
uint8_t d[1024] = {0};
//循环条件:流中是否有可用数据(被读过的数据不可用了)
while ([self.inputStream hasBytesAvailable]) {
    //读取相应长度的数据数据
    NSInteger len = [self.inputStream read:d maxLength:1024];
    //如果读取到数据,便将数据快拼接
    if (len > 0 && !self.inputStream.streamError) {
        [data appendBytes:(void *)d length:len];
    } else {
        break;
    }
}
# CFFTP API

对用 FTP 协议通信的封装,能下载、上传文件和目录到 FTP 服务器。CFFTP 建立的连接可以是同步或者异步,此次不做详解。

# CFHTTP API

是 HTTP 协议的抽象,主要对象是 CFHTTPMessageRef (类似于我们通常的 NSURLRequest) 我们需要像构建 NSURLRequest 那样来构建 CFHTTPMessageRef,同样包含一下几个元素

  • 必须元素
    • 请求方法 (类型为 CFStringRef):POST、GET、DELETE 等..
    • 请求的 URL 地址 (类型为 CFURLRef):https://www.baidu.com
    • 请求的 HTTP 版本 (类型为 CFStringRef):通常使用 kCFHTTPVersion1_1
    • kCFAllocatorDefault:用于创建消息引用的指定默认的系统内存分配器。
  • 可选参数
    • body 体 (类型为 CFDataRef)
代码语言:javascript
复制

CFHTTPMessageSetBody(CFHTTPMessageRef message, CFDataRef bodyData) CF_AVAILABLE(10_1, 2_0);
  • 消息头部,如 User-Agent 等;
代码语言:javascript
复制
CFHTTPMessageSetHeaderFieldValue(CFHTTPMessageRef message, CFStringRef headerField, CFStringRef __nullable value) CF_AVAILABLE(10_1, 2_0);

# CFNetwork 请求过程

# 1:构造并创建 CFHTTPMessageRef 对象
代码语言:javascript
复制
//构造的方式上一步已讲
CFHTTPMessageCreateRequest(CFAllocatorRef __nullable alloc, CFStringRef requestMethod, CFURLRef url, CFStringRef httpVersion) CF_AVAILABLE(10_1, 2_0);
# 2:使用 CFHTTPMessageRef 对象创建输入流
代码语言:javascript
复制
//第一个参数传默认
CFReadStreamCreateForHTTPRequest(CFAllocatorRef __nullable alloc, CFHTTPMessageRef request) CF_DEPRECATED(10_2, 10_11, 2_0, 9_0, "Use NSURLSession API for http requests");
# 3:适配 SNI 环境(一个 IP 地址上可以为不同域名分配使用不同的 SSL 证书;这同时意味着,共享 IP 的虚拟主机也可实现 SSL/TLS 连接。)

因为配置 sni 环境的所有配置都是基于输入流来操作,所以我们构建完成输入流之后来处理 sni,像这样

代码语言:javascript
复制
[self.inputStream setProperty:NSStreamSocketSecurityLevelNegotiatedSSL forKey:NSStreamSocketSecurityLevelKey];
//请求的URL的Host
NSDictionary *sslProperties = @{ (__bridge id) kCFStreamSSLPeerName : host };
[self.inputStream setProperty:sslProperties forKey:(__bridge_transfer NSString *) kCFStreamPropertySSLSettings];
# 4:打开输入流

打开输入流分为两步

设置代理:[self.inputStream setDelegate:weakSelf]

加入当前的 runloop:

代码语言:javascript
复制
[_inputStream removeFromRunLoop:self.runloop forMode:[self runloopMode]];

调用 Open 方法

# 5:收到代理数据回调
代码语言:javascript
复制
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode;

其中分为几个状态

代码语言:javascript
复制
typedef NS_OPTIONS(NSUInteger, NSStreamEvent) {
    NSStreamEventNone = 0,
    NSStreamEventOpenCompleted = 1UL << 0,
    NSStreamEventHasBytesAvailable = 1UL << 1,
    NSStreamEventHasSpaceAvailable = 1UL << 2,
    NSStreamEventErrorOccurred = 1UL << 3,
    NSStreamEventEndEncountered = 1UL << 4
};

通常我们会关心 NSStreamEventOpenCompleted、NSStreamEventHasBytesAvailable、NSStreamEventErrorOccurred、

由于数据是以流的形式回来,我们需要在在 NSStreamEventHasBytesAvailable 下取出数据然后做数据拼接,拼接好完整的数据才可使用,像这样

代码语言:javascript
复制
 case NSStreamEventHasBytesAvailable:
{
    UInt8 buffer[BUFFER_SIZE]; //设置缓存区
    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;

循环结束后我们的 resultData 就是完整的返回数据了。

网络 底层

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018-01-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • # CFNetwork 背景简介
  • # CFNetwork 结构
    • # CFSocket API
      • # CFStream API
        • # CFFTP API
          • # CFHTTP API
          • # CFNetwork 请求过程
            • # 1:构造并创建 CFHTTPMessageRef 对象
              • # 2:使用 CFHTTPMessageRef 对象创建输入流
                • # 3:适配 SNI 环境(一个 IP 地址上可以为不同域名分配使用不同的 SSL 证书;这同时意味着,共享 IP 的虚拟主机也可实现 SSL/TLS 连接。)
                  • # 4:打开输入流
                    • # 5:收到代理数据回调
                    相关产品与服务
                    SSL 证书
                    腾讯云 SSL 证书(SSL Certificates)为您提供 SSL 证书的申请、管理、部署等服务,为您提供一站式 HTTPS 解决方案。
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档