这篇文章是我在项目中需要判断内外网环境根据网上的资料及自己的改造所得结果,有些不足之处望指出。
使用ping
命令来检测数据包(ICMP,Internet Control Message Protocol,互联网控制报文协议)能够通过IP协议到达特定主机,并收到主机的应答,以检查网络是否连通和网络连接速度,帮助我们分析和判定网络故障。因为互联网操作是路由器严密监控的。当路由器端处理报文时若有意外发生,事件通过ICMP报告给发送端。
SimplePing是Appl给开发者提供的一套封装了底层BSD Sockets ping
函数的类,SimplePing下载地址:https://developer.apple.com/library/content/samplecode/SimplePing/Introduction/Intro.html
下面我们一一介绍 SimplePing 类的各个属性、方法以及delegate
回调方法的含义及作用。
初始化方法 1 2 - (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
枚举类型的定义如下:
1 2 3 4 5 typedef NS_ENUM (NSInteger , SimplePingAddressStyle) { SimplePingAddressStyleAny, SimplePingAddressStyleICMPv4, SimplePingAddressStyleICMPv6 };
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
执行。
实例方法:
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
数据包。
stop 方法:当结束要ping
操作时,调用此方法。与start
方法不同的是,当一个实例已经stopped
,再次调用此方法也没事。
delegate
回调方法:
start
方法执行结果的回调:
1 2 3 4 - (void )simplePing:(SimplePing *)pinger didStartWithAddress:(NSData *)address; - (void )simplePing:(SimplePing *)pinger didFailWithError:(NSError *)error;
sendPingWithData: 方法执行结果的回调,每发送一次数据,都会同步地回调以下两个方法其中一个(除非你在发送途中调用了stop
方法):
1 2 3 4 - (void )simplePing:(SimplePing *)pinger didSendPacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber; - (void )simplePing:(SimplePing *)pinger didFailToSendPacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber error:(NSError *)error;
接收到主机返回应答数据的回调:
1 2 3 4 - (void )simplePing:(SimplePing *)pinger didReceivePingResponsePacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber; - (void )simplePing:(SimplePing *)pinger didReceiveUnexpectedPacket:(NSData *)packet;
注:以上回调方法中的 packet 数据包只包含了 ICMP header 和 sendPingWithData: 中传入的数据,但不包含任何 IP 层的 header。
封装了一个简单SimplePing
类,因为只有一个类,就不开repo了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #import <Foundation/Foundation.h> typedef void (^PingSuccessCallback)();typedef void (^PingFailureCallback)();@interface IPLPingManager : NSObject @property (nonatomic , copy ) PingSuccessCallback pingSuccessCallback;@property (nonatomic , copy ) PingFailureCallback pingFailureCallback;- (void )startPing; @end
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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 #import "PJPingManager.h" #import "SimplePing.h" #include <netdb.h> @interface PJPingManager ()<SimplePingDelegate >@property (nonatomic , strong ) SimplePing *pinger;@property (nonatomic , strong ) NSTimer *sendTimer;@end @implementation PJPingManager - (instancetype )init { self = [super init]; if (self ) { NSString *hostName = @"your hostName" ; self .pinger = [[SimplePing alloc] initWithHostName:hostName]; self .pinger.addressStyle = SimplePingAddressStyleAny; self .pinger.delegate = self ; } return self ; } - (void )startPing { [self start]; } - (void )start { [self .pinger start]; } - (void )stop { [self .pinger stop]; self .pinger = nil ; if ([self .sendTimer isValid]) { [self .sendTimer invalidate]; } self .sendTimer = nil ; } - (void )sendPing { [self .pinger sendPingWithData:nil ]; } #pragma mark - pinger delegate - (void )simplePing:(SimplePing *)pinger didStartWithAddress:(NSData *)address { NSLog (@"pinging %@" , [self displayAddressForAddress:address]); [self sendPing]; } - (void )simplePing:(SimplePing *)pinger didFailWithError:(NSError *)error { NSLog (@"failed: %@" , [self shortErrorFromError:error]); [self stop]; if (self .pingFailureCallback) { self .pingFailureCallback(); } } - (void )simplePing:(SimplePing *)pinger didSendPacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber { NSLog (@"#%u sent" , (unsigned int ) sequenceNumber); } - (void )simplePing:(SimplePing *)pinger didFailToSendPacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber error:(NSError *)error { NSLog (@"#%u send failed: %@" , (unsigned int ) sequenceNumber, [self shortErrorFromError:error]); [self stop]; if (self .pingFailureCallback) { self .pingFailureCallback(); } } - (void )simplePing:(SimplePing *)pinger didReceivePingResponsePacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber { NSLog (@"#%u received, size=%zu" , (unsigned int ) sequenceNumber, (size_t) packet.length); [self stop]; if (self .pingSuccessCallback) { self .pingSuccessCallback(); } } - (void )simplePing:(SimplePing *)pinger didReceiveUnexpectedPacket:(NSData *)packet { NSLog (@"unexpected packet, size=%zu" , (size_t) packet.length); [self stop]; if (self .pingSuccessCallback) { self .pingSuccessCallback(); } } #pragma mark - Others mothods - (NSString *)displayAddressForAddress:(NSData *)address { int err; NSString *result; char hostStr[NI_MAXHOST]; result = nil ; if (address != nil ) { err = getnameinfo([address bytes], (socklen_t)[address length], hostStr, sizeof (hostStr), NULL , 0 , NI_NUMERICHOST); if (err == 0 ) { result = [NSString stringWithCString:hostStr encoding:NSASCIIStringEncoding ]; } } if (result == nil ) { result = @"?" ; } return result; } - (NSString *)shortErrorFromError:(NSError *)error { NSString *result; NSNumber *failureNum; int failure; const char *failureStr; result = nil ; if ([[error domain] isEqual:(NSString *)kCFErrorDomainCFNetwork] && ([error code] == kCFHostErrorUnknown)) { failureNum = [[error userInfo] objectForKey:(id )kCFGetAddrInfoFailureKey]; if ([failureNum isKindOfClass:[NSNumber class ]]) { failure = [failureNum intValue]; if (failure != 0 ) { failureStr = gai_strerror(failure); if (failureStr != NULL ) { result = [NSString stringWithUTF8String:failureStr]; } } } } if (result == nil ) { result = [error localizedFailureReason]; } if (result == nil ) { result = [error localizedDescription]; } if (result == nil ) { result = [error description]; } return result; } @end
给出的代码中使用到的netdb
库为Unix和Linux特有的头文件,主要定义了与网络有关的结构、变量类型、宏、函数等。这里有篇在Unix中该函数库相关介绍:http://pubs.opengroup.org/onlinepubs/7908799/xns/netdb.h.html
NI_MAXHOST
给出主机字符串存储空间的最大长度,值为1025;
NI_MAXSERV
给出服务字符串存储空间的最大长度,值为32.
其中还有一些会使用到的宏,如下所示:
宏
解释
#define NI_NOFQDN 0x00000001
只返回FQDN的主机名部分
#define NI_NUMERICHOST 0x00000002
以数串格式返回主机字符串
#define NI_NAMEREQD 0x00000004
若不能从地址解析出名字则返回错误
#define NI_NUMERICSERV 0x00000008
以数串格式返回服务字符串
#define NI_NUMERICSCOPE 0x00000100
以数串格式返回范围标识字符串
#define NI_DGRAM 0x00000010
数据报服务
使用方法:
新建ping
类,复制以上代码;
创建一个NSTimer
类型属性且初始化timer。self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(pingNetWork) userInfo:nil repeats:YES];
。在这里为啥要初始化一个NSTimer呢?因为如果ping
失败后,也就是发送的测试报文成功,但一直没收到响应的报文,此时却不会有任何的回调方法告知我们,而只ping
一次结果也不准确,更何况ping
花费的时间非常之短,故我在此加了个NSTimer
多次进行ping
,或者也可以使用performSelector
进行延时判断,一般0.3~0.8s的延时即可。如果在这期间内未收到响应则可视为超时。
对应的方法为:1 2 3 4 5 6 7 8 9 10 11 12 - (void )pingNetWork{ self .pingManager = [[IPLPingManager alloc] init]; self .pingManager.pingSuccessCallback = ^{ }; self .pingManager.pingFailureCallback = ^{ }; [self .pingManager startPing]; }
跑起工程后每秒都会ping
目标主机,如果你不并想每秒都执行一次,再自定义一个属性去标记吧。当然,你也可也完全不必使用我提供的这个封装,直接使用SimplePing
自行编写也行。