这篇文章是我在项目中须要判断内外网环境根据网上的资料及本身的改造所得结果,有些不足之处望指出。html
使用ping
命令来检测数据包(ICMP,Internet Control Message Protocol,互联网控制报文协议)可以经过IP协议到达特定主机,并收到主机的应答,以检查网络是否连通和网络链接速度,帮助咱们分析和断定网络故障。由于互联网操做是路由器严密监控的。当路由器端处理报文时如有意外发生,事件经过ICMP报告给发送端。git
SimplePing是Appl给开发者提供的一套封装了底层BSD Sockets ping
函数的类,SimplePing下载地址:developer.apple.com/library/con…github
下面咱们一一介绍 SimplePing 类的各个属性、方法以及delegate
回调方法的含义及做用。网络
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithHostName:(NSString *)hostName NS_DESIGNATED_INITIALIZER;
复制代码
SimplePing中,禁用了init
方法,只提供initWithHostName:
这个指定构造方法,它能够用于初始化一个ping
指定的主机实例对象。其中hostName
参数能够是主机的DNS
域名,或者是IPv4/IPv6
地址的字符串形式。app
@property (nonatomic, copy, readonly) NSString * hostName;
复制代码
hostName:只读,保存由初始化方法initWithHostName:
传入的ping
操做要链接的主机域名或IP
地址。dom
@property (nonatomic, assign, readwrite) SimplePingAddressStyle addressStyle;
复制代码
addressStyle:主机的IP
地址类型,如IPv4/IPv6
等,其中SimplePingAddressStyle
枚举类型的定义以下:ide
typedef NS_ENUM(NSInteger, SimplePingAddressStyle) {
SimplePingAddressStyleAny, // IPv4 或 IPv6
SimplePingAddressStyleICMPv4, // IPv4
SimplePingAddressStyleICMPv6 // IPv6
};
复制代码
@property (nonatomic, copy, readonly, nullable) NSData * hostAddress;
复制代码
hostAddress:只读,在start
方法调用以后,根据hostName
获得的要ping
的主机的IP
地址,它是struct sockaddr
形式的NSData
数据。当SimplePing
实例处于stopped
状态,或者实例调用了start
方法,但在simplePing:didStartWithAddress:
方法被调用以前,hostAddress 的值都是nil
。函数
@property (nonatomic, assign, readonly) sa_family_t hostAddressFamily;
复制代码
hostAddressFamily:只读,hostAddress
的地址族,若是hostAddress
为nil
,则其值为:AF_UNSPEC
。oop
@property (nonatomic, assign, readonly) uint16_t identifier;
复制代码
identifier:只读,当建立一个SimplePing
实例对象时,会自动生成一个的随机的标识符,用来惟一标识当前ping
对象。测试
@property (nonatomic, assign, readonly) uint16_t nextSequenceNumber;
复制代码
nextSequenceNumber:只读,ping
每发送一次数据包都会有一个对应的序列号sequence number
,此值为下一次ping
操做要发送数据时的序列号,从0开始递增,当ping
成功发送一次数据到主机并收到应答时,该值+1。而对于本次ping
的sequence number
在成功发送数据(request)和成功接收到响应(response)的delegate
回调方法里都会以方法参数返回,以便进行ping
操做耗时的计算等等。
@property (nonatomic, weak, readwrite, nullable) id delegate;
复制代码
delegate:当前对象的回调,delegate
中的回调方法将在对象调用start
方法所在的线程对应的run loop
中以默认的run loop model
执行。
- (void)start;
复制代码
start 方法:开始一个ping
操做,在调用此方法前,必须给SimplePing
实例对象的delegete
以及其余参数赋值。当start
方法成功执行时,会回调delegate
中的simplePing:didStartWithAddress:
方法,在该回调方法里,就能够经过sendPingWithData:
开始发送ICMP
数据包,并等待接受主机应答的数据包。另外须要注意的是,当一个实例已经started
,又一次调用此start
方法会出错。
- (void)sendPingWithData:(nullable NSData *)data;
复制代码
sendPingWithData: 方法:向主机发送特定格式的ICMP
数据包,调用此方法前必须保证明例已经started
而且要等待simplePing:didStartWithAddress:
回调执行才能开始发送数据。参数data
为要向主机发送的ICMP
数据包,能够为nil
,默认会发一个标准的64 byte
数据包。
- (void)stop;
复制代码
stop 方法:当结束要ping
操做时,调用此方法。与start
方法不一样的是,当一个实例已经stopped
,再次调用此方法也没事。
delegate
回调方法:
start
方法执行结果的回调:
// start 方法成功执行,可在此开始发送数据,其中 address 为主机的 IP 地址;
- (void)simplePing:(SimplePing *)pinger didStartWithAddress:(NSData *)address;
// start 方法执行失败,返回错误信息;
- (void)simplePing:(SimplePing *)pinger didFailWithError:(NSError *)error;
复制代码
sendPingWithData: 方法执行结果的回调,每发送一次数据,都会同步地回调如下两个方法其中一个(除非你在发送途中调用了stop
方法):
// 成功发送 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。
封装了一个简单SimplePing
类,由于只有一个类,就不开repo了:
//
// PJPingManager.h
// DiDiData
//
// Created by PJ on 2018/5/25.
// Copyright © 2018年 Didi.Inc. All rights reserved.
//
#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
复制代码
//
// PJPingManager.m
// DiDiData
//
// Created by PJ on 2018/5/25.
// Copyright © 2018年 Didi.Inc. All rights reserved.
//
#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
/** * 将ping接收的数据转换成ip地址 * @param address 接受的ping数据 */
- (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;
// Handle DNS errors as a special case.
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中该函数库相关介绍:pubs.opengroup.org/onlinepubs/…
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的延时便可。若是在这期间内未收到响应则可视为超时。- (void)pingNetWork{
self.pingManager = [[IPLPingManager alloc] init];
self.pingManager.pingSuccessCallback = ^{
};
self.pingManager.pingFailureCallback = ^{
};
[self.pingManager startPing];
}
复制代码
跑起工程后每秒都会ping
目标主机,若是你不并想每秒都执行一次,再自定义一个属性去标记吧。固然,你也可也彻底没必要使用我提供的这个封装,直接使用SimplePing
自行编写也行。
以上内容收录在个人“iOS开发之路”repo中,若是你喜欢该repo,欢迎start、fork、提PR支持,持续更新。