0%

iOS 网速监控 学习篇

       网络测速会在 App 性能里面经常提到。网络的好与坏影响了 App 的使用体验。因此监听网络状态,根据网络的状态来控制 App 的功能 或者 对应处理,能达到提升用户体验的目的,它也就成了 App 优化项的关键之一。当然性能还包括 App 体积,App 的热启动 & 冷启动 等等指标。

       先说下测速的需求,要在不增加额外的上传 & 下载的资源消耗,就能达到实时测速的目的。也就是得通过计算 App 现有业务的网络请求,获取的传输数据大小 & 请求时间,从而得出网速。

       关于网络测速,在进行准备到测试开发的时候(5月末),居然同时间发现已经有现成的文章 ,那就不重复写(还不是因为懒),内容原理差不多,甚至更详细。那就补充下准备的内容,后面的内容会有点偏离主题,不过都是为了网络请求准备了解的知识点:

前文

       网速关键就是:获取的传输数据大小 & 请求时间。

       通过 Charles 抓包工具,可以看到它有各种数值分析。其中就有 Size & Duration ,然后计算出 Speed。除了总的 Speed,它还区分了 Request Speed 和 Response Speed,也就是 Request & Response 有各自的 Data 和 Duration。

Size

       它有 TLS Handshake、Header、Cookies、Body、Uncompressed Body、Compression。从这里,留意到我们要计算的数据,即 Body,在传输的时候是进过压缩,所以真正传输的 data 大小并不是接口访问后得到的数据包大小,不同的 encode 《HTTP 协议中的数据压缩》,其压缩率都不同,从抓包的数据看出 gzip 大概的压缩率在70%到80%。还有其实传输的 data,虽然 Body 为主要占比,可是如果留意到 TLS Handshake,这是在 Duration 和 Size 都出现的。

Duration

       它包含了 DNS、Connect、TLS Handshake、Request、Response、Latency 的总和。

TLS Handshake,

       它其实就是使用的 HTTP 加入了验证 TLS / SSL 而形成 HTTPS。《HTTP 与 HTTPS 的区别》摘要:

       超文本传输协议HTTP协议被用于在Web浏览器和网站服务器之间传递信息,HTTP协议以明文方式发送内容,不提供任何方式的数据加密,如果攻击者截取了Web浏览器和网站服务器之间的传输报文,就可以直接读懂其中的信息,因此,HTTP协议不适合传输一些敏感信息,比如:信用卡号、密码等支付信息。

       为了解决HTTP协议的这一缺陷,需要使用另一种协议:安全套接字层超文本传输协议HTTPS,为了数据传输的安全,HTTPS在HTTP的基础上加入了SSL协议,SSL依靠证书来验证服务器的身份,并为浏览器和服务器之间的通信加密。

一、HTTP和HTTPS的基本概念

       HTTP:是互联网上应用最为广泛的一种网络协议,是一个客户端和服务器端请求和应答的标准(TCP),用于从WWW服务器传输超文本到本地浏览器的传输协议,它可以使浏览器更加高效,使网络传输减少。

       HTTPS:是以安全为目标的HTTP通道,简单讲是HTTP的安全版,即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。

       HTTPS协议的主要作用可以分为两种:一种是建立一个信息安全通道,来保证数据传输的安全;另一种就是确认网站的真实性。

       它也需要时间和数据交换,并且它不是每次都需要消耗,而是在频繁访问同个接口时候,它仍然处于链接的时候,是会简化数据,所以抓包发现它的时长有时长,有时短,数据包大小也是。

       使用《SSL/TLS 握手过程详解》进行握手之后,成功完成 HTTPS 的安全加密认证,后续的消息都将使用过程生成的秘钥进行加密,从而对 HTTP 的明文传输改为 HTTPS 的密文传输。接着就是 TCP 的握手《TCP 为什么是三次握手,而不是两次或四次?》 & 《面试题:三次握手、四次握手内容整理》 & 《三次握手》。

       加密:《互联网安全之数字签名、数字证书与PKI系统

总结

       了解到这里,Duration 可以是个估计值,有点误差。而 iOS 通过网络得到的 Size 是还原了数据包的二进制大小,而非传输的数据大小,由于 HTTPS 的签名压缩率的不同,所以计算 Size 的误差就更大了,当时写到这里就停了下,直到发现 **小鱼周凌宇 的 iOS 流量监控分析**,后续她还推荐了 ImageSaveWarehouse/iOS 网络流量监控 。通过她的方式模拟传输的数据包大小,从而更精准地得出 Duration & Size 的获取,计算 iOS 网速,并进行实时监控。当时这种模拟 zip & signature 是消耗 CPU,如果真的需要,可以针对部分接口来计算。避免太大的开销。

