您的位置:首页 > 其它

苹果示例源码阅读:SimplePing(1)

2016-11-08 09:05 761 查看

前言

手机网络连接状态的检测对于 iOS App 开发来说是一个非常基础的需求,在前一篇文章 苹果示例源码阅读:Reachability 我们介绍了如何通过 SCNetworkReachability 提供的一系列 C 函数 API 进行网络连接状态变化的监听。但事实上,此方案能获取的只是设备的本地连接状态,有时它很难为我们检测真正的网络连接状态,如以下场景:

现在很多的公共场所的 WiFi,需要网页登录授权,授权之前无法上网,但本地连接已经建立;

存在了本地网络连接,但信号很差,实际无法连接到服务器;

iOS 连接的路由设备本身没有连接外网等。

Ping

ping 是 Windows、Unix 、Linux 和 macOS 等系统下一个常用的命令,利用 ping 命令可以用来测试数据包 (ICMP) 能否通过 IP 协议到达特定主机,并收到主机的应答,以检查网络是否连通和网络连接速度,帮助我们分析和判定网络故障。

幸运的是,苹果为我们提供了示例源码:SimplePing,示范了在 iOS 或者 Mac 上如何用 Objective-C / Swift 实现 ping 操作,因此我们也可以通过 ping 来检查手机网络的真实连接状态。事实上,Github 上著名的第三方开源库 RealReachability 也是这么做的。

SimplePing 源码阅读

对于 SimplePing 源码的阅读,我们将分为两部分来介绍。第一部分将结合 SimplePing.h 头文件里声明的方法,介绍如何使用 SimplePing 类封装的方法进行 ping 操作,第二部分(下一篇)将详细介绍 SimplePing.m 里各方法的具体实现细节。

类结构

通过 SimplePing.h 头文件中的声明,我们整理 SimplePing 的类结构如下图所示:



?

下面我们一一介绍 SimplePing 类的各个属性、方法以及 delegate 回调方法的含义及作用。

初始化方法

12- (instancetype)init NS_UNAVAILABLE;- (instancetype)initWithHostName:(NSString *)hostName NS_DESIGNATED_INITIALIZER;
SimplePing 中,禁用了 init 方法,只提供 initWithHostName: 一个方法,它可以初始化一个用于 ping 指定的主机实例对象。其中 hostName 参数可以是主机的 DNS 域名,或者是 IPv4、IPv6 地址的字符串形式。

属性

1@property (nonatomic, copy, readonly) NSString * hostName;
hostName:只读,保存由初始化方法 initWithHostName: 传入的 ping 操作要连接的主机域名或 IP 地址。

1@property (nonatomic, assign, readwrite) SimplePingAddressStyle addressStyle;
addressStyle:主机的 IP 地址类型,如 IPv4 或 IPv6 等,其中 SimplePingAddressStyle 枚举类型的定义如下:

