天天看點

IOS SCNetworkReachability和Reachability監測網絡連接配接狀态

    iOS Framework : SystemConfiguration.framework 中,包含了SCNetworkReachability工具,可以幫助監測網絡狀況,所有定義包含在SCNetworkReachability.h中。

      iOS Library的 sample code中,包含Reaqchability工程,裡面的Reachability類是對SCNetworkReachability的封裝,可實際開發中可以将Reachability添加到自己的工程中拿來直接使用。

      Reachability是異步工具機制,把網絡狀況類型縮小到了3種:NotReachable,ReachableViaWiFi,ReachableViaWWAN。減輕了開發者了解複雜的網絡狀況的負擔。

      另外,SCNetworkReachability中的各種網絡狀況,分為使用Wifi還是基帶信号,是否需要拔号,是否需要使用者名密碼等,通常的開發中,隻需要了解網絡是否能連上就可以了。是以推薦使用Reachability。

======SCNetworkReachability 詳解

SCNetworkReachability官方的翻譯:

1、SCNetworkReachability

 程式設計接口允許應用确定系統目前網絡配置的狀态,還有目标主機的可達性。

當由應用發送到網絡堆棧的資料包可以離開本地裝置的時候,遠端主機就可以被認為可以到達。 可達性并不保證資料包一定會被主機接收到。 

2、SCNetworkReachability

 程式設計接口支援同步和異步兩種模式。

 在同步模式中,可以通過調用SCNetworkReachabilityGetFlag函數來獲得可達性狀态;

 在異步模式中,可以排程SCNetworkReachability對象到用戶端對象線程的運作循環上,用戶端實作一個回調函數來接收通知,當遠端主機改變可達性狀态,回調則可響應。 

注意這些函數遵循Core Foundation的命名約定,隻要函數名中包含 "Create" 或 "Copy"的函數傳回的引用,都必須調用CFRelease來釋放。 

有關檢測和解釋這些函數産生的錯誤可參考 System Configuration Reference.

主要的函數有以下幾個:

(1)建立測試連接配接的引用 :

(a)SCNetworkReachabilityRef  SCNetworkReachabilityCre ateWithAddress (     // 根據傳入的位址建立網絡連接配接 引用
     CFAllocatorRef allocator,                   // 可以為NULL或kCFAllocatorDefault
   const struct sockaddr *address            //需要測試連接配接的IP位址 
     );
第一個參數  可以為NULL或kCFAllocatorDefault, 第二個參數  為需要測試連接配接的IP位址,當為0.0.0.0時則可以查詢本機的網絡連接配接狀态。同時傳回一個引用必須在用完後釋放。
(b)SCNetworkReachabilityRef  SCNetworkReachabilityCre ateWithName (      //根據傳入的網址建立網絡連接配接 引用
     CFAllocatorRef allocator,                   // 可以為NULL或kCFAllocatorDefault
   const char *nodename                       //比如為"www.baidu.com",此參數為域名
    );

        這個是根據傳入的網址測試連接配接,第二個參數比如為"www.apple.com",其他和上一個一樣。

(2)擷取網絡連接配接狀态(是否存在網絡連接配接):