网络请求

NSURLConnection

       它是 iOS9 之前,最常使用的网络接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
+ (void)getCurrentNetIP {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *rcUrl = @"https://www.xxxx.com";
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:rcUrl] cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:8];
[request setHTTPMethod:@"GET"];
NSHTTPURLResponse* url_response = nil;
NSError *error = nil;
NSData *received = [NSURLConnection sendSynchronousRequest:request returningResponse:&url_response error:&error];
if (received != nil) {
NSString *aString = [[NSString alloc] initWithData:received encoding:NSUTF8StringEncoding];
//...
}
});
}
NSURLSession & NSURLSessionDataTask

       它是 iOS9 之后苹果推行的,并且 AFNetwork 3.0 版本也更新使用。

1
2
3
4
5
6
7
8
9
10
11
12
NSURL *url = [NSURL URLWithString:urlString];
NSURLSession *session = [NSURLSession sharedSession];

NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error == nil && data.length > 0) {
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
}
else {
NSString *errorString = @"";
}
}];
[dataTask resume];

参考:

NSOperation & NSOperationQueue

       上述两个都有下载 & 上传文件的接口,而 Operation 也可以针对文件进行下载。这一部分其实早期写开发下载模块的时候有使用过。

参考:

GCDWebServer

       本地服务,可用于 task 一边下载视频文件,然后开启本地服务,为播放器提供本地的视频流,从而达到一边下载一边播放的功能。[TODO]

参考:

NSURLProtocol

       以上是 iOS 的网络请求提供的高级接口和对象。它们能满足我们客户端在请求获取数据。如前文说说,真正的 Speed 其实按开头的想法计算是不准确的,有很大的误差,但是换个角度,如果 App 内部使用同一套规则计算,其实它的网速快慢就是相对的,所以它对于 Charles 的数据的“误差”也就可以忽略。只要我们按获取的数据包大小比请求到消耗的时间来定义快慢标准就可以了。因此,继续这个话题引用 NSURLProtocol 《iOS H5容器的一些探究(二):iOS下的黑魔法NSURLProtocol》,使用它其实等同于在每个请求的接口 task 的回调来计算是一样的,它的优点是覆盖了所有的基于系统的 NSURLConnection 或者 NSURLSession 进行封装的网络请求,包括 UIWebView 。

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void) connectionDidFinishLoading:(NSURLConnection *)connection {
CGFloat endTime = CFAbsoluteTimeGetCurrent();
CGFloat useTime = endTime - self.startTime;
NSLog(@"chen--useTime--%f", useTime);

CGFloat sizeB = connection.currentRequest.HTTPBody.length + self.totalSize;
CGFloat kb = sizeB/1024.f;
NSLog(@"chen--data--%f", kb);

NSLog(@"chen--speed--%f", kb/useTime);

[self.client URLProtocolDidFinishLoading:self];
}

其他网络测试

       说起网络测试,肯定看过 Reachability 和 SimplePing,这两个都是苹果开源的工具。Github 上有多个扩展版本。

Reachability

       Reachability 就不在细说,它可以检查当前网络 和 实时监控网络的切换情况,自己的项目也是针对它再次修改,细化到判断当前网络的类型,如 4G、3G 等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 开启
- (void)p_startNetReachable:(BOOL)bEable {
dispatch_async(dispatch_get_main_queue(), ^{
if (bEable == YES) {
self.reachability = [QHReachability reachabilityForInternetConnection];
if (self.currentStatus != [self.reachability currentReachabilityStatus]) {
self.currentStatus = [self.reachability currentReachabilityStatus];
[self p_netReachable:self.currentStatus];
}
[self.reachability startNotifier];
}
else {
if (self.reachability != nil) {
[self.reachability stopNotifier];
self.reachability = nil;
}
}
});
}
// 监听
// 添加
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(qhReachabilityChangedAction:) name:kQHReachabilityChangedNotification object:nil];
// 移除
[[NSNotificationCenter defaultCenter] removeObserver:self name:kQHReachabilityChangedNotification object:nil];
// Action
- (void)qhReachabilityChangedAction:(NSNotification *)notif {
QHReachability *curReach = [notif object];
NetworkStatus status = [curReach currentReachabilityStatus];
if (self.currentStatus == status)
return;
self.currentStatus = status;
[self p_netReachable:self.currentStatus];
}
SCNetworkReachability

       文章讲解了内部都是使用 SCNetworkReachability用SCNetworkReachability判断联网状态》 来实现的。

       也可以使用《iOS Reachability检测网络状态》中介绍了 AFNetworkReachabilityManager。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#import "NetworkUtil.h"
