第三章、地址族与数据序列
3.1、分配给套接字的IP地址与端口号
IP地址分为IPv4 和IPv6,书中主要讲解IPv4,但其实截至到目前IPv4已经全部耗尽,开始使用IPv6了。
网络地址分类:
IPv4一共分为ABCDE5类,但E类一般不使用。可以通过IP第一个字节判断网络地址占用的字节数。
A类地址 (0 - 127) 0 开始
B类地址 (128 - 191) 10 开始
C类地址 (192 - 223) 110开始
用于区分套接字的端口号:
端口号就是同一操作系统内为区分不同套接字而设置的,因此无法将一个端口号分配给不同的套接字。端口号由16位构成,可分配的端口号范围是0-65535,但0-1023是知名端口号,一般给特定程序。由于TCP和UDP不共用端口号,所以两个协议可以同时使用一个端口号。(比如都使用9000这个端口号)
目标地址 = IP地址 + 端口号
3.2、地址信息的表示
IPv4结构体主要参数:
struct sockaddr_in
{
sa_family_t sin_family; //地址族
uint16_t sin_port; //16位端口号
struct in_addr sin_addr; //32位IP
char sin_zero[8]; //不使用
};
结构体 sockaddr_in的成员分析:
成员sin_family:每种协议族适用的地址族均不同。
AF_INET IPv4网络协议使用的地址族
AF_INET6 IPv6网络协议使用的地址族
AF_LOCAL 本地通信中采用的UNIX协议的地址族
成员sin_port:保存16位端口号,以网络字节序保存。
成员sin_addr:保存32位IP地址信息,也以网络字节序保存。
成员sin_zero:无特殊含义,必要时需要填充0.比如Bind处(bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)))
struct sockaddr{
sa_family_t sin_family; //地址族
char sa_data[14]; //地址信息
}
此结构体成员sa_data保存的地址信息中需要包含IP地址和端口号,剩余部分应填充0.
3.3、网络字节序和地址转换
字节序与网络字节序
CPU向内存保存数据的方式有2种,这意味着CPU解析数据的方式也分为两种;1、高位字节放低位地址(大端序);2、高位字节放高位地址(小端序)。在网络传输数据时约定统一网络字节序为大端序。
字节序转换:
unsigned/signed short/long htons/ntohs(unsigned short/long);
htons中h代表主机字节序,n代表网络字节序 s表示short l表示long htons 表示 h to n s 把short的主机字节序转化为网络字节序。
s作为后缀的函数中,s代表两个字节short,用于端口转换;以l作为后缀的函数中,l代表4个字节,用于IP地址转换。
3.4、网络地址的初始化和分配
sockaddr_in中保存地址信息为32位整数型,为了分配地址,需要将字符串转化为32位整数型数据。
int_addr_t inet_addr(const char * string);//成功返回32位大端序整数型值,失败返回INADDR_NONE。
inet_aton函数与inet_addr函数在功能上完全相同,也将字符串形式IP地址转化为32位网络字节序整数并返回。
int inet_aton(const char *string, struct in_addr *addr);//成功返回1,失败返回0.
//string:需要转换为IP的字符串,addr将保存转换结果的in_addr结构体变量的地址值
char * inet_ntoa(struct in_addr adr);//成功返回转换的字符串地址值,失败返回-1.
网络初始化
eg:
struct sockaddr_in addr;
char * serv_ip = "211.217.168.13";
char * serv_port = "9190";
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(serv_ip);
addr.sin_port = htons(atoi(serv_port)); //基于字符串的端口号初始化。
使用 INADDR_ANY,只需要修改一行代码就行了,利用常数INADDR_ANY分配服务器IP地址。
addr.sin_addr.s_addr = htonl(INADDR_ANY);