最近在看CSAPP的网络编程小节,盯着getaddrinfo这个api看了两天,总算是琢磨出了为何要引入这样复杂的一个api。
其依赖头文件如下,涉及的3个接口如下
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
int getaddrinfo(const char *host, const char *service,
const struct addrinfo *hints,
struct addrinfo **result);
void freeaddrinfo(struct addrinfo *result);
const char *gai_strerror(int errcode);
就我个人理解,引入getaddrinfo有以下三点好处:
1. 写网络相关代码时不需要将ipv4或者ipv6相关的预定义常数指明,通过查询填入。
2. 不用操心字节序问题。
首先说第一点,如果写代码时使用 AF_INET这样指明ipv4协议的预定义常数的话,相应代码只能用于ipv4。同理,如果使用了AF_INET6,那么代码只能用于ipv6.
为了使代码既能用于ipv4又能用于ipv6的话,当需要指明下图中结构体socketaddr_in里的协议族时sin_family,可以避免硬编码为AF_INET或AF_INET6,而是采用getaddrinfo去查询,然后填入,查询结果是ipv4就填AF_INET,查询结果是ipv6就填AF_INET6。因为没有深入底层源码,查询具体如何实施的不太清楚,初步推测可能是通过访问当前网段的DNS server来获取相应的信息。
/*IP socket address structure*/
struct sockaddr_in{
uint16_t sin_family;
uint16_6 sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
};
再说第二点,在上图中,sin_port 和 sin_addr 都是需要按照大端法在网络中传输的。
简单说下大小端法的区分,设整数 v 占 4个字节 为0x12345678, v的地址&v指 栈或堆或.data段中(取决于我们声明的方式)的最低地址,如下图
virtual memory:
0x40000F:
0x40000E:
0x40000D:
0x40000C:
如果变量v放在这4个字节里,则其地址 &v 为0x40000C,如果0x40000C里存的是最低有效位0x78则称其为小端法,这也是大多数机器的做法,如果0x40000C里存的是最高有效位0x12则是大端法。
假设port 为 80,80的16比特表示为 00000000 01010000,其16进制表示为 0x00 0x50
在存储时,声明一个结构体 struct sockaddr_in addr_example; 我们想将addr_example的端口号设为80。
如果我们的机器是大端法,那直接通过以下语句即可,
addr_example.sin_port = 80
但如果我们的机器是小端法,则需要通过以下4句代码来将 结构体里的2字节端口号设为指定的80;
char *temp = (char *)(addr_example.sin_port)
*temp = 0x00;
temp++;
*temp = 0x50;
这是非常麻烦的,通过查询getaddrinfo并赋值的方式,不管我们的机器是大端法还是小端法,它都将相应的sin_port设置为了大端法表示下的80,即通过getaddrinfo查询并复制后,如下检测为真。
* ((char *)(addr_example.sin_port)) == 0x00 &&
* ((char *)(add_example.sin_port) + 1) == 0x50;
最后就是为啥 声明一个 struct sockaddr *result 而传了&result,
如果api 里声明成 struct sockaddr *result的话,我们想要修改这个结构体就需要先声明并且定义一个 struct sockaddr result,然后把其地址传进去。
但是如果我们想要在接口内修改一个 struct sockaddr * 类型,那我们就必须传一个 struct sockaddr ** 类型,