3f687d662be9c33168acac73e9f4f778
App 网络监控

我们有时候需要监控 App 的网络各个阶段的状态,以此来定位 App 中哪些页面或者哪些 url 的请求比较慢,进而对症下药地优化 App 的网络速度。

其实,App 网络监控是属于APM(Application performance management)的一个子话题,有兴趣的可以顺带去了解一下APM,这里暂按下不表,我有机会也会单独写篇文章来探讨一下。

目录

  1. App网络请求过程
  2. App网络监控原理
  3. Show Me the Code
  4. 优缺点

1. App网络请求过程

App发送一次网络请求一般而言会经历下面几个步骤:

(1) DNS 解析

容易理解,就是找到域名对应的IP地址。首先会查询本地DNS缓存,查找失败就去DNS服务器查询,这其中可能经过很多节点,涉及到递归查询和迭代查询的过程。值得一提的是现在有很多运营商非常的恶心,除了有可能劫持你的请求然后加上广告外,还可能把你的请求丢给一个很远的基站去做DNS解析,这就导致我们的App的DNS解析耗时比较长。我们可以尝试用自己的CDN服务器做DNS缓存。

(2) TCP 3次握手

这个也很好理解。关于3次握手的过程以及为什么是3次握手我想大家都很熟悉了,在此不浪费流量。最终建立了TCP的可靠连接。

(3) TLS握手

对于https请求还要做TLS的握手,实际上就是协商加密秘钥的过程。关于这个过程在网络上也有大量的资料可以参考。最终建立了安全的连接通道。

(4) 发起请求
连接建立好后就可以发送request,此时我们可以记录下request start的时间。

(5) 等待回应
等待服务器返回响应。这个时间就取决于请求资源的大小,也是整个网络请求过程中容易耗时的阶段。

(6)返回响应
服务端返回响应给客户端。根据http header中的状态码可以看出本次请求是否成功以及是否需要读取本地缓存数据或者重定向请求。

用一张图直观地看这整个过程:
图片来自网络

图片来自网络

2. 监控原理

目前业界对于App的监控主要有两种,一种通过子类化NSURLProtocol、一种是使用Hook技术。我们本次先讲简单的前者。
实际上,我在拦截 App 网络请求的那些事儿那篇文章里已经讲解了如何子类化实现一个NSURLProtocol,监控网络就是基于这个技术,即在拦截网络请求的基础上统计各个网络阶段的状态信息。
iOS10之后,Apple在NSURLSessionTaskDelegate中增加了一个新的方法:

/*
 * Sent when complete statistics information has been collected for the task.
 */
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));

可以从回调的NSURLSessionTaskMetrics类型的参数中获取网络的各项指标。该类型定义如下:

API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0))
@interface NSURLSessionTaskMetrics : NSObject
@property (copy, readonly) NSArray<NSURLSessionTaskTransactionMetrics *> *transactionMetrics;
@property (copy, readonly) NSDateInterval *taskInterval;
@property (assign, readonly) NSUInteger redirectCount;

-(instancetype)init;
@end

其中,
taskInterval表示任务从创建到完成花费的总时间,任务的创建时间是任务被实例化时的时间;任务完成时间是任务的内部状态将要变为完成的时间;
redirectCount表示被重定向的次数;
transactionMetrics数组包含了任务执行过程中每个请求/响应事务中收集的指标,具体的指标包含如下:

API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0))
@interface NSURLSessionTaskTransactionMetrics : NSObject
//请求事务
@property (copy, readonly) NSURLRequest *request;
//响应事务
@property (nullable, copy, readonly) NSURLResponse *response;
//客户端开始请求的时间,无论资源是从服务器还是本地缓存中获取
@property (nullable, copy, readonly) NSDate *fetchStartDate;
//DNS 解析开始时间
@property (nullable, copy, readonly) NSDate *domainLookupStartDate;
//DNS 解析完成时间
@property (nullable, copy, readonly) NSDate *domainLookupEndDate;
//客户端与服务器开始建立 TCP 连接的时间
@property (nullable, copy, readonly) NSDate *connectStartDate;
//HTTPS 的 TLS 握手开始时间
@property (nullable, copy, readonly) NSDate *secureConnectionStartDate;
//HTTPS 的 TLS 握手结束时间。
@property (nullable, copy, readonly) NSDate *secureConnectionEndDate;
//客户端与服务器建立 TCP 连接完成时间,包括 TLS 握手时间
@property (nullable, copy, readonly) NSDate *connectEndDate;
//客户端开始请求的时间,可以理解为开始传输 HTTP 请求的 header 第一个字节的时间。
@property (nullable, copy, readonly) NSDate *requestStartDate;
//客户端请求结束的时间,可以理解为HTTP 请求最后一个字节传输完成的时间
@property (nullable, copy, readonly) NSDate *requestEndDate;
//客户端从服务器接收到响应的第一个字节的时间。
@property (nullable, copy, readonly) NSDate *responseStartDate;
//客户端从服务器接收到最后一个字节的时间。
@property (nullable, copy, readonly) NSDate *responseEndDate;
//网络协议名,如http/1.1 、h2(http/2)
@property (nullable, copy, readonly) NSString *networkProtocolName;
//是否使用了代理
@property (assign, readonly, getter=isProxyConnection) BOOL proxyConnection;
//是否复用现有连接
@property (assign, readonly, getter=isReusedConnection) BOOL reusedConnection;
//资源获取来源
@property (assign, readonly) NSURLSessionTaskMetricsResourceFetchType resourceFetchType;

-(instancetype)init;
@end

那么我们就可以利用NSURLProtocol拦截请求时,发送NSURLSessionDataTask请求,然后在这个代理方法中统计网络流量。

3. Show Me the Code

为了方便我们上传监控信息,我们自定义监控信息与错误信息的数据结构:

监控信息基类

@interface  NetworkMonitorBaseDataModel : NSObject
// 请求的 URL 地址
@property (nonatomic, strong) NSString *requestUrl;
//请求头
@property (nonatomic, strong) NSArray *requestHeaders;
//响应头
@property (nonatomic, strong) NSArray *responseHeaders;
//GET方法 的请求参数
@property (nonatomic, strong) NSString *getRequestParams;
//HTTP 方法, 比如 POST
@property (nonatomic, strong) NSString *httpMethod;
//协议名,如http1.0 / http1.1 / http2.0
@property (nonatomic, strong) NSString *httpProtocol;
//是否使用代理
@property (nonatomic, assign) BOOL useProxy;
//DNS解析后的 IP 地址
@property (nonatomic, strong) NSString *ip;
@end

监控信息数据结构:
```
@interface NetworkMonitorDataModel : NetworkMonitorBaseDataModel
//客户端发起请求的时间
@property (nonatomic, assign) UInt64 requestDate;
//客户端开始请求到开始dns解析的等待时间,单位ms
@property (nonatomic, assign) int waitDNSTime;
//DNS 解析耗时

top Created with Sketch.