您的位置:首页 > 理论基础 > 计算机网络

ios 检测网络状态

2014-02-10 16:01 423 查看
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

#import < SystemConfiguration/SystemConfiguration.h >

#include < netdb.h >

- (BOOL) connectedToNetwork

{

// 创建零地址,0.0.0.0的地址表示查询本机的网络连接状态

struct sockaddr_in zeroAddress;//sockaddr_in是与sockaddr等价的数据结构

bzero(&zeroAddress, sizeof(zeroAddress));

zeroAddress.sin_len = sizeof(zeroAddress);

zeroAddress.sin_family = AF_INET;//sin_family是地址家族,一般都是“AF_xxx”的形式。通常大多用的是都是AF_INET,代表TCP/IP协议族

/**

* SCNetworkReachabilityRef: 用来保存创建测试连接返回的引用

*

* SCNetworkReachabilityCreateWithAddress: 根据传入的地址测试连接.

* 第一个参数可以为NULL或kCFAllocatorDefault

* 第二个参数为需要测试连接的IP地址,当为0.0.0.0时则可以查询本机的网络连接状态.

* 同时返回一个引用必须在用完后释放.

* PS: SCNetworkReachabilityCreateWithName: 这个是根据传入的网址测试连接,

* 第二个参数比如为"www.apple.com",其他和上一个一样.

*

* SCNetworkReachabilityGetFlags: 这个函数用来获得测试连接的状态,

* 第一个参数为之前建立的测试连接的引用,

* 第二个参数用来保存获得的状态,

* 如果能获得状态则返回TRUE,否则返回FALSE

*

*/

SCNetworkReachabilityRef defaultRouteReachability = SCNetworkReachabilityCreateWithAddress(NULL, (struct
sockaddr *)&zeroAddress); //创建测试连接的引用:

SCNetworkReachabilityFlags flags;

BOOL didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags);

CFRelease(defaultRouteReachability);

if (!didRetrieveFlags)

{

printf("Error. Could not recover
network reachability flagsn");

return NO;

}

/**

* kSCNetworkReachabilityFlagsReachable: 能够连接网络

* kSCNetworkReachabilityFlagsConnectionRequired: 能够连接网络,但是首先得建立连接过程

* kSCNetworkReachabilityFlagsIsWWAN: 判断是否通过蜂窝网覆盖的连接,

* 比如EDGE,GPRS或者目前的3G.主要是区别通过WiFi的连接.

*

*/

BOOL isReachable = ((flags & kSCNetworkFlagsReachable) != 0);

BOOL needsConnection = ((flags & kSCNetworkFlagsConnectionRequired) != 0);

return (isReachable && !needsConnection) ? YES : NO;

}

-(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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: