IOS 监测网络连接状态

======简介

    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官方的翻译:

SCNetworkReachability 编程接口允许应用确定系统当前网络配置的状态,还有目标主机的可达性。当由应用发送到网络堆栈的数据包可以离开本地设备的时候,远程主机就可以被认为可以到达。 可达性并不保证数据包一定会被主机接收到。 

SCNetworkReachability 编程接口支持同步和异步两种模式。 在同步模式中,可以通过调用SCNetworkReachabilityGetFlag函数来获得可达性状态;在异步模式中,可以调度SCNetworkReachability对象到客户端对象线程的运行循环上,客户端实现一个回调函数来接收通知,当远程主机改变可达性状态,回调则可响应。 

注意这些函数遵循Core Foundation的命名约定,只要函数名中包含 "Create" 或 "Copy"的函数返回的引用,都必须调用CFRelease来释放。 

有关检测和解释这些函数产生的错误可参考 System Configuration Reference.

主要的函数有以下几个:
(1)创建测试连接的引用:
(a)SCNetworkReachabilityRef SCNetworkReachabilityCreateWithAddress (    //根据传入的地址创建网络连接引用
   CFAllocatorRef allocator,                  //可以为NULL或kCFAllocatorDefault
   const struct sockaddr *address            //需要测试连接的IP地址
);
根据传入的地址测试连接,第一个参数可以为NULL或kCFAllocatorDefault,第二个参数为需要测试连接的IP地址,当为0.0.0.0时则可以查询本机的网络连接状态。同时返回一个引用必须在用完后释放。
(b)SCNetworkReachabilityRef SCNetworkReachabilityCreateWithName (     //根据传入的网址创建网络连接引用
   CFAllocatorRef allocator,                  //可以为NULL或kCFAllocatorDefault
   const char *nodename                       //比如为"www.baidu.com",此参数为域名
);
这个是根据传入的网址测试连接,第二个参数比如为"www.apple.com",其他和上一个一样。
(2)获取网络连接状态(是否存在网络连接)
Boolean SCNetworkReachabilityGetFlags (        //用来获得网络连接的状态
   SCNetworkReachabilityRef target,            //之前建立的网络连接的引用
   SCNetworkReachabilityFlags *flags           //保存确定连接是否获得的状态
);
这个函数用来获得测试连接的状态,第一个参数为之前建立的测试连接的引用,第二个参数用来保存获得的状态,如果能获得状态则返回TRUE,否则返回FALSE
(3)主要的数据类型介绍:
SCNetworkReachabilityRef:用来保存创建测试连接返回的引用
(4)主要常量介绍:
SCNetworkReachabilityFlags:保存返回的测试连接状态
其中常用的状态有:
kSCNetworkReachabilityFlagsReachable:能够连接网络
kSCNetworkReachabilityFlagsConnectionRequired:能够连接网络,但是首先得建立连接过程
kSCNetworkReachabilityFlagsIsWWAN:判断是否通过蜂窝网覆盖的连接,比如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.  
  9.      /**
  10.      *  SCNetworkReachabilityRef: 用来保存创建测试连接返回的引用
  11.      *
  12.      *  SCNetworkReachabilityCreateWithAddress: 根据传入的地址测试连接.
  13.      *  第一个参数可以为NULL或kCFAllocatorDefault
  14.      *  第二个参数为需要测试连接的IP地址,当为0.0.0.0时则可以查询本机的网络连接状态.
  15.      *  同时返回一个引用必须在用完后释放.
  16.      *  PS: SCNetworkReachabilityCreateWithName: 这个是根据传入的网址测试连接,
  17.      *  第二个参数比如为"www.apple.com",其他和上一个一样.
  18.      *
  19.      *  SCNetworkReachabilityGetFlags: 这个函数用来获得测试连接的状态,
  20.      *  第一个参数为之前建立的测试连接的引用,
  21.      *  第二个参数用来保存获得的状态,
  22.      *  如果能获得状态则返回TRUE,否则返回FALSE
  23.      *
  24.      */
  25.     SCNetworkReachabilityRef defaultRouteReachability  = SCNetworkReachabilityCreateWithAddress ( NULL(struct sockaddr  * ) &zeroAddress ) ; //创建测试连接的引用:
  26.     SCNetworkReachabilityFlags flags ;
  27.  
  28.     BOOL didRetrieveFlags  = SCNetworkReachabilityGetFlags (defaultRouteReachability,  &flags ) ;
  29.     CFRelease (defaultRouteReachability ) ;
  30.  
  31.      if  ( !didRetrieveFlags )
  32.      {
  33.         printf ( "Error. Could not recover network reachability flagsn" ) ;
  34.          return NO ;
  35.      }
  36.  
  37.      /**
  38.      *  kSCNetworkReachabilityFlagsReachable: 能够连接网络
  39.      *  kSCNetworkReachabilityFlagsConnectionRequired: 能够连接网络,但是首先得建立连接过程
  40.      *  kSCNetworkReachabilityFlagsIsWWAN: 判断是否通过蜂窝网覆盖的连接,
  41.      *  比如EDGE,GPRS或者目前的3G.主要是区别通过WiFi的连接.
  42.      *
  43.      */
  44.     BOOL isReachable  =  ( (flags  & kSCNetworkFlagsReachable )  !=  0 ) ;
  45.     BOOL needsConnection  =  ( (flags  & kSCNetworkFlagsConnectionRequired )  !=  0 ) ;
  46.      return  (isReachable  &&  !needsConnection )  ? YES  : NO ;
  47. }

-(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







你可能感兴趣的:(iphone)