iOS网络底层之BSD Socket Libra1ry

时间:2022-12-15 10:46:28

注:

  • 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);

    }

}