网络环境监听(Reachability)

  • 没事看看这个网络环境监听的东西,待会还要去学英语呢
  • 我看的这个是属于ASI框架的
  • 大概看了一下,最核心的判断网络的方法是这个
  • SCNetworkReachabilityGetFlags
  • 这个c函数获取到各种flag,它是系统SystemConfiguration框架里SCNetworkReachability.h里的一个方法
  • 看它的介绍

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /*!
    @function SCNetworkReachabilityGetFlags
    @discussion Determines if the given target is reachable using the
    current network configuration.
    @param target The network reference associated with the address or name
    to be checked for reachability.
    @param flags A pointer to memory that will be filled with the
    SCNetworkReachabilityFlags detailing the reachability
    of the specified target.
    @result Returns TRUE if the network connection flags are valid;
    FALSE if the status could not be determined.
    */
  • 大概意思就是根据传入的target(SCNetworkReachabilityRef)来得到当前网络状态

  • 让我们看看得到的flag分别怎么处理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    - (NetworkStatus)networkStatusNewForFlags:(SCNetworkReachabilityFlags)flags
    {
    if ((flags & kSCNetworkReachabilityFlagsReachable) == 0)
    {
    return NotReachable;
    }
    NetworkStatus retVal = NotReachable;
    if ((flags & kSCNetworkReachabilityFlagsConnectionRequired) == 0)
    {
    // if target host is reachable and no connection is required
    // then we'll assume (for now) that your on Wi-Fi
    retVal = ReachableViaWiFi;
    }
    if ((((flags & kSCNetworkReachabilityFlagsConnectionOnDemand ) != 0) ||
    (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0))
    {
    // ... and the connection is on-demand (or on-traffic) if the
    // calling application is using the CFSocketStream or higher APIs
    if ((flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0)
    {
    // ... and no [user] intervention is needed
    retVal = ReachableViaWiFi;
    }
    }
    if ((flags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN)
    {
    if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0)
    {
    CTTelephonyNetworkInfo *info = [[CTTelephonyNetworkInfo alloc] init];
    NSString *currentRadioAccessTechnology = info.currentRadioAccessTechnology;
    if (currentRadioAccessTechnology) {
    if ([currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyLTE]) {
    return kReachableVia4G;
    } else if ([currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyEdge] || [currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyGPRS]) {
    return kReachableVia2G;
    } else {
    return kReachableVia3G;
    }
    }
    }
    if ((flags & kSCNetworkReachabilityFlagsTransientConnection) == kSCNetworkReachabilityFlagsTransientConnection) {
    if((flags & kSCNetworkReachabilityFlagsConnectionRequired) == kSCNetworkReachabilityFlagsConnectionRequired) {
    return kReachableVia2G;
    }
    return kReachableVia3G;
    }
    return kReachableViaWWAN;
    }
    return retVal;
    }
  • 上面这里将一个方法,一个生产flag的东西当做参数传了进去

  • 很明显,利用runloop进行网络的环境监听
  • 当有变动时,则会调用ReachabilityCallback函数
  • 这个函数也是有讲究的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    /*!
    @typedef SCNetworkReachabilityCallBack
    @discussion Type of the callback function used when the
    reachability of a network address or name changes.
    @param target The SCNetworkReachability reference being monitored
    for changes.
    @param flags The new SCNetworkReachabilityFlags representing the
    reachability status of the network address/name.
    @param info A C pointer to a user-specified block of data.
    */
    typedef void (*SCNetworkReachabilityCallBack) (
    SCNetworkReachabilityRef target,
    SCNetworkReachabilityFlags flags,
    void * __nullable info
    );
  • 看它说明

    the callback function used when the reachability of a network address or name changes

  • 为网络改变而生的回调,哈哈

  • 再来看看这个设置callback的方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    SCNetworkReachabilitySetCallback
    /////////////
    /*!
    @function SCNetworkReachabilitySetCallback
    @discussion Assigns a client to a target, which receives callbacks
    when the reachability of the target changes.
    @param target The network reference associated with the address or
    name to be checked for reachability.
    @param callout The function to be called when the reachability of the
    target changes. If NULL, the current client for the target
    is removed.
    @param context The SCNetworkReachabilityContext associated with
    the callout. The value may be NULL.
    @result Returns TRUE if the notification client was successfully set.
    */
    Boolean
    SCNetworkReachabilitySetCallback (
    SCNetworkReachabilityRef target,
    SCNetworkReachabilityCallBack __nullable callout,
    SCNetworkReachabilityContext * __nullable context
    ) __OSX_AVAILABLE_STARTING(__MAC_10_3,__IPHONE_2_0);
  • 所以,你也可以自己写个网络环境监听,知道系统方法干啥的就行了

  • 最后别忘了释放监听

    1
    2
    3
    4
    5
    6
    7
    - (void)stopNotifier
    {
    if (_reachabilityRef != NULL)
    {
    SCNetworkReachabilityUnscheduleFromRunLoop(_reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
    }
    }
  • 细看就可以大概了解这些flag是怎么运作的

  • 接下来看看是如何做到实时的网络环境监听的
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    - (BOOL)startNotifier
    {
    BOOL returnValue = NO;
    SCNetworkReachabilityContext context = {0, (__bridge void *)(self), NULL, NULL, NULL};
    if (SCNetworkReachabilitySetCallback(_reachabilityRef, ReachabilityCallback, &context))
    {
    if (SCNetworkReachabilityScheduleWithRunLoop(_reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode))
    {
    returnValue = YES;
    }
    }
    return returnValue;
    }

  • 这里附上一个监测ipv6的东西
  • 这里先贴上两个工具方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    /**
    * @brief 判断是否为IPv4地址
    *
    * @return
    */
    - (BOOL)isIPv4Address:(NSString *)addr {
    if (addr == nil) {
    return NO;
    }
    const char *utf8 = [addr UTF8String];
    // Check valid IPv4.
    struct in_addr dst;
    int success = inet_pton(AF_INET, utf8, &(dst.s_addr));
    return success == 1;
    }
    /**
    * @brief 判断是否为IPv6地址
    *
    * @return
    */
    - (BOOL)isIPv6Address:(NSString *)addr {
    if (addr == nil) {
    return NO;
    }
    const char *utf8 = [addr UTF8String];
    // Check valid IPv6.
    struct in6_addr dst6;
    int success = inet_pton(AF_INET6, utf8, &dst6);
    return (success == 1);
    }
  • 然后再进行下面的逻辑运算

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    dnsIPStack = IPLocalIPStack_IPv4 | IPLocalIPStack_IPv6;
    dnsIPStack &= [self getDNSServersIpStack];
    if (dnsIPStack & IPLocalIPStack_IPv4) {
    // support IPv4
    isIPv6Only = NO;
    } else if (dnsIPStack & IPLocalIPStack_IPv6) {
    // IPv6-Only
    isIPv6Only = YES;
    [[OSSIPv6PrefixResolver getInstance] updateIPv6Prefix];
    } else {
    KGIPv6Log(@"[%s]: Error.", __FUNCTION__);
    isIPv6Only = NO;
    }
  • 其中如果是ipv6我们就要做些事情了,不过先贴上完整代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    - (BOOL)isIPv6OnlyNetwork {
    @synchronized(self) {
    if (isIPv6OnlyResolved) {
    return isIPv6Only;
    }
    struct in6_addr addr6_gateway = {0};
    if (0 != getdefaultgateway6(&addr6_gateway) || IN6_IS_ADDR_UNSPECIFIED(&addr6_gateway)) {
    isIPv6Only = NO;
    return isIPv6Only;
    }
    struct in_addr addr_gateway = {0};
    if (0 != getdefaultgateway4(&addr_gateway)) {
    isIPv6Only = YES;
    [[OSSIPv6PrefixResolver getInstance] updateIPv6Prefix];
    return isIPv6Only;
    }
    if (INADDR_NONE == addr_gateway.s_addr
    || INADDR_ANY == addr_gateway.s_addr ) {
    isIPv6Only = YES;
    [[OSSIPv6PrefixResolver getInstance] updateIPv6Prefix];
    return isIPv6Only;
    }
    int haveIPv4 = _have_ipv4();
    int haveIPv6 = _have_ipv6();
    int local_stack = 0;
    if (haveIPv4) { local_stack |= IPLocalIPStack_IPv4; }
    if (haveIPv6) { local_stack |= IPLocalIPStack_IPv6; }
    if (IPLocalIPStack_IPv4 == local_stack) {
    isIPv6Only = NO;
    return isIPv6Only;
    } else if (IPLocalIPStack_IPv6 == local_stack) {
    isIPv6Only = YES;
    [[OSSIPv6PrefixResolver getInstance] updateIPv6Prefix];
    return isIPv6Only;
    }
    KGIPv6Log(@"Start resolved network to see if in IPv6-Only env.");
    int dnsIPStack = 0;
    dnsIPStack = IPLocalIPStack_IPv4 | IPLocalIPStack_IPv6;
    dnsIPStack &= [self getDNSServersIpStack];
    if (dnsIPStack & IPLocalIPStack_IPv4) {
    // support IPv4
    isIPv6Only = NO;
    } else if (dnsIPStack & IPLocalIPStack_IPv6) {
    // IPv6-Only
    isIPv6Only = YES;
    [[OSSIPv6PrefixResolver getInstance] updateIPv6Prefix];
    } else {
    KGIPv6Log(@"[%s]: Error.", __FUNCTION__);
    isIPv6Only = NO;
    }
    isIPv6OnlyResolved = YES;
    if (isIPv6Only) {
    KGIPv6Log(@"[%s]: IPv6-Only network now.", __FUNCTION__);
    } else {
    KGIPv6Log(@"[%s]: Not IPv6-Only network now.", __FUNCTION__);
    }
    return isIPv6Only;
    }
    }
  • 下面我们看一下是如何判断当前网络是否为ipv6only的

  • 答案是查询本机DNS Servers协议

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    - (int)getDNSServersIpStack {
    int dns_stack = 0;
    res_state res = malloc(sizeof(struct __res_state));
    int result = res_ninit(res);
    if (result == 0) {
    union res_9_sockaddr_union *addr_union = malloc(res->nscount * sizeof(union res_9_sockaddr_union));
    res_getservers(res, addr_union, res->nscount);
    for (int i = 0; i < res->nscount; i++) {
    if (addr_union[i].sin.sin_family == AF_INET) {
    char ip[INET_ADDRSTRLEN];
    if (inet_ntop(AF_INET, &(addr_union[i].sin.sin_addr), ip, INET_ADDRSTRLEN)) {
    dns_stack |= IPLocalIPStack_IPv4;
    }
    } else if (addr_union[i].sin6.sin6_family == AF_INET6) {
    char ip[INET6_ADDRSTRLEN];
    if (inet_ntop(AF_INET6, &(addr_union[i].sin6.sin6_addr), ip, INET6_ADDRSTRLEN)) {
    dns_stack |= IPLocalIPStack_IPv6;
    }
    } else {
    KGIPv6Log(@"%s: Undefined family.", __FUNCTION__);
    }
    }
    }
    res_nclose(res);
    return dns_stack;
    }
  • 我们看看这个OSSIPv6PrefixResolver是如何解决ipv6问题的

  • 直接就是下面的转换,不过还需要一个函数解决prefix的问题,代码下面附上

    1
    2
    3
    4
    5
    6
    7
    8
    9
    if (([[KGIPv6Adapater sharedInstance] isIPv6OnlyNetwork]
    && [[UIDevice currentDevice].systemVersion floatValue] < 9.0)) {
    NSURL *tempURL = [NSURL URLWithString:domainURLString];
    if ([[KGIPv6Adapater sharedInstance] isIPv4Address:tempURL.host]) {
    NSString *tempIPv6String = [[KGIPv6Adapater sharedInstance] handleIpv4Address:tempURL.host];
    domainURLString = [domainURLString stringByReplacingOccurrencesOfString:tempURL.host
    withString:tempIPv6String];
    }
    }
  • 查看当前是否只允许ipv6网络,是则将ipv4转换成ipv6

  • 下面是转换算法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    - (NSString *)convertIPv4toIPv6:(NSString *)ipv4 {
    if ([ipv4 length] <= 0) {
    return nil;
    }
    __uint8_t ipv6[16] = {0x00};
    __uint8_t length = [self resolveIPv6Prefix:ipv6];
    if (length <= 0) {
    return nil;
    }
    in_addr_t addr_v4 = inet_addr([ipv4 UTF8String]);
    // 按length的不同情况进行处理
    if (length==4 || length==12) { //32 bits or 96 bits
    ipv6[length+0] |= (__uint8_t)(addr_v4>>0 & 0xff);
    ipv6[length+1] |= (__uint8_t)(addr_v4>>8 & 0xff);
    ipv6[length+2] |= (__uint8_t)(addr_v4>>16 & 0xff);
    ipv6[length+3] |= (__uint8_t)(addr_v4>>24 & 0xff);
    }
    else if (length == 5) { //40 bits :a.b.c.0.d
    ipv6[length+0] |= (__uint8_t)(addr_v4>>0 & 0xff);
    ipv6[length+1] |= (__uint8_t)(addr_v4>>8 & 0xff);
    ipv6[length+2] |= (__uint8_t)(addr_v4>>16 & 0xff);
    ipv6[length+4] |= (__uint8_t)(addr_v4>>24 & 0xff);
    }
    else if (length == 6) { //48 bits :a.b.0.c.d
    ipv6[length+0] |= (__uint8_t)(addr_v4>>0 & 0xff);
    ipv6[length+1] |= (__uint8_t)(addr_v4>>8 & 0xff);
    ipv6[length+3] |= (__uint8_t)(addr_v4>>16 & 0xff);
    ipv6[length+4] |= (__uint8_t)(addr_v4>>24 & 0xff);
    }
    else if (length == 7) { //56 bits :a.0.b.c.d
    ipv6[length+0] |= (__uint8_t)(addr_v4>>0 & 0xff);
    ipv6[length+2] |= (__uint8_t)(addr_v4>>8 & 0xff);
    ipv6[length+3] |= (__uint8_t)(addr_v4>>16 & 0xff);
    ipv6[length+4] |= (__uint8_t)(addr_v4>>24 & 0xff);
    }
    else if (length == 8) { //64 bits :0.a.b.c.d
    ipv6[length+1] |= (__uint8_t)(addr_v4>>0 & 0xff);
    ipv6[length+2] |= (__uint8_t)(addr_v4>>8 & 0xff);
    ipv6[length+3] |= (__uint8_t)(addr_v4>>16 & 0xff);
    ipv6[length+4] |= (__uint8_t)(addr_v4>>24 & 0xff);
    }
    // 构造IPv6的结构
    char addr_text[ MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) ];
    if(inet_ntop(AF_INET6, ipv6, addr_text, INET6_ADDRSTRLEN)) {
    NSString *ret = [NSString stringWithUTF8String:addr_text];
    return ret;
    }
    return nil;
    }
  • 解决ipv6prefix问题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    - (__uint8_t)resolveIPv6Prefix:(__uint8_t *)prefix {
    if ( !prefix ) {
    return 0;
    }
    __uint8_t len = prefixLen;
    memcpy(prefix, ipv6Prefix, prefixLen);
    @synchronized(self) {
    if (ipv6PrefixResolveStatus==IPv6PrefixUnResolved ) {
    ipv6PrefixResolveStatus = IPv6PrefixResolving;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    struct addrinfo hints, *addr;
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = PF_INET6;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_DEFAULT;
    if (0 != getaddrinfo("ipv4only.arpa", "http", &hints, &addr)) {
    ipv6PrefixResolveStatus = IPv6PrefixUnResolved;
    return;
    }
    if (addr && AF_INET6 == addr->ai_addr->sa_family) {
    struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)(addr->ai_addr);
    if ( !addr6 ) {
    ipv6PrefixResolveStatus = IPv6PrefixUnResolved;
    return;
    }
    __uint8_t* u8 = addr6->sin6_addr.__u6_addr.__u6_addr8;
    for (__uint8_t i=0; i < V6_PREFIX_TABLE_SIZE; i++) {
    if ([self isIPv6Prefix:(__uint8_t *)V6_PREFIX_CONTENT_TABLE[i]
    withPrefixLen:V6_PREFIX_SIZE_TABLE[i]
    withIP:u8
    withIPLen:16]) {
    ipv6Prefix = (__uint8_t *)V6_PREFIX_CONTENT_TABLE[i];
    prefixLen = V6_PREFIX_SIZE_TABLE[i];
    ipv6PrefixResolveStatus = IPv6PrefixResolved;
    break;
    }
    }
    ipv6PrefixResolveStatus = IPv6PrefixUnResolved;
    }
    });
    }
    }
    return len;
    }
  • ok,本期讲座到此结束