文章目录
应用程序中使用的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
结构体中也有地址族信息。