前言
手机网络连接状态的检测对于 iOS App 开发来说是一个非常基础的需求,在前一篇文章 苹果示例源码阅读:Reachability 我们介绍了如何通过 SCNetworkReachability
提供的一系列 C 函数 API 进行网络连接状态变化的监听。但事实上,此方案能获取的只是设备的本地连接状态,有时它很难为我们检测真正的网络连接状态,如以下场景:
Ping
ping
是 Windows、Unix 、Linux 和 macOS 等系统下一个常用的命令,利用 ping
命令可以用来测试数据包 (ICMP) 能否通过 IP 协议到达特定主机,并收到主机的应答,以检查网络是否连通和网络连接速度,帮助我们分析和判定网络故障。
幸运的是,苹果为我们提供了示例源码:SimplePing,示范了在 iOS 或者 Mac 上如何用 Objective-C / Swift 实现 ping
操作,因此我们也可以通过 ping
来检查手机网络的真实连接状态。事实上,Github 上著名的第三方开源库 RealReachability 也是这么做的。
SimplePingHelper:是一个检测iPhone设备同服务器联通的助手类,是基于SimplePing再做的一层封装。
SimplePing源码阅读
对于 SimplePing
源码的阅读,我们将分为两部分来介绍。第一部分将结合 SimplePing.h
头文件里声明的方法,介绍如何使用 SimplePing
类封装的方法进行 ping
操作,第二部分(下一篇)将详细介绍 SimplePing.m
里各方法的具体实现细节。
类结构
通过 SimplePing.h
头文件中的声明,我们整理 SimplePing
的类结构如下图所示:
![1478568145712313.png SimplePing1-1.png](http://cc.cocimg.com/api/uploads/20161108/1478568145712313.png)
?
下面我们一一介绍 SimplePing
类的各个属性、方法以及 delegate 回调方法的含义及作用。
初始化方法
| - (instancetype)init NS_UNAVAILABLE;- (instancetype)initWithHostName:(NSString *)hostName NS_DESIGNATED_INITIALIZER; |
SimplePing
中,禁用了 init
方法,只提供 initWithHostName:
一个方法,它可以初始化一个用于 ping 指定的主机实例对象。其中 hostName
参数可以是主机的 DNS 域名,或者是 IPv4、IPv6 地址的字符串形式。
属性
| @property (nonatomic, copy, readonly) NSString * hostName; |
| @property (nonatomic, assign, readwrite) SimplePingAddressStyle addressStyle; |
| typedef NS_ENUM(NSInteger, SimplePingAddressStyle) {SimplePingAddressStyleAny,
// IPv4 或 IPv6SimplePingAddressStyleICMPv4, // IPv4SimplePingAddressStyleICMPv6 // IPv6}; |
| @property (nonatomic, copy, readonly, nullable) NSData * hostAddress; |
| @property (nonatomic, assign, readonly) sa_family_t hostAddressFamily; |
| @property (nonatomic, assign, readonly) uint16_t identifier; |
| @property (nonatomic, assign, readonly) uint16_t nextSequenceNumber; |
| @property (nonatomic, weak, readwrite, nullable) id delegate; |
实例方法
| - (void)sendPingWithData:(nullable NSData *)data; |
delegate 回调方法
| // start 方法成功执行,可在此开始发送数据,其中 address 为主机的 IP 地址;
- (void)simplePing:(SimplePing *)pinger didStartWithAddress:(NSData *)address;
// start 方法执行失败,返回错误信息;
- (void)simplePing:(SimplePing *)pinger didFailWithError:(NSError *)error; |
| // 成功发送 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; |
| // 成功接收到主机回传的与之前发送相匹配的 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
操作的流程如下图所示:
![1478568088381082.png SimplePing2.png](http://cc.cocimg.com/api/uploads/20161108/1478568088381082.png)
?
根据上图,我们写了一个简单的使用示例,详见下面代码以及注释,不再赘述。
| #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 的 delegate
self.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
源码的阅读,我们将分为两部分来介绍。第一部分将结合 SimplePing.h
头文件里声明的方法,介绍如何使用 SimplePing
类封装的方法进行 ping
操作,第二部分(下一篇)将详细介绍 SimplePing.m
里各方法的具体实现细节。
类结构
通过 SimplePing.h
头文件中的声明,我们整理 SimplePing
的类结构如下图所示:
![1478568145712313.png SimplePing1-1.png](http://cc.cocimg.com/api/uploads/20161108/1478568145712313.png)
?
下面我们一一介绍 SimplePing
类的各个属性、方法以及 delegate 回调方法的含义及作用。
初始化方法
| - (instancetype)init NS_UNAVAILABLE;- (instancetype)initWithHostName:(NSString *)hostName NS_DESIGNATED_INITIALIZER; |
SimplePing
中,禁用了 init
方法,只提供 initWithHostName:
一个方法,它可以初始化一个用于 ping 指定的主机实例对象。其中 hostName
参数可以是主机的 DNS 域名,或者是 IPv4、IPv6 地址的字符串形式。
属性
| @property (nonatomic, copy, readonly) NSString * hostName; |
| @property (nonatomic, assign, readwrite) SimplePingAddressStyle addressStyle; |
| typedef NS_ENUM(NSInteger, SimplePingAddressStyle) {SimplePingAddressStyleAny,
// IPv4 或 IPv6SimplePingAddressStyleICMPv4, // IPv4SimplePingAddressStyleICMPv6 // IPv6}; |
| @property (nonatomic, copy, readonly, nullable) NSData * hostAddress; |
| @property (nonatomic, assign, readonly) sa_family_t hostAddressFamily; |
| @property (nonatomic, assign, readonly) uint16_t identifier; |
| @property (nonatomic, assign, readonly) uint16_t nextSequenceNumber; |
| @property (nonatomic, weak, readwrite, nullable) id delegate; |
实例方法
| - (void)sendPingWithData:(nullable NSData *)data; |
delegate 回调方法
| // start 方法成功执行,可在此开始发送数据,其中 address 为主机的 IP 地址;
- (void)simplePing:(SimplePing *)pinger didStartWithAddress:(NSData *)address;
// start 方法执行失败,返回错误信息;
- (void)simplePing:(SimplePing *)pinger didFailWithError:(NSError *)error; |
| // 成功发送 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; |
| // 成功接收到主机回传的与之前发送相匹配的 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
操作的流程如下图所示:
![1478568088381082.png SimplePing2.png](http://cc.cocimg.com/api/uploads/20161108/1478568088381082.png)
?
根据上图,我们写了一个简单的使用示例,详见下面代码以及注释,不再赘述。
| #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 的 delegate
self.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 源码阅读
对于 SimplePing
源码的阅读,我们将分为两部分来介绍。第一部分将结合 SimplePing.h
头文件里声明的方法,介绍如何使用 SimplePing
类封装的方法进行 ping
操作,第二部分(下一篇)将详细介绍 SimplePing.m
里各方法的具体实现细节。
类结构
通过 SimplePing.h
头文件中的声明,我们整理 SimplePing
的类结构如下图所示:
![1478568145712313.png SimplePing1-1.png](http://cc.cocimg.com/api/uploads/20161108/1478568145712313.png)
?
下面我们一一介绍 SimplePing
类的各个属性、方法以及 delegate 回调方法的含义及作用。
初始化方法
| - (instancetype)init NS_UNAVAILABLE;- (instancetype)initWithHostName:(NSString *)hostName NS_DESIGNATED_INITIALIZER; |
SimplePing
中,禁用了 init
方法,只提供 initWithHostName:
一个方法,它可以初始化一个用于 ping 指定的主机实例对象。其中 hostName
参数可以是主机的 DNS 域名,或者是 IPv4、IPv6 地址的字符串形式。
属性
| @property (nonatomic, copy, readonly) NSString * hostName; |
| @property (nonatomic, assign, readwrite) SimplePingAddressStyle addressStyle; |
| typedef NS_ENUM(NSInteger, SimplePingAddressStyle) {SimplePingAddressStyleAny,
// IPv4 或 IPv6SimplePingAddressStyleICMPv4, // IPv4SimplePingAddressStyleICMPv6 // IPv6}; |
| @property (nonatomic, copy, readonly, nullable) NSData * hostAddress; |
| @property (nonatomic, assign, readonly) sa_family_t hostAddressFamily; |
| @property (nonatomic, assign, readonly) uint16_t identifier; |
| @property (nonatomic, assign, readonly) uint16_t nextSequenceNumber; |
| @property (nonatomic, weak, readwrite, nullable) id delegate; |
实例方法
| - (void)sendPingWithData:(nullable NSData *)data; |
delegate 回调方法
| // start 方法成功执行,可在此开始发送数据,其中 address 为主机的 IP 地址;
- (void)simplePing:(SimplePing *)pinger didStartWithAddress:(NSData *)address;
// start 方法执行失败,返回错误信息;
- (void)simplePing:(SimplePing *)pinger didFailWithError:(NSError *)error; |
| // 成功发送 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; |
| // 成功接收到主机回传的与之前发送相匹配的 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
操作的流程如下图所示:
![1478568088381082.png SimplePing2.png](http://cc.cocimg.com/api/uploads/20161108/1478568088381082.png)
?
根据上图,我们写了一个简单的使用示例,详见下面代码以及注释,不再赘述。
| #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 的 delegate
self.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 操作,以进行判断网络真实连接状态。