注:
-
POSIX
:表示可移植操作系统接口(Portable Operating System Interface ,缩写为 POSIX ),POSIX标准定义了操作系统应该为应用程序提供的接口标准 -
BSD
:(Berkeley Software Distribution,伯克利软件套件)是Unix的衍生系统 -
Byte order
:字节顺序,指的是数据存储到内存中的顺序,如果要让两台使用不同字节顺序的设备通信,你需要使用某些函数来转换。
BSD Socket API是一系列用来处理网络通信的标准函数,所有现代的操作系统对Berkeley Socket接口都有一些实现,来将设备连接到网络上。
BSD sockets建立连接时,一般依赖于”客户端/服务器”架构,在这种架构中,设备会充当下面的其中一个角色:
- 服务器:选择性地与网络上的其他设备分享资源
- 客户端:连接到服务器上使用服务器分享的资源
Socket API通常使用下面两种核心协议:
- TCP(Transmission Control Protocol)
- UDP(User Datagram Protocol)
Darwin 是开源的遵循POSIX标准的操作系统,构成了Mac OS X和iOS依赖的核心组件,也就是说,OS X和iOS都包含了BSD Socket Libra1ry。
注意:
使用BSD Socket Libra1ry的代码几乎全部都可以在iOS平台上工作,在标准UNIX平台和iOS平台使用BSD Socket Libra1ry最大的区别就是iOS平台不支持多进程,所以在iOS平台上你应该使用多线程。
1.查看设备的字节顺序
设备的字节顺序取决于设备使用的微处理器架构,下面我们使用代码来查看设备的字节顺序:
示例代码:
ViewController.h
文件代码:
#import <UIKit/UIKit.h>
typedef NS_ENUM(NSUInteger, EndianType) {
ENDIAN_UNKNOWN,
ENDIAN_LITTLE,
ENDIAN_BIG
};
@interface ViewController : UIViewController
@end
ViewController.m
文件代码:
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"%lu",(unsigned long)[self byteOrder]);
}
-( EndianType)byteOrder {
union {
short sNum;
char cNum[sizeof(short)];
} un;
un.sNum = 0x0102;
if (sizeof(short) == 2) {
if(un.cNum[0] == 1 && un.cNum[1] == 2)
return ENDIAN_BIG;
else if (un.cNum[0] == 2 && un.cNum[1] == 1)
return ENDIAN_LITTLE;
else
return ENDIAN_UNKNOWN;
} else
return ENDIAN_UNKNOWN;
}
@end
讲解:
首先在头文件中定义了枚举类型,用来表示字节顺序的类型,在byteOrder
方法中,sizeof
的结果等于类型所占的内存字节数,把0x0102
赋值给un联合体中sNum成员,十六进制数0x0102
,一个数字占4bit,所以四个数字占16bit,也就是2byte,2字节,即cNum的长度是2。可以根据cNum的第一个元素、第二个元素存储的内容来判断字节顺序。
2.获取网络地址信息
许多应用程序需要获得所运行在的设备提供接口的网络信息,下面来获取设备上所有活动的网络接口的信息,包括:接口名称(interface name),IP版本号(IP version),IP地址(IP address),网络掩码(netmask),默认网关(default gateway)。
示例代码:
#include <ifaddrs.h>
#include <arpa/inet.h>
-(void)obtainNet{
struct ifaddrs *interfaces = NULL;
int success = getifaddrs(&interfaces);
success = getifaddrs(&interfaces);
struct ifaddrs *temp_addr = interfaces;
for (temp_addr = interfaces; temp_addr != NULL; temp_addr = temp_addr->ifa_next) {
int ipversion;
NSLog(@"................");
if (temp_addr->ifa_addr->sa_family == AF_INET) {
NSLog(@"IPv4");
ipversion = AF_INET;
}else if (temp_addr->ifa_addr->sa_family == AF_INET6){
NSLog(@"IPv6");
ipversion = AF_INET6;
}else{
NSLog(@"Unkown IP version");
ipversion = 0;
}
//
char naddr[INET6_ADDRSTRLEN];
char nmask[INET6_ADDRSTRLEN];
char ngate[INET6_ADDRSTRLEN];
NSLog(@"Name : %@",[NSString stringWithUTF8String:temp_addr->ifa_name]);
inet_ntop(ipversion, &((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr, naddr, INET_ADDRSTRLEN);
NSLog(@"Address: %@",[NSString stringWithUTF8String:naddr]);
if ((struct sockaddr_in6 *)temp_addr->ifa_netmask != NULL) {
inet_ntop(ipversion, &((struct sockaddr_in *)temp_addr->ifa_netmask)->sin_addr, nmask, INET_ADDRSTRLEN);
NSLog(@"Netmask: %@",[NSString stringWithUTF8String:nmask]);
}
if ((struct sockaddr_in6 *)temp_addr->ifa_dstaddr != NULL) {
inet_ntop(ipversion, &((struct sockaddr_in *)temp_addr->ifa_dstaddr)->sin_addr, ngate, INET_ADDRSTRLEN);
NSLog(@"Gateway: %@",[NSString stringWithUTF8String:ngate]);
}
}
freeifaddrs(interfaces);
}
讲解:
我们使用getifaddrs()
函数来获取网络地址信息,这个函数会存储ifaddrs
结构体链表。每个ifaddrs
结构体代表一个物理或虚拟的网络接口。如果成功,getifaddrs()
函数会返回1,否则返回-1。函数定义为int getifaddrs(struct ifaddrs **);
。
获取ifaddrs
链表后,我们需要遍历这个链表来获取每个网络接口的信息。先来看一下ifaddrs
结构体:
struct ifaddrs {
struct ifaddrs *ifa_next;
char *ifa_name;
unsigned int ifa_flags;
struct sockaddr *ifa_addr;
struct sockaddr *ifa_netmask;
struct sockaddr *ifa_dstaddr;
void *ifa_data;
};
接下来就是对每个ifaddrs
结构体进行处理:temp_addr->ifa_addr->sa_family
,其中,从ifaddrs
结构体的定义可以看出temp_addr->ifa_addr
的类型是struct sockaddr *
,这个一个泛型结构体,定义如下:
struct sockaddr {
__uint8_t sa_len; /* total length */
sa_family_t sa_family; /* [XSI] address family */
char sa_data[14]; /* [XSI] addr value (actually larger) */
};
从文档看出,sa_family_t
本质上是__signed char
。而#define AF_INET 2
和#define AF_INET6 30
则对IP版本号进行了宏定义。temp_addr->ifa_name
通过访问结构体的成员来获取接口地址名称,ifa_name
是字符数组,所以将其转化为NSString。
我们定义了三个字符数组来保存网络地址、网络掩码和网关信息。我们将数据的大小设为INET6_ADDRSTRLEN
,因为它比INET_ADDRSTRLEN
要大,官方宏定义如下:#define INET6_ADDRSTRLEN 46
和#define INET_ADDRSTRLEN 16
。
我们会使用const char *inet_ntop(int, const void *, char *, socklen_t);
函数来为naddr、nmask和ngate字符数组来赋值,然后将它们转变为NSString。当然,该函数中要将struct sockaddr
转换为 struct sockaddr_in
。
最后一点,getifaddrs()函数返回的数据是动态分配的,当不再需要的时候应该使用freeifaddrs()
函数释放,防止内存泄露。
3.执行网络地址解析
大多数的应用程序最后都需要将主机/服务(name/service)名称转变为sockaddr结构体,或者将sockaddr结构体转变为主机/服务名称。BSD Socket Libra1ry提供了两个函数来完成转换:
-
Getaddrinfo()
:这个函数会返回关于给定主机/服务名称的信息,返回值为addrinfo
结构体。 -
Getnameinfo()
:返回给定addrinfo
结构体的主机和服务名称。
上面的两个函数是兼容IPv4和IPv6地址的,下面我们来将这两个函数包装成Objective-C类。
示例代码:
AddrInfo.h
文件代码:
#import <Foundation/Foundation.h>
@interface AddrInfo : NSObject
@property(nonatomic,strong)NSString *hostname, *service;
@property(nonatomic)struct addrinfo *results;
@property(nonatomic)struct socketaddr *sa;
@property(nonatomic,readonly)int errorCode;
-(void)addrWithHostname:(NSString *)Hostname Service: (NSString *)Service andHints:(struct addrinfo *)Hints;
-(void)nameWithSockaddr:(struct sockaddr *)saddr;
-(NSString *)errorString;
@end
AddrInfo.m
文件代码:
#import "AddrInfo.h"
#import <netinet/in.h>
#import <netinet6/in6.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
@implementation AddrInfo
-(instancetype)init{
self = [super init];
if (self) {
[self setVare];
}
return self;
}
-(void)setVare{
self.hostname = @"";
self.service = @"";
self.results = (__bridge struct addrinfo *)(@"");
_errorCode = 0;
}
-(void)addrWithHostname:(NSString *)Hostname Service:(NSString *)Service andHints:(struct addrinfo *)Hints{
[self setVare];
self.hostname = Hostname;
self.service = Service;
struct addrinfo *res;
_errorCode = getaddrinfo([_hostname UTF8String],[_service UTF8String],Hints,&res);
self.results = res;
}
-(void)nameWithSockaddr:(struct sockaddr *)saddr{
[self setVare];
char host[1024];
char serv[20];
_errorCode = getnameinfo(saddr, sizeof saddr, host, sizeof host, serv, sizeof serv, 0);
self.hostname = [NSString stringWithUTF8String:host];
self.service = [NSString stringWithUTF8String:serv];
}
-(NSString *)errorString{
return [NSString stringWithCString:gai_strerror(_errorCode) encoding:NSASCIIStringEncoding];
}
-(void)setResults:(struct addrinfo *)results{
_results = results;
}
下面我们在控制器中使用封装的类来解析:
ViewController.h
文件代码
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@end
ViewController.m
文件代码
#import "ViewController.h"
#include <ifaddrs.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#import "AddrInfo.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//[self obtainNet];
[self convertEachOther];
}
-(void)convertEachOther{
struct addrinfo *res;
struct addrinfo hints;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
AddrInfo *ai = [[AddrInfo alloc] init];
[ai addrWithHostname:@"www.packtpub.com" Service:@"443" andHints:&hints];
if (ai.errorCode != 0) {
NSLog(@"Error in getaddrinfo(): %@",[ai errorString]);
return ;
}
struct addrinfo *results = ai.results;
for (res = results; res != NULL; res = res->ai_next) {
void *addr;
NSString *ipver =@"";
char ipstr[INET6_ADDRSTRLEN];
if (res->ai_family == AF_INET) {
struct sockaddr_in * ipv4 = (struct sockaddr_in *)res->ai_addr;
addr = &(ipv4->sin_addr);
ipver = @"IPv4";
}else if (res->ai_family == AF_INET6){
struct sockaddr_in6 * ipv6 = (struct sockaddr_in6 *)res->ai_addr;
addr = &(ipv6->sin6_addr);
ipver = @"IPv6";
}else{
continue;
}
inet_ntop(res->ai_family, addr, ipstr, sizeof ipstr);
NSLog(@" %@ %s",ipver,ipstr);
AddrInfo *ai2 = [[AddrInfo alloc] init];
[ai2 nameWithSockaddr:res->ai_addr];
if (ai2.errorCode == 0) {
NSLog(@"-----%@ %@",ai2.hostname,ai2.service);
}
freeaddrinfo(results);
}
}