Performing a network address resolution(执行网络地址解析)
通过互联网进行通信的大多数的应用程序最终都需要转换成一个主机名的IP地址或者是IP地址的主机名。下面的教程将分装网络地址的解析功能继承到一个独立的Objective-c类。你会发现,大部分的CFNetworking API是调用一系列的C函数,并使用类似BSD的API。
开始准备
这个教程是关于iOS和OSX中的执行网络地址解析的教程,不需要考虑额外的框架和兼容性。
怎样做:
下面就让我们开始吧,
创建一个CFNetworkUtilities 头文件
下面的代码片段就是创建CFNetworkUtilities头文件:
#Import <Foundation/Foundation.h>
typedef NS_ENUM(NSUiteger,CFNetworkingSelf.errorCode){
NOERROR, HOSTREDOLUTIONERROR,ADDRESSRESOLUTIONERROR
};
@property int(nonatomic) self.errorCode;
-(NSArray *)addressesForHostname:(NSString *)hostname;
-(NSArray *)hostnamesForAddress:(NSString *)address;
@end
该CFNetworkUtilities头文件首先定义一个枚举数据类型,它会代表我们的错误条件。这些错误条件将被存储在所述的errorCode属性。
我们还将定义下面两个方法来实现:
1. addressesForHostname:此方法是为给定的主机名返回网络地址
2.hostnamesForAddress: 这个方法是给网络地址返回主机名
Creating the CFNetworkUtilities implementation file(创建CFNetworkUtilities实现文件)
下面的代码片段就是创建CFNetworkUtilities实现文件:
#import "CFNetworkUtilities.h"
#if TARGET_OS_IPHONE
#import <CFNetwork/CFNetwork.h>
#else
#import <CoreServices/CoreServices.h>
#endif
#import <sys/types.h>
#import <sys/scoket.h>
#import <netdb.h>
@implementation CFNetworkUtilities
我们通过导入CFNetworkUtilities实现文件来完成我们的地址解析。
现在,让我们创建 addressesForHostname:方法。这个方法是用来获取与主机名相关联的IP地址的列表。
-(NSArray *)addressesForHostname:(NSString *)hostname{
self.errorCode = NOERROR;
char ipAddr[INET6_ADDRSTRLEN];
NSMutableArray *addresses =[ [NSMutableArray alloc] init];
CFHostRef hostRef = CFHostCreateWitName(KCFAllocatorDefault,(CFStringRef)hostname);
}
上面的方法首先将ErrorCode属性设置为NOERROR, 如果这个方法的执行过程中如果发生任何错误,我们将通过设置的errorCode 返回nil.
然后我们定义char类型的IPADDR数组和由INET6_ADDRSTRLEN常数设置的长度,因为是定义的char数组 类型所以,能够容纳IPv6或IPv4地址。INET6_ADDRSTRLEN常数定义为46,INET_ADDRSTRLEN常数定义为16。
我们还定义NSMutableArray对象类存储并返回主机名的IP地址的列表。
我们通过使用CFHostCreateWithName()函数来创建一个CFHost对象,这个函数创建一个CFHost的引用对象,并给一个主机名。你将在本节后面看到在我们的hostnamesForAddress方法中命名为CFHostCreateWithAddress()的另一个函数,这个将返回一个CFHost对象并给出IP地址。下面的代码片段将告诉你CFHostStartInfoResolution()函数是怎样解析的:
BOOL success = CFHostStartInfoResoliution(hostRef,kCFHostAddress,nil);
if (!sucess){
self.errorCode = HOSTRESOLUTIONERROR;
return nil;
}
CFArrayRef addressesRef = CFHostGetAddressing(hostRef,nil);
if (addressesRef == nil){
self.errorCode = HOSTRESOLUTIONERROR;
return nil;
}
在这个例子中,我们使用kCFHostAddress来见左IP地址列表,你还可以使用kCFHostNames常数来检索主机名的列表或者是KCFHostReachability来制定你想要获取的信息。
接下来,我们调用CFHostGetAddressing()函数来检索主机地址列表。你必须在调用CFHostGetAddressing()函数之前,调用CFHostStartInfoResolution()函数来执行地址解析,来完成CFHostGetAddressing()执行实际地址解析功能的函数。
现在,我们需要循环主机列表:
CFIndex numAddresses = CFArrayGetCount(addressesRef);
for (CFIndex currentIndex = 0;currentIndex<numAddress;currentIndex++){
struct sockaddr *address = (struct sockaddr *)CFDataGetBytePtr (CFArrayGetValueAtIndex(addressesRef,currentIndex));
if (address == nil){
self.errorCode = HOSTRESOLUTIONERROR;
return nil;
}
getnameinfo(address,address->sa_len,ipAddr,INET6_ADDRESTRLEN,nil,0,NI_NUMERICHOST);
if (ipAddr == nil){
self.errorCode = HOSTRESOLUTIONERROR;
return nil;
}
[address addObject:[NSString stringWithCString:ipAdde encoding:NSASCIIStringEncoding]];
}
return addresses;
}
CFArrayGetValueAtIndex()函数检索一个给定CFArray中的索引的指针值。CFDataGetBytePtr()函数返回一个执行CFData对象的内部字节,作为一个scokaddr的指针。
在使用getnameinfo()函数后,我们就可以得到从scokaddr结构体返回的IP地址并将其插入到IPADDR字符数组中。NI_NUMERICHOST标识告诉我们,我们得到的返回地址是以数字形式返回而不是主机名。
一旦所有地址被处理,我们将返回地址数组,它包含与主机名相关联的IP地址列表。
那么接下来,我们创建hostnamesForAddress:函数
hostnameForAddress:方法将用于提供IP地址到与该地址相关联的主机名的列表中。
-(NSArray *)hostnameForAddress:(NSString *)address{
self.errorCode = NOERROR;
struct addrinfo hints;
struct addrinfo *result = NULL;
memset (&hints,0,sizeof(hints));
hints.ai_family = AF_UNSPEC;//所有的地址版本
hints.ai_socktype = SOCK_STREAM;//Limit out search to Scoket Stream,se could also set this to SOCK_DGRAM
hints.ai_protocol = 0;
}
该hostnamesForAddress :方法是先定义ErrorCode属性NOERROR 。如果这个方法的执行过程中发生任何错误,我们将设置ErrorCode属性,并返回nil 。
memset()函数是用来清除所需的提示结构的存储器。
现在,我们调用getaddrinfor ( )函数将IP地址转换成addrinfo结构的链表:
int error = getaddrinfo([address cStringUsingEncoding:NSASCIIStringEncoding],NUL:,&hints,&result);
if (error != 0){
self.errorCode = ADDRESSRESOLUTIONERROR;
return nil;
}
CFDataRef addressRef = CFDataCreate(NULL,(UInt 8 *)->result->ai_addr,result->ai_addrlen);
if (addressRef == nil){
self.errorCode = ADDRESSRESOLUTIONERROR;
return nil;
}
freeaddrinfo(result);
getaddrinfo ( )函数获取到的主机名和服务器我们需要以字符数组形式存放,因此我们需要我们的NSString的值转换为字符数组,所以使用的cStringUsingEncoding :在NSString类的方法。