Boolean SCNetworkReachabilityGet Flags (   //用來獲得網絡 連接配接的狀态

     SCNetworkReachabilityRef  target,             // 之前建立的 網絡 連接配接的引用

     SCNetworkReachabilityFla gs *flags            // 儲存确定連接配接是否獲得的狀态 );

這個函數用來獲得測試連接配接的狀态,第一個參數為之前建立的測試連接配接的引用,第二個參數用來儲存獲得的狀态,如果能獲得狀态則傳回TRUE,否則傳回FALSE (3)主要的資料類型介紹: SCNetworkReachabilityRef :用來儲存建立測試連接配接傳回的引用 (4)主要常量介紹: SCNetworkReachabilityFla gs:儲存傳回的測試連接配接狀态 其中常用的狀态有: kSCNetworkReachabilityFl agsReachable:能夠連接配接網絡 kSCNetworkReachabilityFl agsConnectionRequired:能夠連接配接網絡,但是首先得建立連接配接過程 kSCNetworkReachabilityFl agsIsWWAN:判斷是否通過蜂窩網覆寫的連接配接,比如EDGE,GPRS或者目前的3G.主要是差別通過WiFi的連接配接。

傳回标記:

kSCNetworkReachabilityFlagsIsWWAN    :測試使用者使用的時營運商的網絡還是本地wifi。

kSCNetworkFlagsConnectionRequired:無需更多連結。

kSCNetworkFlagsReachable:表明網絡可以通路。

======code

  1. #import  < SystemConfiguration /SystemConfiguration. h  >
  2. #include  < netdb. h  >
  1. -  (BOOL ) connectedToNetwork
  2. {
  3.      // 建立零位址,0.0.0.0的位址表示查詢本機的網絡連接配接狀态
  4.     struct sockaddr_in zeroAddress ;//sockaddr_in是與sockaddr等價的資料結構
  5.     bzero ( &zeroAddress, sizeof (zeroAddress ) ) ;
  6.     zeroAddress. sin_len  = sizeof (zeroAddress ) ;
  7.     zeroAddress. sin_family  = AF_INET ;//sin_family是位址家族,一般都是“AF_xxx”的形式。通常大多用的是都是AF_INET,代表TCP/IP協定族
  8.     SCNetworkReachabilityRef defaultRouteReachability  = SCNetworkReachabilityCreateWithAddress ( NULL,  (struct sockaddr  * ) &zeroAddress ) ; //建立測試連接配接的引用:
  9.     SCNetworkReachabilityFlags flags ;
  10.     BOOL didRetrieveFlags  = SCNetworkReachabilityGetFlags (defaultRouteReachability,  &flags ) ;
  11.     CFRelease (defaultRouteReachability ) ;
  12.      if  ( !didRetrieveFlags )
  13.      {
  14.         printf ( "Error. Could not recover network reachability flagsn" ) ;
  15.          return NO ;
  16.      }
  17.     BOOL isReachable  =  ( (flags  & kSCNetworkFlagsReachable )  !=  0 ) ;
  18.     BOOL needsConnection  =  ( (flags  & kSCNetworkFlagsConnectionRequired )  !=  0 ) ;
  19.      return  (isReachable  &&  !needsConnection )  ? YES  : NO ;
  20. }

-(void) start {

    if (![self connectedToNetwork]) {

                UIAlertView *alert = [[UIAlertView alloc] 

                         initWithTitle:@"Network Connection Error" 

                         message:@"You need to be connected to the internet to use this feature." 

                         delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];

        [alert show];

        [alert release];

        } else {

             //do something 

        }

}

=============================================

使用上述方法可能會出現以下問題:

當使用者在使用你的應用程式的時候,如果關掉螢幕,将裝置放在鎖定狀态(就是那個重新打開時需要在螢幕上畫一下「解鎖」的狀态)一陣子,然後再按一下按鈕恢複使用,這個時候你想要做一些網路操作,其實是可以連線,但是Reachability API 還是告訴你無法連線;或剛打開應用程式的時候,也告訴你無法連線。當你需要判斷能不能連線,使用者用了哪種連線而應不應該繼續連線的時候,其實使用者可以連線,API 卻始終一直告訴你不能連線。

把裝置設定為鎖定,然後恢複使用的狀況是這樣的-蘋果的設計是,為了節省電力消耗,會在進入鎖定狀态後,自動關閉無線網路介面,而當你解除鎖定後,才會把無線網路再度打開。而Reachability 基本上隻詢問「目前的網路狀态」,如果裝置的無線網路正處于從關閉的狀态恢複的階段,這時後回傳的結果便是無法連線。

順道一提,在第三方應用程式中,可以在Info.plist 檔案中,設定UIRequiresPersistentWifi 這個選項,檔案中說這個設定可以讓應用程式持續保持無線網路的連線狀态,但是,就算設了這項設定,在進入鎖定狀态後,系統仍然會自動關閉無線網路介面,這項設定僅局限于iPhone 一直開着、你不去把螢幕關掉的狀況。

至于怎樣在鎖定狀态下繼續保持連線,那又是另外一個話題了。

剛進入應用程式的時候,也往往回傳無法連線-猜測應該是使用iPhone 主畫面(Springboard)的時候,無線網路介面大概也是關閉的。在點選要用什麼應用程式的時候,好像也用不到什麼網路功能,為了節電把網路介面關了也好。至于定時檢查信箱、或從AppStore 下載下傳軟體什麼的,應該是蘋果有其他自己的背景程式,負責呼叫網路介面。

簡言之,就是你常會遇到「問的時候說沒有,但是下一秒鐘網路就通」的狀況,遇到這種狀況,要使用比較白爛的作法,可以向Reachability 連續問兩次,如果第一次說沒有第二次卻說有,那就代表其實還是有網路…可能比較好的作法,可以是,我們不要叫一個method 直接回傳給我們網路狀态,而是變成delegate 的方式來處理。

流程大概是這樣,這邊稍微有些啰嗦-

   1. 設計兩個delegate method,分别用于有網路與沒網路兩種狀況。

   2. 先生出一個SCNetworkReachabilityRef 物件,然後用SCNetworkReachabilityGetFlags() 抓取目前的網路狀态,如果是有,就呼叫「有網路」的那組delegate method,直接結束。

   3. 如果這一次抓取網路狀态的結果是沒有連線,我們就對剛剛産生的SCNetworkReachabilityRef 物件設定一個SCNetworkReachabilityCallBack function。因為隻要連線狀态出現變化,就會呼叫這個function,是以,在呼叫到的時候,再用SCNetworkReachabilityGetFlags() 抓一次目前的網路狀态,決定要回傳是「有網路」或「沒網路」的delegate method。如果有呼叫到,通常是會有,如此一來,我們可以捕捉到了「第一次說沒有,但是後來又有網路」的狀況,并且成功回傳「有網路」。

   4. 同時設一組timer(或用NSObject 的perform selector after delay 之類的),如果超過一段時間,SCNetworkReachability API 都沒有被呼叫前一點中提到的SCNetworkReachabilityCallBack function,就代表不但一開始沒網路,而且後來一直還是那個狀态,那…就代表一直沒網路。這時後呼叫「沒網路」的delegate method。

   5. 記得要release 那個SCNetworkReachabilityRef 物件…。

采用這種實作,在沒有網路連線的狀态下,就會需要幾秒鐘的等待,确定目前的确沒有網路連線。不過嘛,反正沒有網路連線,也不能夠做什麼别的事情,是以等個幾秒鐘也無所謂嘛。

=====相關代碼講解======================================================

struct sockaddr_in {

__uint8_t   sin_len;

sa_family_t   sin_family;

in_port_t   sin_port;

struct in_addr  sin_addr;

char        sin_zero[8];

};

    sin_family指代協定族,在socket程式設計中隻能是AF_INET

  sin_port存儲端口号(使用網絡位元組順序)

  sin_addr存儲IP位址,使用in_addr這個資料結構

  sin_zero是為了讓sockaddr與sockaddr_in兩個資料結構保持大小相同而保留的空位元組。

  sin_addr按照網絡位元組順序存儲IP位址

  sockaddr_in和sockaddr是并列的結構,指向sockaddr_in的結構體的指針也可以指向sockaddr的結構體,并代替它。也就是說,你可以使用sockaddr_in建立你所需要的資訊,然後用進行類型轉換就可以了

bzero((char*)&mysock,sizeof(mysock));//初始化

sockaddr_in mysock;

  bzero((char*)&mysock,sizeof(mysock));

  mysock.sa_family=AF_INET;

  mysock.sin_port=htons(1234);//1234是端口号

  mysock.sin_addr.s_addr=inet_addr("192.168.0.1");

上面我們提到sockaddr,現在我也簡單的說一下

struct sockaddr {  unsigned short sa_family;     char sa_data[14]; };  sa_family是位址家族,一般都是“AF_xxx”的形式。通常大多用的是都是AF_INET,代表TCP/IP協定族。  sa_data是14位元組協定位址。  這個資料結構用做bind、connect、recvfrom、sendto等函數的參數,指明位址資訊。但一般程式設計中并不直接針對此資料結構操作,而是使用另一個與sockaddr等價的資料結構,就是我們上面提到的sockaddr_in;

上面我們還提到了一個資料結構struct in_addr  sin_addr,這裡也簡單的介紹一下

typedef struct in_addr {

  union{

   struct {     unsigned char s_b1,s_b2,s_b3,s_b4;  } S_un_b;

  struct {     unsigned short s_w1,s_w2;                    } S_un_w;

   struct {     unsigned long S_addr;                            } S_un;

} IN_ADDR;

結構體in_addr 用來表示一個32位的IPv4位址.

  in_addr_t 一般為 32位的unsigned long.

  其中每8位代表一個IP位址位中的一個數值.

  例如192.168.3.144記為0xc0a80390,其中b1 為192 ,b2 為 168, b3 為 3 , b4 為 144

繼續閱讀