iOS 关于wifi环境下指定使用蜂窝网

2019-01-04  本文已影响53人  yydev

需求

最近做一个需求,接入电信校验手机号码功能电信手机号码校验API。通过与电信工作人员沟通,移动端必须在使用电信蜂窝数据的时候才可以成功获取accessCode,用与本机号码校验。也就是如果在wifi和蜂窝数据同时打开的情况下,使用蜂窝数据做网络请求才能成功。什么鬼???这不是偷偷用用户的数据流量吗?没办法,要实现这个功能,也只能去找对应的解决办法了。

在网上查找资料,受这个切换网卡的启示,尝试了一下通过 getifaddrs() 来获取本机所有地址信息,其中 "pdp_ip0" 的是蜂窝数据的地址。然后 socket 指定这个地址为网卡出口就可以了。

实现

通过socket来实现http请求,参考CocoaAsyncSocket中http demo

http demo-1 http demo-2
- (void)startSocket
{
    
    asyncSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
    NSError *error = nil;
    uint16_t port = WWW_PORT;
    if (port == 0)
    {
    #if USE_SECURE_CONNECTION
        port = 443; // HTTPS
    #else
        port = 80;  // HTTP
    #endif
    }
    
    if (![asyncSocket connectToHost:WWW_HOST onPort:port error:&error]){
        DDLogError(@"Unable to connect to due to invalid configuration: %@", error);
    }else{
        DDLogVerbose(@"Connecting to \"%@\" on port %hu...", WWW_HOST, port);
    }
    ...
}

startSocket 中 可以看到给 socket 指定了连接的地址和端口,那么既然我们需要指定本地网卡出口,就需要换一个接入的方法, 如下的接口中可以允许我们指定本地 interface, 剩下的就是获取本机ip, 并传入这个方法啦。

- (BOOL)connectToAddress:(NSData *)remoteAddr
            viaInterface:(nullable NSString *)interface
             withTimeout:(NSTimeInterval)timeout
                   error:(NSError **)errPtr;

获取本机ip

#define IOS_CELLULAR    @"pdp_ip0"
#define IOS_WIFI        @"en0"
#define IP_ADDR_IPv4    @"ipv4"
#define IP_ADDR_IPv6    @"ipv6"
/**
 获取本机ip (必须在有网的情况下才能获取手机的IP地址)
 @return str 本机ip 返回蜂窝数据的结果
 */
- (NSString *)getDeviceIPAddress:(BOOL)preferIPv4 {
   
    NSDictionary *addresses = [self getIPAddresses];
    NSLog(@"addresses==%@", addresses);
    NSString *address = addresses[IOS_CELLULAR @"/" IP_ADDR_IPv4] ?:addresses[IOS_CELLULAR @"/" IP_ADDR_IPv6];
    return address ? address : nil;
}

//获取所有相关IP信息
- (NSDictionary *)getIPAddresses
{
    NSMutableDictionary *addresses = [NSMutableDictionary dictionaryWithCapacity:8];
    
    // retrieve the current interfaces - returns 0 on success
    struct ifaddrs *interfaces;
    if(!getifaddrs(&interfaces)) {
        // Loop through linked list of interfaces
        struct ifaddrs *interface;
        for(interface=interfaces; interface; interface=interface->ifa_next) {
            if(!(interface->ifa_flags & IFF_UP) /* || (interface->ifa_flags & IFF_LOOPBACK) */ ) {
                continue; // deeply nested code harder to read
            }
            const struct sockaddr_in *addr = (const struct sockaddr_in*)interface->ifa_addr;
            char addrBuf[ MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) ];
            if(addr && (addr->sin_family== AF_INET || addr->sin_family==AF_INET6)) {
                NSString *name = [NSString stringWithUTF8String:interface->ifa_name];
                NSString *type;
                if(addr->sin_family == AF_INET) {
                    if(inet_ntop(AF_INET, &addr->sin_addr, addrBuf, INET_ADDRSTRLEN)) {
                        type = IP_ADDR_IPv4;
                    }
                } else {
                    const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6*)interface->ifa_addr;
                    if(inet_ntop(AF_INET6, &addr6->sin6_addr, addrBuf, INET6_ADDRSTRLEN)) {
                        type = IP_ADDR_IPv6;
                    }
                }
                if(type) {
                    NSString *key = [NSString stringWithFormat:@"%@/%@", name, type];
                    addresses[key] = [NSString stringWithUTF8String:addrBuf];
                }
            }
        }
        // Free memory
        freeifaddrs(interfaces);
    }
    return [addresses count] ? addresses : nil;
}

