地址族与数据序列
目录
- 分配给套接字的IP地址和端口号
- 地址信息的表示
- 网络字节序与地址变换
- 网络地址的初始化与分配
- 基于Windows的实现
1 分配给套接字的IP地址和端口号
1.1 网络地址
1.1.1 两类IP地址
IPV4:4字节地址族(目前通用)
IPV6:16字节地址族(为应对2010年前后IP地址耗尽的问题而提出的标准)
IPV4地址族(E类已被预约,一般不会使用)
1.1.2 基于IP地址的数据传输过程
- 向构成网络的路由器(交换机)传递数据
- 由路由器(交换机)根据数据中的主机地址,向目标主机传递数据
1.2 网络地址分类与主机地址边界
只需通过IP地址的第一个字节即可判断网络地址占用的字节数
A类地址的首字节范围:0-127(首位以0开始)
B类地址的首字节范围:128-191(前两位以10开始)
C类地址的首字节范围:192-223(前三位以110开始)
1.3 用于区分套接字的端口号
数据传输目标地址同时包含IP地址(确定目标计算机)和端口号(确定目标应用程序)
- 端口号作用:同一操作系统内,区分不同套接字
- 端口号由16位组成,可分配的端口号范围是0-65535(一般不含0-1023)
- 同一类别(TCP、UDP)套接字的端口号不能重复
2 地址信息的表示
2.1 表示IPV4地址的结构体
struct sockaddr_in
{
sa_family_t sin_family; //地址族(不只为IPV4设计)
uint16_t sin_port; //16位TCP/UDP端口号
struct in_addr sin_addr; //32位IP地址
char sin_zero[8]; //不使用
};
struct in_addr
{
in_addr_t s_addr; //32位IPv4地址
};
2.2 结构体sockaddr_in的成员分析
成员sin_family(地址族)
成员sin_port(16位TCP/UDP端口号)
以网络字节序保存
成员sin_addr(32位IP地址)
以网络字节序保存
成员sin_zero
为使结构体sockaddr_in的大小与sockaddr结构体保持一致,必须填充为0
3 网络字节序与地址变换
3.1 字节序与网络字节序
3.1.1 大端序和小端序
大端序:高位字节存放到低位地址
小端序:高位字节存放到高位地址
3.1.2 主机字节序和网络字节序
主机字节序:代表CPU数据保存方式,在不同CPU中也各不相同
目前主流的Intel系列CPU以小端序方式保存数据
网络字节序:通过网络传输数据时约定统一方式,统一为大端序
3.1.3 字节序转换
帮助转换字节序的函数:
unsigned short htons(unsigned short);//把short型数据从主机字节序转化为网络字节序
unsigned short ntohs(unsigned short);
unsigned long htonl(unsigned long);
unsigned long ntohl(unsigned long);
unsigned short host_port = 0x1234;
unsigned short net_port;
unsigned long host_addr = 0x12345678;
unsigned long net_addr;
net_port = htons(host_port); //0x3412
net_addr = htonl(host_addr); //0x78563412
4 网络地址的初始化与分配
4.1 将字符串信息转换为网络字节序的整数型
4.1.1 inet_addr
#include <arpa/inet.h>
//成功时返回32位大端序整数型值,失败时返回INADDR_NONE
in_addr_t inet_addr(const char* string);
char* addr1 = "1.2.3.4";
char* addr2 = "1.2.3.256";
unsigned long conv_addr = inet_addr(addr1); //0x4030201
unsigned long conv_error_addr = inet_addr(addr2); //INADDR_NONE
4.1.2 inet_aton
#include <arpa/inet.h>
//string: 含有需转换的IP地址信息的字符串地址值
//addr: 将保存转换结果的in_addr结构体变量的地址值
//成功时返回1(true),失败时返回0(false)
int inet_aton(const char* string, struct in_addr* addr);
char* addr = "127.232.124.79";
struct sockaddr_in addr_inet;
if(inet_aton(addr, &addr_inet.sin_addr))
{
printf("%#x\n", addr_inet.sin_addr.s_addr); //0x4f7ce87f
}
4.1.3 inet_ntoa
#include <arpa/inet.h>
//调用完该函数后,应立即将字符串信息复制到其他内存空间
//再次调用该函数,会覆盖之前保存的字符串信息
//成功时返回转换的字符串地址值,失败时返回-1
char* init_ntoa(struct in_addr adr);
struct sockaddr_in addr1, addr2;
char *str_ptr;
char str_arr[20];
addr1.sin_addr.s_addr = htonl(0x1020304);
addr2.sin_addr.s_addr = htonl(0x1010101);
str_ptr = inet_ntoa(addr1.sin_addr);
strcpy(str_arr, str_ptr);
printf("%s\n", str_ptr); //1.2.3.4
inet_ntoa(addr2.sin_addr);
printf("%s\n", str_ptr); //1.1.1.1
printf("%s\n", str_arr); //1.2.3.4
4.2 网络地址初始化
struct sockaddr_in addr;
//声明IP地址字符串
char* serv_ip = "221.217.168.13";
//声明端口号字符串
char* serv_port = "9190";
//结构体变量addr的所有成员初始化为0(将sockaddr_in结构体的成员sin_zero初始化为0)
memset(&addr, 0, sizeof(addr));
//指定地址族
addr.sin_family = AF_INET;
//基于字符串的IP地址初始化
addr.sin_addr.s_addr = inet_addr(serv_ip);
//基于字符串的端口号初始化
addr.sin_port = htons(atoi(serv_port));
4.3 客户端地址信息初始化
与服务器端的区别
- 调用函数不同:准备工作时,服务器端通过bind函数完成,客户端通过connect函数完成
- 调用函数前需准备的地址值类型不同:服务器端声明sockaddr_in结构体变量,将其初始化为赋予服务器端IP和套接字的端口号;客户端声明sockaddr_in结构体,并初始化为要与之连接的服务器端套接字的IP和端口号,然后调用connect函数
4.4 INADDR_ANY
struct sockaddr_in addr;
//声明端口号字符串
char* serv_port = "9190";
//结构体变量addr的所有成员初始化为0(将sockaddr_in结构体的成员sin_zero初始化为0)
memset(&addr, 0, sizeof(addr));
//指定地址族
addr.sin_family = AF_INET;
//利用常数INADDR_ANY分配服务器端的IP地址。利用这种方式可自动获取运行服务器端的计算机IP地址,不必亲自输入
addr.sin_addr.s_addr = htonl(INADDR_ANY);
//基于字符串的端口号初始化
addr.sin_port = htons(atoi(serv_port));
4.5 向套接字分配网络地址
#include<sys/socket.h>
//bind函数负责将初始化的地址信息分配给套接字
//sockfd:要分配地址信息(IP地址和端口号)的套接字文件描述符
//myaddr:存有地址信息的结构体变量地址值
//addlen:第二个结构体变量的长度
int bind(int sockfd, struct sockaddr* myaddr, socklen_t addrlen);
//常见套接字初始化过程
int serv_sock;
struct sockaddr_in serv_addr;
char* serv_port = "9190";
//创建服务器端套接字(监听套接字)
serv_sock = socket(PF_INET, SOCK_STREAM, 0);
//地址信息初始化
memset(&serv_addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(atoi(serv_port));
//分配地址信息
bind(serv_sock, (struct sockaddr* )&serv_addr, sizeof(serv_addr));
......
5 基于Windows的实现
WSAStringToAddress&WSAAddressToString
- Winsock2增加的两个转换函数,功能上与inet_itoa和inet_addr完全相同
- 优点:支持多种协议,在IPv4和IPv6中均可适用
- 缺点:依赖于特定平台,降低兼容性