#import <SystemConfiguration/SCNetworkReachability.h>
#import <netdb.h>

@implementation NetworkUtil

+ (BOOL)isNetworkReachable {
// Create zero addy
//创建零地址,0.0.0.0的地址表示查询本机的网络连接状态
struct sockaddr_in zeroAddress;
bzero(&zeroAddress, sizeof(zeroAddress));
zeroAddress.sin_len = sizeof(zeroAddress);
zeroAddress.sin_family = AF_INET;

// Recover reachability flags
// SCNetworkReachabilityFlags:保存返回的测试连接状态
// 其中常用的状态有:
// kSCNetworkReachabilityFlagsReachable:能够连接网络
// kSCNetworkReachabilityFlagsConnectionRequired:能够连接网络,但是首先得建立连接过程
// kSCNetworkReachabilityFlagsIsWWAN:判断是否通过蜂窝网覆盖的连接,比如EDGE,GPRS或者目前的3G.主要是区别通过WiFi的连接。
SCNetworkReachabilityRef defaultRouteReachability = SCNetworkReachabilityCreateWithAddress(NULL,
(struct sockaddr*)&zeroAddress);
SCNetworkReachabilityFlags flags;

BOOL didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags);
CFRelease(defaultRouteReachability);

if (!didRetrieveFlags) {
return NO;
}

BOOL isReachable = flags & kSCNetworkFlagsReachable;
BOOL needsConnection = flags & kSCNetworkFlagsConnectionRequired;
return (isReachable && !needsConnection) ? YES : NO;
}

@end
SimplePing

       常用的是 STSimplePing.h 这个版本,它扩展了一个每个 syn 之后响应所消耗的时间。通过它我们也可以判断当前网络的状态。但是回顾测试的需求,它是消耗额外资源的方式,也不建议实时使用。它通过 ping 下域名的延迟及时长来分析网络状态。

1
2
3
4
5
6
7
8
9
10
11
self.simplePing = [[STSimplePing alloc] initWithHostName:@"www.baidu.com"];
self.simplePing.delegate = self;
[self.simplePing start];

#pragma mark - STSimplePingDelegate

- (void)st_simplePing:(STSimplePing *)pinger didReceivePingResponsePacket:(NSData *)packet timeToLive:(NSInteger)timeToLive sequenceNumber:(uint16_t)sequenceNumber timeElapsed:(NSTimeInterval)timeElapsed {
[self.simplePing stop];
NSLog(@"%s", __FUNCTION__);
NSLog(@"simplePing==%f", timeElapsed);//0.078942
}

参考:

网卡

       上述参考中还记录了获取其他数据的功能及不仅仅使用 SimplePing,还使用网卡来进行计算。网卡的这部分只做记录,因为在计算网速上,它无法测出满载,即当前设备网络的真正网速。它的用途更在于记录消耗的流量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- (long long)getDeviceCurrentBytesCount {
struct ifaddrs* addrs;
const struct ifaddrs* cursor;

long long currentBytesValue = 0;

if (getifaddrs(&addrs) == 0) {
cursor = addrs;
while (cursor != NULL) {
const struct if_data* ifa_data = (struct if_data*)cursor->ifa_data;
if (ifa_data) {
// total number of octets received
int receivedData = ifa_data->ifi_ibytes;

currentBytesValue += receivedData;
}
cursor = cursor->ifa_next;
}
}
freeifaddrs(addrs);

NSLog(@"BytesCount:%lld",currentBytesValue);
return currentBytesValue;
}
tcpdump

       它在 GT 中使用过。GT 是腾讯开源的项目,主要是针对 APP 实时的性能测试记录,包含 CPU、电池等消耗。而其中也包含了网络测速,它使用的就是本次要说明的 tcpdump ,文档其实也就简单描述

tcpdump 抓 包 手 机 需 要 越 狱 , 且 暂 时 只 支 持 iOS6 。 抓 包 保 存 目 录 :
Documents/GT/Plugin/pcap/

       并没有将 tcpdump 的代码开源处理,因此无法使用。

       tcpdump《抓包工具tcpdump用法说明》,它其实是 PC 系统常用的抓包命令《通过tcpdump对iOS进行流量分析(无需越狱) 》里面介绍了结合 PC 工具进行抓包,而越狱的使用是通过在 cydia 里安装 openssh,tcpdump 两个工具之后使用手机终端操作,而 App 开发调用的方法暂无资料《利用tcpdump抓取ios的tcp数据包》。