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

需求

最近做一个需求,接入电信校验手机号码功能电信手机号码校验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)connectToHost:(NSString *)inHost
               onPort:(uint16_t)port
         viaInterface:(NSString *)inInterface
          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);
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,898评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,401评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,058评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,539评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,382评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,319评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,706评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,370评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,664评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,715评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,476评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,326评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,730评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,003评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,275评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,683评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,877评论 2 335

推荐阅读更多精彩内容

  • 文章首发于个人blog欢迎指正补充,可联系lionsom_lin@qq.com原文地址:《网络是怎样连接的》阅读整...
    lionsom_lin阅读 14,088评论 6 31
  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,808评论 6 13
  • 简介 用简单的话来定义tcpdump,就是:dump the traffic on a network,根据使用者...
    保川阅读 5,934评论 1 13
  • 2018是不平凡的一年,是喜乐的一年,是双倍恩膏也是收获的一年,感恩进入2018孩子改变很多,使我的心被安慰,我的...
    周淑峰阅读 202评论 0 0
  • 今天是母亲的生日,昨晚匆匆忙忙赶回深圳,就是希望当清晨起床时她能够看到身边还有一个陪伴的人。 1月1...
    齐镁慧励阅读 525评论 2 3