iOS 關于wifi環(huán)境下指定使用蜂窩網(wǎng)

需求

最近做一個需求盛撑,接入電信校驗手機號碼功能電信手機號碼校驗API。通過與電信工作人員溝通,移動端必須在使用電信蜂窩數(shù)據(jù)的時候才可以成功獲取accessCode,用與本機號碼校驗歉眷。也就是如果在wifi和蜂窩數(shù)據(jù)同時打開的情況下,使用蜂窩數(shù)據(jù)做網(wǎng)絡請求才能成功颤枪。什么鬼汗捡??畏纲?這不是偷偷用用戶的數(shù)據(jù)流量嗎扇住?沒辦法,要實現(xiàn)這個功能盗胀,也只能去找對應的解決辦法了艘蹋。

在網(wǎng)上查找資料,受這個切換網(wǎng)卡的啟示票灰,嘗試了一下通過 getifaddrs() 來獲取本機所有地址信息簿训,其中 "pdp_ip0" 的是蜂窩數(shù)據(jù)的地址咱娶。然后 socket 指定這個地址為網(wǎng)卡出口就可以了。

實現(xiàn)

通過socket來實現(xiàn)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 指定了連接的地址和端口,那么既然我們需要指定本地網(wǎng)卡出口屈糊,就需要換一個接入的方法, 如下的接口中可以允許我們指定本地 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 (必須在有網(wǎng)的情況下才能獲取手機的IP地址)
 @return str 本機ip 返回蜂窩數(shù)據(jù)的結(jié)果
 */
- (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;
}

建立連接之后就按照協(xié)議組好 http 請求的包逻锐, 發(fā)送就可以了夫晌。。昧诱。

- (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];
  //發(fā)送數(shù)據(jù)
    [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協(xié)議中每個字段的分隔符
    
    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]];//發(fā)送數(shù)據(jù)的格式
    [packetData appendData:crlfData];

   //組包體
   ...
   
    [packetData appendData:[[NSString stringWithFormat:@"Content-Length: %ld", bodyStr.length] dataUsingEncoding:NSUTF8StringEncoding]];//說明請求體內(nèi)容的長度
    [packetData appendData:crlfData];
    
    [packetData appendData:[@"Connection: close" dataUsingEncoding:NSUTF8StringEncoding]];
    [packetData appendData:crlfData];
    [packetData appendData:crlfData];//注意這里請求頭拼接完成要加兩個回車換行
    //以上http頭信息就拼接完成晓淀,下面繼續(xù)拼接上body信息
    NSString *encodeBodyStr = [NSString stringWithFormat:@"%@\r\n\r\n", bodyStr];//請求體最后也要加上兩個回車換行說明數(shù)據(jù)已經(jīng)發(fā)送完畢
    [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體里的數(shù)據(jù)
        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) {//如果返回的數(shù)據(jù)中不包含以上符號,會崩潰
        sub = [string substringWithRange:NSMakeRange(start.location, end.location-start.location+1)];//這就是服務器返回的body體里的數(shù)據(jù)
        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);
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末盏档,一起剝皮案震驚了整個濱河市凶掰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蜈亩,老刑警劉巖懦窘,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異稚配,居然都是意外死亡畅涂,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進店門道川,熙熙樓的掌柜王于貴愁眉苦臉地迎上來午衰,“玉大人,你說我怎么就攤上這事冒萄‰叮” “怎么了?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵宦言,是天一觀的道長扇单。 經(jīng)常有香客問我,道長奠旺,這世上最難降的妖魔是什么蜘澜? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮响疚,結(jié)果婚禮上鄙信,老公的妹妹穿的比我還像新娘。我一直安慰自己忿晕,他們只是感情好装诡,可當我...
    茶點故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般鸦采。 火紅的嫁衣襯著肌膚如雪宾巍。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天渔伯,我揣著相機與錄音顶霞,去河邊找鬼。 笑死锣吼,一個胖子當著我的面吹牛选浑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播玄叠,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼古徒,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤搓茬,失蹤者是張志新(化名)和其女友劉穎浦马,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了互墓。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,739評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡蒋搜,死狀恐怖篡撵,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情豆挽,我是刑警寧澤育谬,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站帮哈,受9級特大地震影響膛檀,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜娘侍,卻給世界環(huán)境...
    茶點故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一咖刃、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧憾筏,春花似錦嚎杨、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽刨肃。三九已至,卻和暖如春箩帚,著一層夾襖步出監(jiān)牢的瞬間真友,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工膏潮, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留锻狗,地道東北人。 一個月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓焕参,卻偏偏與公主長得像,于是被迫代替她去往敵國和親油额。 傳聞我的和親對象是個殘疾皇子叠纷,可洞房花燭夜當晚...
    茶點故事閱讀 44,647評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 文章首發(fā)于個人blog歡迎指正補充,可聯(lián)系lionsom_lin@qq.com原文地址:《網(wǎng)絡是怎樣連接的》閱讀整...
    lionsom_lin閱讀 14,145評論 6 31
  • 國家電網(wǎng)公司企業(yè)標準(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報批稿:20170802 前言: 排版 ...
    庭說閱讀 10,961評論 6 13
  • 簡介 用簡單的話來定義tcpdump潦嘶,就是:dump the traffic on a network涩嚣,根據(jù)使用者...
    保川閱讀 5,956評論 1 13
  • 2018是不平凡的一年,是喜樂的一年掂僵,是雙倍恩膏也是收獲的一年航厚,感恩進入2018孩子改變很多,使我的心被安慰锰蓬,我的...
    周淑峰閱讀 209評論 0 0
  • 今天是母親的生日幔睬,昨晚匆匆忙忙趕回深圳,就是希望當清晨起床時她能夠看到身邊還有一個陪伴的人芹扭。 1月1...
    齊鎂慧勵閱讀 539評論 2 3