Address模块概述

提供网络地址相关的类,支持与网络地址相关的操作,一共有以下几个类:

Address:所有网络地址的基类,抽象类,对应sockaddr类型,但只包含抽象方法,不包含具体的成员。除此外,Address作为地址类还提供了网络地址查询及网卡地址查询功能。

IPAddress:IP地址的基类,抽象类,在Address基础上,增加了IP地址相关的端口以及子网掩码、广播地址、网段地址操作,同样是只包含抽象方法,不包含具体的成员。

IPv4Address:IPv4地址类,实体类,表示一个IPv4地址,对应sockaddr_in类型,包含一个sockaddr_in成员,可以操作该成员的网络地址和端口,以及获取子码掩码等操作。

IPv6Address:IPv6地址类,实体类,与IPv4Address类似,表示一个IPv6地址,对应sockaddr_in6类型,包含一个sockaddr_in6成员。

UnixAddreess:Unix域套接字类,对应sockaddr_un类型,同上。

UnknownAddress:表示一个未知类型的套接字地址,实体类,对应sockaddr类型,这个类型与Address类型的区别是它包含一个sockaddr成员,并且是一个实体类。

整个网络地址模块的类继承关系图如下:

关于套接字地址

Linux使用Berkeley套接字接口进行网络编程,这套接口是事实上的标准网络套接字编程接口,在基本所有的系统上都支持。Berkeley套接字接口提供了一系列用于网络编程的通用API,通过这些API可以实现跨主机之间网络通信,或是在本机上通过Unix域套接字进行进程间通信。

几乎所有的(Berkeley)套接字接口都需要传入一个地址参数(比如在connect或send时指定对端的地址),用于表示网络中的一台主机的通信地址。不同的协议类型对应的地址类型不一样,比如IPv4协议对应IPv4地址,长度是32位,而IPv6协议对应IPv6地址,长度是128位,又比如Unix域套接字地址是一个路径字符串。

如果针对每种类型的地址都制定一套对应的API接口,那么最终的套接字API接口数量规模一定会非常宠大,这对开发和维护都没好处。我们希望的是只使用一套通用的API接口就能实现各种地址类型的操作,比如针对IPv4地址的代码,能够在不修改或尽量少修改的前提下,就可用于IPv6地址。对此,Berkeley套接字接口拟定了一个通用套接字地址结构sockaddr,用于表示任意类型的地址,所有的套接字API在传入地址参数时都只需要传入sockaddr类型,以保证接口的通用性。除通用地址结构sockaddr外,还有一系列表示具体的网络地址的结构,这些具体的网络地址结构用于用户赋值,但在使用时,都要转化成sockaddr的形式。

sockaddr表示通用套接字地址结构,其定义如下:

struct sockaddr
{
    unsigned short sa_family; // 地址族,也就是地址类型
    char sa_data[14];         // 地址内容
};

所有的套接字API都是以指针形式接收sockaddr参数,并且额外需要一个地址长度参数,这可以保证当sockaddr本身不足以容纳一个具体的地址时,可以通过指针取到全部的内容。比如上面的地址内容占14字节,这并不足以容纳一个128位16字节的IPv6地址。但当以指针形式传入时,完全可以通过指针取到适合IPv6的长度。

除sockaddr外,套接字接口还定义了一系列具体的网络地址结构,比如sockaddr_in表示IPv4地址,sockaddr_in6表示IPv6地址,sockaddr_un表示Unix域套接字地址,它们的定义如下:

struct sockaddr_in 
{
    unsigned short sin_family; // 地址族,IPv4的地址族为AF_INET
    unsigned short sin_port;   // 端口
    struct in_addr sin_addr;   // IP地址,IPv4的地址用一个32位整数来表示
    char sin_zero[8];          // 填充位,填零即可
};

