地址信息的表示

应用程序中使用的IP地址和端口号以结构体的形式给出了定义。

1.表示 IPv4 地址的结构体

结构体 sockaddr_in 的定义如下所示,结构体 sockaddr_in 将作为地址信息传递给 bind 函数。

struct sockaddr_in
{
    
    
    sa_family_t       sin_family;     // 地址族(Address Family)
    uint16_t          sin_port;       // 16位TCP/UDP端口号
    struct in_addr    sin_addr;       // 32位IP地址
    char              sin_zero[8];    // 不使用
};

结构体 sockaddr_in 中提到的另一个结构体 in_addr 的定义如下所示,它用来存放 32 32 32 位IP地址。

struct in_addr
{
    
    
    in_addr_t    s_addr;    // 32位IPv4地址
};

uint16_t、in_addr_t 等类型可以参考 POSIX(Portable Operating System Interface,可移植操作系统接口),POSIX 是为 UNIX 系列操作系统设立的标准,它定义了一些其他数据类型,如下表所示。

数据类型名称 数据类型说明 声明的头文件
int8_t signed 8-bit int sys/types.h
uint8_t unsigned 8-bit int (unsigned char) sys/types.h
int16_t signed 16-bit int sys/types.h
uint16_t unsigned 16-bit int (unsigned short) sys/types.h
int32_t signed 32-bit int sys/types.h
uint32_t unsigned 32-bit int (unsigned long) sys/types.h
sa_family_t 地址族(address family) sys/socket.h
socklen_t 长度(length of struct) sys/socket.h
in_addr_t IP地址,声明为 uint32_t netinet/in.h
in_port_t 端口号,声明为 uint16_t netinet/in.h

问:为什么要额外定义这些数据类型呢?

答:这是考虑到扩展性的结果。如果使用 int32_t 类型的数据,就能保证在任何时候都占用 4 4 4 字节,即使将来用 64 64 64 位表示 int 类型也是如此。

2.结构体 sockaddr_in 的成员分析

2.1 成员 sin_family

每种协议族适用的地址族均不同,如下表所示。比如,IPv4 使用 4 4 4 字节地址族,IPv6 使用 16 16 16 字节地址族。

地址族(Address Family) 含义
AF_INET IPv4网络协议中使用的地址族
AF_INET6 IPv6网络协议中使用的地址族
AF_LOCAL 本地通信中采用的UNIX协议的地址族

AF_LOCAL 只是为了说明具有多种地址族而添加的,希望各位不要感到太突然。

2.2 成员 sin_port

该成员保存 16 16 16 位端口号,重点在于,它以网络字节序保存。

2.3 成员 sin_addr

该成员保存 32 32 32 位IP地址信息,且也以网络字节序保存。为理解好该成员,应同时观察结构体 in_addr。但结构体 in_addr 声明为 uint32_t,因此只需当作 32 32 32 位整数型即可。

2.4 成员 sin_zero

无特殊含义。只是为使结构体 sockaddr_in 的大小与 sockaddr 结构体保持一致而插入的成员。必须填充为 0 0 0,否则无法得到想要的结果。

3.sockaddr_in 与 sockaddr 的区别

sockaddr_in 结构体变量地址值将以如下方式传递给 bind 函数,这里重点关注参数传递和类型转换部分的代码。

struct sockaddr_in serv_addr;
// ......
if (bind(serv_sock, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) == -1)
{
    
    
    error_handling("bind() error");
}
// ......

此处重要的是第二个参数的传递。实际上,bind 函数的第二个参数期望得到 sockaddr 结构体变量地址值,包括地址族、端口号、IP地址等。然而,从下面代码可以看出,直接向 sockaddr 结构体填充这些信息会带来麻烦。

struct sockaddr
{
    
    
    sa_family_t    sin_family;    // 地址族(Address Family)
    char           sa_data[14];   // 地址信息
};

结构体 sockaddr 的成员 sa_data 保存的地址信息中需包含IP地址和端口号,剩余部分应填充 0 0 0,这也是 bind 函数要求的。而这对于包含地址信息来讲非常麻烦,进而就有了新的结构体 sockaddr_in。若按照前面的讲解填写 sockaddr_in 结构体,则将生成符合 bind 函数要求的字节流。最后转换为 sockaddr 型的结构体变量,再传递给 bind 函数即可。

问:sockaddr_in 是保存 IPv4 地址信息的结构体,那为何还需要通过 sin_family 单独指定地址族信息呢?

答:这与 sockaddr 结构体有关。结构体 sockaddr 并非只为 IPv4 设计,这从保存地址信息的数组 sa_data 长度为 14 14 14 字节也可看出。因此,结构体 sockaddr 要求在 sin_family 中指定地址族信息。为了与 sockaddr 保持一致,sockaddr_in 结构体中也有地址族信息。

猜你喜欢

转载自blog.csdn.net/qq_42815188/article/details/129504536