這篇文章是我在項目中須要判斷內外網環境根據網上的資料及本身的改造所得結果,有些不足之處望指出。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支持,持續更新。