12345typedef NS_ENUM(NSInteger, SimplePingAddressStyle) {SimplePingAddressStyleAny, // IPv4 或 IPv6SimplePingAddressStyleICMPv4, // IPv4SimplePingAddressStyleICMPv6 // IPv6};
1@property (nonatomic, copy, readonly, nullable) NSData * hostAddress;
hostAddress:只读,在 start 方法调用之后,根据 hostName 得到的要 ping 的主机的 IP 地址,它是 struct sockaddr 形式的 NSData 数据。当 SimplePing 实例处于 stopped 状态,或者实例调用了 start 方法,但在 simplePing:didStartWithAddress:方法被调用之前,hostAddress 的值都是 nil。

1@property (nonatomic, assign, readonly) sa_family_t hostAddressFamily;
hostAddressFamily:只读,hostAddress 的地址族,如果 hostAddress 为 nil,则其值为:AF_UNSPEC。

1@property (nonatomic, assign, readonly) uint16_t identifier;
identifier:只读,当创建一个 SimplePing 实例对象时,会自动生成一个的随机的标识符,用来唯一标识当前 ping 对象。

1@property (nonatomic, assign, readonly) uint16_t nextSequenceNumber;
nextSequenceNumber:只读,ping 每发送一次数据包都会有一个对应的序列号(sequence number),此值为下一次 ping 操作要发送数据时的序列号,从 0 开始递增,当 ping 成功发送一次数据到主机并收到应答时,该值 +1。而对于本次 ping 的 sequence number在成功发送数据(request)和成功接收到响应(response)的 delegate 回调方法里都会以方法参数返回,以便进行 ping 操作耗时的计算等等。

1@property (nonatomic, weak, readwrite, nullable) id delegate;
delegate:当前对象的回调,delegate 中的回调方法将在对象调用 start 方法所在的线程对应的 run loop 中以默认的 run loop model 执行。

实例方法

1- (void)start;
start 方法:开始一个 ping 操作,在调用此方法前,必须给 SimplePing 实例对象的 delegete 以及其他参数赋值。当 start 方法成功执行时,会回调 delegate 中的 simplePing:didStartWithAddress: 方法,在该回调方法里,就可以通过 sendPingWithData: 开始发送 ICMP 数据包,并等待接受主机应答的数据包。另外需要注意的是,当一个实例已经 started,又一次调用此 start 方法会出错。

1- (void)sendPingWithData:(nullable NSData *)data;
sendPingWithData: 方法:向主机发送特定格式的 ICMP 数据包,调用此方法前必须保证实例已经 started 并且要等待 simplePing:didStartWithAddress: 回调执行才能开始发送数据。参数 data 为要向主机发送的 ICMP 数据包,可以为 nil,默认会发一个标准的 64 byte 数据包。

1- (void)stop;
stop 方法:当结束要 ping 操作时,调用此方法。与 start方法不同的是,当一个实例已经 stopped,再次调用此方法也没事。

delegate 回调方法

start 方法执行结果的回调:

12345// start 方法成功执行,可在此开始发送数据,其中 address 为主机的 IP 地址;- (void)simplePing:(SimplePing *)pinger didStartWithAddress:(NSData *)address;// start 方法执行失败,返回错误信息;- (void)simplePing:(SimplePing *)pinger didFailWithError:(NSError *)error;
sendPingWithData: 方法执行结果的回调,每发送一次数据,都会同步地回调以下两个方法其中一个(除非你在发送途中调用了 stop 方法):

12345// 成功发送 ICMP 数据包到指定主机,在此传回已发送的数据包以及本次 ping 对应的序列号;- (void)simplePing:(SimplePing *)pinger didSendPacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber;// 发送数据失败,并返回错误信息,绝大部分原因由于 hostName 解析失败。另,当此方法调用时,ping 实例状态会自动转为 `stopped`,不用再显示调用 `stop` 方法;- (void)simplePing:(SimplePing *)pinger didFailToSendPacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber error:(NSError *)error;
接收到主机返回应答数据的回调:

12345// 成功接收到主机回传的与之前发送相匹配的 ICMP 数据包;- (void)simplePing:(SimplePing *)pinger didReceivePingResponsePacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber;// 收到的未知的数据包。- (void)simplePing:(SimplePing *)pinger didReceiveUnexpectedPacket:(NSData *)packet;
注:以上回调方法中的 packet 数据包只包含了 ICMP header 和 sendPingWithData: 中传入的数据,但不包含任何 IP 层的 header。

使用流程

根据苹果提供的 Demo,我们梳理了一下使用 SimplePing 类进行 ping 操作的流程如下图所示:



?

根据上图,我们写了一个简单的使用示例,详见下面代码以及注释,不再赘述。

1#import "ViewController.h"#import "SimplePing.h"@interface ViewController () @property (nonatomic, strong) SimplePing *pinger;@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];// (1)// 初始化一个 SimplePing 实例,// 注意,这个 pinger 实例不能为临时变量,不然当前函数执行完毕后,pinger 实例就会被释放,那么它的 delegate 将不会执行。self.pinger = [[SimplePing alloc] initWithHostName:@"www.apple.com"];// (2)// 指定 pinger 的 delegateself.pinger.delegate = self;// 指定要 ping 的 IP 地址的类型self.pinger.addressStyle = SimplePingAddressStyleICMPv4;// (3)// 调用 start 方法开始 ping[self.pinger start];}#pragma mark - SimplePingDelegate// (4) start 方法成功执行,可开始发送数据- (void)simplePing:(SimplePing *)pinger didStartWithAddress:(NSData *)address {NSLog(@"address: %@", address);// (5) 调用 sendPingWithData: 方法发送数据[pinger sendPingWithData:nil]; // data 可传入 nil,此时 ping 发送的数据会有一个默认值。}// (4) start 方法执行失败,返回错误信息- (void)simplePing:(SimplePing *)pinger didFailWithError:(NSError *)error {NSLog(@"%@", error.localizedDescription);}#pragma mark -// (6) 成功发送数据- (void)simplePing:(SimplePing *)pinger didSendPacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber {NSLog(@"didSendPacket: %@", packet);NSLog(@"identifier: %d", pinger.identifier);NSLog(@"sequenceNumber: %d", sequenceNumber);NSLog(@"nextSequenceNumber: %d", pinger.nextSequenceNumber);}// (6) 发送数据失败- (void)simplePing:(SimplePing *)pinger didFailToSendPacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber error:(NSError *)error {NSLog(@"didFailToSendPacket: %@", error.localizedDescription);}#pragma mark -// (7) 成功接收到之前 pinger 发送的数据- (void)simplePing:(SimplePing *)pinger didReceivePingResponsePacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber {NSLog(@"didReceivePingResponsePacket: %@", packet);NSLog(@"identifier: %d", pinger.identifier);NSLog(@"sequenceNumber: %d", sequenceNumber);NSLog(@"nextSequenceNumber: %d", pinger.nextSequenceNumber);}// (7) 接收到到未知的数据- (void)simplePing:(SimplePing *)pinger didReceiveUnexpectedPacket:(NSData *)packet {NSLog(@"didReceiveUnexpectedPacket: %@", packet);}

总结

本篇文章只介绍了如何使用苹果提供的示例源码 SimplePing 类初始化一个实例在 iOS 设备上进行 ping 操作,以进行判断网络真实连接状态,在下一篇文章《苹果实例源码阅读:SimplePing(2)》,我们将介绍 SimplePing 类的各个方法的具体内部实现。

Reference

Apple Sample Code: SimplePing

iOS下的实际网络连接状态检测:RealReachability
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  SimplePing