struct sockaddr_in6
{
    unsigned short sin6_family; // 地址族,IPv6的地址族为AF_INET6
    in_port_t sin6_port;	    // 端口
    uint32_t sin6_flowinfo;	    // IPv6流控信息
    struct in6_addr sin6_addr;	// IPv6地址,实际为一个128位的结构体
    uint32_t sin6_scope_id;	    // IPv6 scope-id
};

struct sockaddr_un
{
    unsigned short sun_family;  // 地址族,Unix域套字地址族为AF_UNIX
    char sun_path[108];         // 路径字符串
};

通过上面的定义也可以发现,除sockaddr_in可以无缝转换为sockaddr外,sockaddr_in6和sockaddr_un都不能转换为sockaddr,因为大小不一样。但这并不影响套接字接口的通用性,因为在使用时,所有类型的地址都会转换成sockaddr指针形式,又由于以上所有的地址结构的前两个字节都表示地址族,所以通过sockaddr指针总能拿到传入地址的地址类型,通过地址类型判断出地址长度后,再通过sockaddr指针取适合该地址的长度即可拿到地址内容。


实际使用时,一般都是先定义具体的网络地址结构并初始化,然后在传入套接字接口时,通过指针强制转化成sockaddr类型,以下分别是使用IPv4地址和IPv6地址发起bind的代码:

IPv4
int sockfd;
sockaddr_in addr;

sockfd = socket(AF_INET, SOCK_STREAM, 0);

memset(&addr, 0, sizeof(sockaddr_in));
addr.sa_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(80);

bind(sockfd, (sockaddr*)&addr, sizeof(addr)); // bind支持所有的地址类型
                                              // 这里要将IPv4地址结构强制转化成sockaddr*类型,并传入地址长度
IPv6
int sockfd;
sockaddr_in6 addr;

sockfd = socket(AF_INET6, SOCK_STREAM, 0);

memset(&addr, 0, sizeof(sockaddr_in6));
addr.sa_family = AF_INET6;
addr.sin6_addr.s_addr = in6addr_any;
addr.sin6_port = htons(80);

bind(sockfd, (sockaddr*)&addr, sizeof(addr)); // 这里将IPv6地址也转化成sockaddr*再传入bind
                                              // 虽然sockaddr结构体并不足以容纳一个IPv6地址,但是传入的是指针和地址长度,
                                              // bind内部在判断出当前地址是IPv6地址的情况下,
                                              // 仍然可以通过指针和长度取到一个完整的IPv6地址

通过这两段代码也可以看出,虽然IPv4和IPv6的地址结构不一样,但它们在进行bind操作时的形式是一样的,Berkeley套接字接口正是通过这种形式保证了接口在大部分情况下都可以通用。

Address类

这个类是所有网络地址类的基类,并且是一个抽象类,对应的是sockaddr,表示通用网络地址。对于一个通用的网络地址,需要关注它的地址类型,sockaddr指针,地址长度这几项内容。

除此外,Address类还提供了地址解析与本机网卡地址查询的功能,地址解析功能可以实现域名解析,网卡地址查询可以获取本机指定网卡的IP地址。

IPAddress类

继承自Address类,表示一个IP地址,同样是一个抽象类,因为IP地址包含IPv4地址和IPv6地址。IPAddress类提供了IP地址相关的端口和掩码、网段地址、网络地址操作,无论是IPv4还是IPv6都支持这些操作,但这些方法都是抽象方法,需要由继承类来实现。

IPv4Address类

继承自IPAddress类,表示一个IPv4地址,到这一步,IPv4Address就是一个实体类了,它包含一个sockaddr_in类型的成员,并且提供具体的端口设置/获取,掩码、网段、网络地址设置/获取操作。

IPv6Address类

继承自IPAddress类,表示一个IPv6地址,也是一个实体类,实现思路和IPv4Address一致。

UnixAddress类

继承自Address类,表示一个Unix域套接字地址,是一个实体类,可以用于实例化对象。UnixAddress类包含一个sockaddr_un对象以及一个路径字符串长度。

UnknownAddress类

继承自Address类,包含一个sockaddr成员,表示未知的地址类型。











  • 无标签