你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> 蘋果示例源碼閱讀:SimplePing(1)

蘋果示例源碼閱讀:SimplePing(1)

編輯:IOS開發基礎

前言

手機網絡連接狀態的檢測對於 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 的類結構如下圖所示:

SimplePing1-1.png

?

下面我們一一介紹 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 枚舉類型的定義如下:

12345
typedef 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 操作的流程如下圖所示:

SimplePing2.png

?

根據上圖,我們寫了一個簡單的使用示例,詳見下面代碼以及注釋,不再贅述。

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

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved