说在前面
- 环境: WSL
- 参考: UNIX网络编程
数据类型说明
数据类型 | 说明 | 头文件 |
int8_t | 带符号8位整数 | <sys/types.h> |
uint8_t | 无符号8位整数 | <sys/types.h> |
int16_t | 带符号16位整数 | <sys/types.h> |
uint16_t | 无符号16位整数 | <sys/types.h> |
int32_t | 带符号32位整数 | <sys/types.h> |
uint32_t | 无符号32位整数 | <sys/types.h> |
sa_family_t | 套接字地址结构的地址族 | <sys/socket.h> |
socklen_t | 套接字地址结构的长度,一般为uint32_t | <sys/socket.h> |
in_addr_t | IPv4地址,一般为uint32_t | <netinet/in.h> |
in_port_t | TCP或UDP端口,一般为uint16_t | <netinet/in.h> |
套接字地址结构
-
IPv4套接字地址结构
通常也被称为"网际套接字地址结构",以sockaddr_in命名。
- 头文件
#include <netinet/in.h>
- 定义
/* struct in_addr { in_addr_t s_addr; // 32位,IPv4地址,网络序 }; */ struct sockaddr_in { uint8_t sin_len; // 8位,结构体长度(16字节) sa_family_t sin_family; // 8位,AF_INET in_port sin_port; // 16位,TCP或者UDP端口号,网络序 struct in_addr sin_addr; // 32位,IPv4地址,网络序 char sin_zero[8];// unused };
- sin_len字段
unsigned short类型;为增加对OSI协议的支持而增加的,不是所有厂家都支持该字段,POSIX规范也未要求。该字段涉及了一些高级内容,暂不讨论。 - POSIX规范只要求了sin_addr、sin_family以及sin_port字段。几乎所有的实现都增加了sin_zero字段(干啥用的?)。
- in_addr_t数据类型必须是至少32位的无符号整型,in_port_t必须是至少16位的无符号整型,sa_family_t可以是任何无符号整型类型(在支持长度字段sin_len的实现中,sa_family_t通常为8位无符号整型;在不支持长度字段的实现中,通常为16位无符号整型)
- 注意结构体in_addr与数据类型in_addr_t的区分。
- 头文件
-
通用套接字地址结构
- 头文件
#include <sys/socket.h>
- 定义
struct sockaddr { uint_8 sa_len; sa_family_t sa_family; /* 支持的地址族, AF_xxx */ char sa_data[14]; /* 14字节的协议数据 */ };
- 在作为参数传递给各种套接字函数时,套接字地址结构总是以指针的形式来传递。但是,套接字函数必须处理来自所支持的任何协议族的套接字地址结构。(比如bind函数必须支持IPv4和IPv6,但是它们的套接字结构不相同,如果我们直接传递各自的指针是需要重载bind函数的)
- 为了解决这个问题,定义了通用的套接字地址结构,用于不同协议套接字结构指针之间的类型强制转换。
- 由于不同实现中,地址结构长度字段可能没有,那么必须保证在该实现中所有协议的套接字地址结构都没有该字段;(sin_len、sa_len等)要么都有,要么都没有。
- 疑问: 这个结构体的构造其实不太懂。为啥要填充成16字节?
补充: 查找了bind等系统调用的代码,发现在其实现中,sockaddr类型的指针还是会通过sa_family转换为对应协议套接字结构体的指针。这样的话,即使协议套接字结构体长度大于十六,还是可以访问正确的数据。
- 头文件
-
IPv6套接字地址结构
- 头文件
#include <netinet/in.h>
- 定义
/* struct in6_addr { uint8_t s6_addr[16]; // 128位,IPv6地址,网络序 }; */ #define SIN6_LEN struct sockaddr_in6 { uint8_t sin6_len; // 8位,结构体长度(16字节) sa_family_t sin6_family; // 8位,AF_INET6 in_port sin6_port; // 16位,传输层端口号,网络序 uint32_t sin6_flowinfo; // 流信息,未定义 struct in6_addr sin6_addr; // 128位,IPv6地址,网络序 uin32_t sin6_scope_id; // 标识范围ID };
- 如果系统支持套接字地址结构中的长度(sin6_len)字段,那么SIN6_LEN必须定义。
- 结构中的字段先后顺序做过编排,使得如果sockaddr_in6结构本身是64位对齐的,那么128位的sin6_addr也是64位对齐的(如下图)。
在一些64位处理机上,如果64位数据存储在某个64位边缘位置,那么对其访问将得到优化。例如访问sin6_addr字段时,若是按上述结构存储,仅需取两次;而如果不是对齐的,需要取三次。(如下图)
- sin6_flowinfo字段分成两部分:低序20位为流标,高序12位保留。该部分暂不讨论。
- 对于具有范围的地址(scoped address),sin6_scope_id字段标识其范围。最常见的是链路局部地址(link-local address),由于一个网络节点(如主机)有多个网络接口(如网卡),但是网络接口的链路局部地址是相同的,而scope_id可以用以区分它们。
- 头文件
-
新的通用套接字地址结构
- 头文件
#include <netinet/in.h>
- 定义
struct sockaddr_storage{ uint8_t ss_len; // 8位,结构体长度 ss_family_t ss_family; // 8位,支持的地址族, AF_xxx /* 其他依赖于实现的字段,需要满足以下要求 * 1)必须满足最苛刻的对齐要求 * 2)必须足够大,可以容纳任何系统支持的套接字地址结构 */ };
- ubuntu18.04中的实现
可以看到在该实现中sockaddr_storage结构大小为128字节(这样就可以满足对齐?)// in (\usr\include\x86_64-linux-gnu\bits\sockaddr.h) /* Size of struct sockaddr_storage. */ #define _SS_SIZE 128 // in (\usr\include\x86_64-linux-gnu\bits\socket.h) /* Structure large enough to hold any socket address (with the historical exception of AF_UNIX). */ #define __ss_aligntype unsigned long int #define _SS_PADSIZE \ (_SS_SIZE - __SOCKADDR_COMMON_SIZE - sizeof (__ss_aligntype)) struct sockaddr_storage { __SOCKADDR_COMMON (ss_); /* Address family, etc. */ char __ss_padding[_SS_PADSIZE]; __ss_aligntype __ss_align; /* Force desired alignment. */ };
- 除了ss_family和ss_len外,其他字段对用户来说是透明的(无法正确的访问其中的数据)。sockaddr_storage结构只有类型强制转换/复制为适合ss_family字段的套接字地址结构中才可以访问。
- 头文件
-
套接字地址结构比较
对于可变长度的套接字地址结构,在作为参数传递时,需要同时传递结构的长度。
sockaddr_un结构本身不是变长的,但是路径名是可变的。