建立连接之后就按照协议组好 http 请求的包, 发送就可以了。。。

- (void)startSocket
{
    
    asyncSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
    
    NSError *error = nil;
    
    uint16_t port = WWW_PORT;
    if (port == 0)
    {
    #if USE_SECURE_CONNECTION
        port = 443; // HTTPS
    #else
        port = 80;  // HTTP
    #endif
    }
    NSString *interface = [self getDeviceIPAddress:YES];
    //[asyncSocket connectToHost:WWW_HOST onPort:port error:&error]
    
    if (![asyncSocket connectToHost:WWW_HOST onPort:port viaInterface:interface withTimeout:-1 error:&error])
    {
        DDLogError(@"Unable to connect to due to invalid configuration: %@", error);
    }
    else
    {
        DDLogVerbose(@"Connecting to \"%@\" on port %hu...", WWW_HOST, port);
    }
    
#if USE_SECURE_CONNECTION
    
    #if USE_CFSTREAM_FOR_TLS
    {
        // Use old-school CFStream style technique
        
        NSDictionary *options = @{
            GCDAsyncSocketUseCFStreamForTLS : @(YES),
            GCDAsyncSocketSSLPeerName : CERT_HOST
        };
        
        DDLogVerbose(@"Requesting StartTLS with options:\n%@", options);
        [asyncSocket startTLS:options];
    }
    #elif MANUALLY_EVALUATE_TRUST
    {
        // Use socket:didReceiveTrust:completionHandler: delegate method for manual trust evaluation
        
        NSDictionary *options = @{
            GCDAsyncSocketManuallyEvaluateTrust : @(YES),
            GCDAsyncSocketSSLPeerName : CERT_HOST
        };
        
        DDLogVerbose(@"Requesting StartTLS with options:\n%@", options);
        [asyncSocket startTLS:options];
    }
    #else
    {
        // Use default trust evaluation, and provide basic security parameters
        
        NSDictionary *options = @{
            GCDAsyncSocketSSLPeerName : CERT_HOST
        };
        
        DDLogVerbose(@"Requesting StartTLS with options:\n%@", options);
        [asyncSocket startTLS:options];
    }
    #endif
    
#endif
}
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port
{
    DDLogVerbose(@"socket:didConnectToHost:%@ port:%hu", host, port);
        
    NSMutableData *requestData = [self sendData];
  //发送数据
    [asyncSocket writeData:requestData withTimeout:-1.0 tag:0];
    
#if READ_HEADER_LINE_BY_LINE
    
    // Now we tell the socket to read the first line of the http response header.
    // As per the http protocol, we know each header line is terminated with a CRLF (carriage return, line feed).
    
    [asyncSocket readDataToData:[GCDAsyncSocket CRLFData] withTimeout:-1.0 tag:0];
    
#else
    
    [asyncSocket readDataWithTimeout:-1 tag:0];
    
#endif
}

