需求
最近做一個需求盛撑,接入電信校驗手機號碼功能電信手機號碼校驗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
- (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);
}