美文网首页iOS开发ios框架我的ios进阶
iOS网络——检测手机网络状态Reachability

iOS网络——检测手机网络状态Reachability

作者: 时间已静止 | 来源:发表于2016-04-26 22:51 被阅读6552次

    一、整体介绍

    • 前面已经介绍了网络访问的NSURLSessionNSURLConnection,还有网页加载有关的webview,基本满足通常的网络相关的开发。
      其实在网络开发中还有比较常用的就是网络状态的检测。苹果对需要联网的应用要求很高,就是必须要进行联网检查。另外,当网络发生异常时能够及时提示用户网络已断开,而不是程序问题造成卡顿;当用户观看视频或下载大文件时,提示用户当前的网络状态为移动流量或wifi下,是否继续使用,以避免在用户不知情下产生过多流量资费等等。

    • 网络状态的检测有多种方法,常用的有三种

    以上三种都有一个缺陷,会在本文最后给出解决方案

    二、苹果Reachability使用

    使用非常简单,将Reachability.hReachability.m加入项目中,在要使用的地方包含Reachability.h头文件,示例代码:

    #import "Reachability.h"
    
    /// 在刚开始就开始监听
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Reachability使用了通知,当网络状态发生变化时发送通知kReachabilityChangedNotification
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(appReachabilityChanged:)
                                                     name:kReachabilityChangedNotification
                                                   object:nil];
        // 检测指定服务器是否可达
        NSString *remoteHostName = @"www.bing.com";
        self.hostReachability = [Reachability reachabilityWithHostName:remoteHostName];
        [self.hostReachability startNotifier];
        // 检测默认路由是否可达
        self.routerReachability = [Reachability reachabilityForInternetConnection];
        [self.routerReachability startNotifier];
    }
    /// 当网络状态发生变化时调用
    - (void)appReachabilityChanged:(NSNotification *)notification{
        Reachability *reach = [notification object];
        if([reach isKindOfClass:[Reachability class]]){
            NetworkStatus status = [reach currentReachabilityStatus];
            // 两种检测:路由与服务器是否可达  三种状态:手机流量联网、WiFi联网、没有联网
            if (reach == self.routerReachability) {
                if (status == NotReachable) {
                    NSLog(@"routerReachability NotReachable");
                } else if (status == ReachableViaWiFi) {
                    NSLog(@"routerReachability ReachableViaWiFi");
                } else if (status == ReachableViaWWAN) {
                    NSLog(@"routerReachability ReachableViaWWAN");
                }
            }
            if (reach == self.hostReachability) {
                NSLog(@"hostReachability");
                if ([reach currentReachabilityStatus] == NotReachable) {
                    NSLog(@"hostReachability failed");
                } else if (status == ReachableViaWiFi) {
                    NSLog(@"hostReachability ReachableViaWiFi");
                } else if (status == ReachableViaWWAN) {
                    NSLog(@"hostReachability ReachableViaWWAN");
                }
            }
            
        }
    }
    /// 取消通知
    - (void)dealloc {
        [[NSNotificationCenter defaultCenter] removeObserver:self name:kReachabilityChangedNotification object:nil];
    }
    

    代码中两种检测:默认路由是否可达、服务器是否可达。有很多人可能有疑问,检测是否联网就可以了,怎么还要检测是否服务器可达?默认路由可达?

    其实虽然联网了,也不一定能访问外网(通常说的互联网)。比如连了一个路由器,但是路由器没有联网,那么也是不能联网的。还有就是网络数据包在网际层传递时,一个路由传到另一个路由称为一跳,当达到255跳(大部分路由设置为255)还没有传到目的地时,网络数据包则丢弃。
    路由器有一套算法来保证路径最优,还有路由表(保存路径表),如果一个数据包在路由表中没有匹配的路径的话,那么路由器就将此数据包发送到默认路由,这里的默认路由就是上面检测的默认路由是否可达。(里面相当复杂,就此打住)

    令人崩溃的事:Reachability并不能检测到服务器是否真的可达,只能检测设备是否连接到局域网,以及用的WiFi还是WWAN。即:把设备网络关了,立马检测出NotReachable,连接到路由器立马检测出是ReachableViaWiFi、、、

    代码中使用了通知,则释放对象时一定要在dealloc中取消通知。我们知道,通知不能在进程间通信,在哪个线程发送通知则在哪个线程执行。如果想在其它线程监听,则在其它线程调用startNotifier函数,新开启的线程默认都没有开启runloop消息循环,因此还要开启runloop,如下:

        // 被通知函数运行的线程应该由startNotifier函数执行的线程决定
        typeof(self) weakSelf = self;
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSString *remoteHostName = @"www.bing.com";
            weakSelf.hostReachability = [Reachability reachabilityWithHostName:remoteHostName];
            [weakSelf.hostReachability startNotifier];
            
            weakSelf.routerReachability = [Reachability reachabilityForInternetConnection];
            [weakSelf.routerReachability startNotifier];
            // 开启当前线程消息循环
            [[NSRunLoop currentRunLoop] run];
        });
    

    最后,如果想取消检测,调用stopNotifier方法即可

    [self.hostReachability stopNotifier];
    [self.routerReachability stopNotifier];
    

    三、AFNetworkReachabilityManager使用

    • 直接使用

    使用CocoaPods或者直接将AFNetwork下载并添加进项目。如果只是使用AFNetworkReachabilityManager而不适用其它网络功能则只将其.m和.h添加进项目即可。AFNetworkReachabilityManager使用了block的方式,当网络状态发生变化就会调用,且block的调用AFN已经将其限定在主线程下。下面介绍直接使用

    #import "AFNetworkReachabilityManager.h"
    - (void)afnReachabilityTest {
        [[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
            // 一共有四种状态
            switch (status) {
                case AFNetworkReachabilityStatusNotReachable:
                    NSLog(@"AFNetworkReachability Not Reachable");
                    break;
                case AFNetworkReachabilityStatusReachableViaWWAN:
                    NSLog(@"AFNetworkReachability Reachable via WWAN");
                    break;
                case AFNetworkReachabilityStatusReachableViaWiFi:
                    NSLog(@"AFNetworkReachability Reachable via WiFi");
                    break;
                case AFNetworkReachabilityStatusUnknown:
                default:
                    NSLog(@"AFNetworkReachability Unknown");
                    break;
            }
        }];
        
        [[AFNetworkReachabilityManager sharedManager] startMonitoring];
    }
    
    • 使用AFHTTPSessionManager

    当使用AFN网络框架时,大多情况下,我们使用AFNetwork时会创建一个网络中间单例类,以防止换网络框架时要改动太多,比如替换之前用的多的ASI,如果有个中间类的话,替换就很简单,只需要修改中间类即可。使用时调用[NetworkTools sharedManager];即可

    /// 头文件
    #import "AFHTTPSessionManager.h"
    
    @interface NetworkTools : AFHTTPSessionManager
    + (instancetype)sharedManager;
    @end
    
    ---------------------------------------------------------------------------------
    
    /// .m文件
    #import "NetworkTools.h"
    
    @implementation NetworkTools
    + (instancetype)sharedManager {
        static dispatch_once_t once;
        static id instance;
        dispatch_once(&once, ^{
            //#warning 基地址
            //        instance = [[self alloc] initWithBaseURL:[NSURL URLWithString:@"http://www.bing.com"]];
            instance = [[self alloc] init];
        });
        return instance;
    }
    - (instancetype)init {
        if ((self = [super init])) {
            // 设置超时时间,afn默认是60s
            self.requestSerializer.timeoutInterval = 30;
            // 响应格式添加text/plain
            self.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", @"text/plain", nil];
    
            // 监听网络状态,每当网络状态发生变化就会调用此block
            [self.reachabilityManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
                switch (status) {
                    case AFNetworkReachabilityStatusNotReachable:     // 无连线
                        NSLog(@"AFNetworkReachability Not Reachable");
                        break;
                    case AFNetworkReachabilityStatusReachableViaWWAN: // 手机自带网络
                        NSLog(@"AFNetworkReachability Reachable via WWAN");
                        break;
                    case AFNetworkReachabilityStatusReachableViaWiFi: // WiFi
                        NSLog(@"AFNetworkReachability Reachable via WiFi");
                        break;
                    case AFNetworkReachabilityStatusUnknown:          // 未知网络
                    default:
                        NSLog(@"AFNetworkReachability Unknown");
                        break;
                }
            }];
            // 开始监听
            [self.reachabilityManager startMonitoring];
        }
        return self;
    }
    @end
    

    四、第三方框架使用

    这个使用会更方便一点,有block和通知两种方式,且支持多线程,这里不再详细介绍,README.md有使用方法:

    - (void)viewDidLoad {
        [super viewDidLoad];
        // Allocate a reachability object
        Reachability* reach = [Reachability reachabilityWithHostname:@"www.bing.com"];
        
        // Set the blocks
        reach.reachableBlock = ^(Reachability*reach) {
            // keep in mind this is called on a background thread
            // and if you are updating the UI it needs to happen
            // on the main thread, like this:
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"REACHABLE!");
            });
        };
        
        reach.unreachableBlock = ^(Reachability*reach) {
            NSLog(@"UNREACHABLE!");
        };
        
        // Start the notifier, which will cause the reachability object to retain itself!
        [reach startNotifier];
    }
    

    问题解决

    三种方式差不多,它们在检测设备是否连接局域网和连接方式时很灵敏,但是不能检测服务器是否可达。因为它们底层都是使用了SCNetworkReachabilitySCNetworkReachability发送网络数据包到服务器,但它并不会确认服务器真的收到了此数据包。所以,如果我们想确认是否服务器可达,则需要发送一个真实的网络请求。或者我们使用socket编程,建立一个tcp链接来检测(三次握手成功),只要链接成功则服务器可达。这样只会发送tcpip的报头,数据量最小。如果网络环境差,connect函数会阻塞(可以尝试select函数),所以最后不要在主线程下,调用示例代码,示例如下:

    /// 服务器可达返回true
    - (BOOL)socketReachabilityTest {
        // 客户端 AF_INET:ipv4  SOCK_STREAM:TCP链接
        int socketNumber = socket(AF_INET, SOCK_STREAM, 0);
        // 配置服务器端套接字
        struct sockaddr_in serverAddress;
        // 设置服务器ipv4
        serverAddress.sin_family = AF_INET;
        // 百度的ip
        serverAddress.sin_addr.s_addr = inet_addr("202.108.22.5");
        // 设置端口号,HTTP默认80端口
        serverAddress.sin_port = htons(80);
        if (connect(socketNumber, (const struct sockaddr *)&serverAddress, sizeof(serverAddress)) == 0) {
            close(socketNumber);
            return true;
        }
        close(socketNumber);;
        return false;
    }
    
    

    示例代码下载

    在github下载

    参考:http://www.cnblogs.com/mddblog/p/5304346.html

    相关文章

      网友评论

      • 纠结的哈士奇:想问下楼主 ipv6下 会有影响不?
      • SmallSunSir:楼主,我手机检测不到wifi状态,一直是status = NotReachable
      • 半江瑟瑟:**<发现有时候通知会发出2次;有时候通知发出1次?why!**>
        我觉得应该发出2次才对啊 startNotifier方法调用了2次啊!
      • Aiverson:貌似不支持ip6
      • 程旭媛:请问如何实现酷我音乐盒里“仅wifi联网”的功能呢?
        时间已静止:@程旭媛 根据博客中提到的网络状态监测也可以,是wifi就联网;另外也可以设置NSURLSessionConfiguration一个通过蜂窝网联网的属性为NO(默认是YES)。酷我网络访问用的是AFNetworking,AFN也提供了实时网络监测,也可以配置Configuration
      • a558d5f37b23:你这个检查是否能连接到服务器,也有问题,如果连接到没有外网的路由器,connect还是会返回0
        时间已静止:@勤严 这种方法是建立TCP点对点链接的方式,不会有问题的,另外你也可以试下Ping
        a558d5f37b23:你可以试一下,我以前试过
        时间已静止:@勤严 你测试了吗?
      • Bastionhh:serverAddress.sin_addr.s_addr = inet_addr("202.108.22.5");
        这句报错
        时间已静止:@Bastionhh 要包含头文件:#import <arpa/inet.h>
      • da210647ad60:感谢分享
        时间已静止:@Tech_Jene :v:
      • 这小歌不错:赞一个

      本文标题:iOS网络——检测手机网络状态Reachability

      本文链接:https://www.haomeiwen.com/subject/bdqcrttx.html