- (NSMutableData *)sendData {
    
    NSMutableData *packetData = [[NSMutableData alloc] init];
    NSData *crlfData = [@"\r\n" dataUsingEncoding:NSUTF8StringEncoding];//回车换行是http协议中每个字段的分隔符
    
    NSString *requestStrFrmt = @"POST /openapi/networkauth/preGetMobile.do HTTP/1.1\r\nHost: %@\r\n";
    NSString *requestStr = [NSString stringWithFormat:requestStrFrmt, WWW_HOST];
    DDLogVerbose(@"Sending HTTP Request:\n%@", requestStr);
    
    [packetData appendData:[requestStr dataUsingEncoding:NSUTF8StringEncoding]];//拼接的请求行

    [packetData appendData:[@"Content-Type: application/x-www-form-urlencoded; charset=utf-8" dataUsingEncoding:NSUTF8StringEncoding]];//发送数据的格式
    [packetData appendData:crlfData];

   //组包体
   ...
   
    [packetData appendData:[[NSString stringWithFormat:@"Content-Length: %ld", bodyStr.length] dataUsingEncoding:NSUTF8StringEncoding]];//说明请求体内容的长度
    [packetData appendData:crlfData];
    
    [packetData appendData:[@"Connection: close" dataUsingEncoding:NSUTF8StringEncoding]];
    [packetData appendData:crlfData];
    [packetData appendData:crlfData];//注意这里请求头拼接完成要加两个回车换行
    //以上http头信息就拼接完成,下面继续拼接上body信息
    NSString *encodeBodyStr = [NSString stringWithFormat:@"%@\r\n\r\n", bodyStr];//请求体最后也要加上两个回车换行说明数据已经发送完毕
    [packetData appendData:[encodeBodyStr dataUsingEncoding:NSUTF8StringEncoding]];
    return packetData;
}

- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag
{
    DDLogVerbose(@"socket:didWriteDataWithTag:");
}

- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
    DDLogVerbose(@"socket:didReadData:withTag:");
    
    NSString *httpResponse = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    
#if READ_HEADER_LINE_BY_LINE
    
    DDLogInfo(@"Line httpResponse: %@", httpResponse);
    
    // As per the http protocol, we know the header is terminated with two CRLF's.
    // In other words, an empty line.
    
    if ([data length] == 2) // 2 bytes = CRLF
    {
        DDLogInfo(@"<done>");
    }
    else
    {
        // Read the next line of the header
        [asyncSocket readDataToData:[GCDAsyncSocket CRLFData] withTimeout:-1.0 tag:0];
    }
    
#else
    
    NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    //NSLog(@"%@", string);
    NSString *testStr = @" HTTP/1.1  302  Found\r\n";
    NSRange startCode = [testStr rangeOfString:@"HTTP/"];
    NSRange endCode = [testStr rangeOfString:@"\r\n"];
    if (endCode.location != NSNotFound && startCode.location != NSNotFound) {
        NSString *sub = [testStr substringWithRange:NSMakeRange(startCode.location, endCode.location-startCode.location+1)];//这就是服务器返回的body体里的数据
        NSMutableArray *subArr = [[sub componentsSeparatedByString:@" "] mutableCopy];
        [subArr removeObject:@""];
        if (subArr.count > 2) {
            NSString *code = subArr[1];
            NSLog(@"code === %@", code);
        }
        NSLog(@"code str === %@", sub);
    }
    NSRange start = [string rangeOfString:@"{"];
    NSRange end = [string rangeOfString:@"}"];
    NSString *sub;
    if (end.location != NSNotFound && start.location != NSNotFound) {//如果返回的数据中不包含以上符号,会崩溃
        sub = [string substringWithRange:NSMakeRange(start.location, end.location-start.location+1)];//这就是服务器返回的body体里的数据
        NSData *subData = [sub dataUsingEncoding:NSUTF8StringEncoding];;
        NSDictionary *subDic = [NSJSONSerialization JSONObjectWithData:subData options:0 error:nil];
        NSLog(@"result === %@", subDic);
    }

    
    DDLogInfo(@"Full HTTP Response:\n%@", httpResponse);
    
#endif
    
}

- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
{
    // Since we requested HTTP/1.0, we expect the server to close the connection as soon as it has sent the response.
    
    DDLogVerbose(@"socketDidDisconnect:withError: \"%@\"", err);
}
上一篇 下一篇

猜你喜欢

热点阅读