Skip to content

3 地址族与数据序列

IP 地址与端口号

网络地址

为了区分网络而设置的一部分 IP 地址,在寻找目标主机时,先访问网络地址,然后路由器接到数据以后,再浏览主机地址并把数据传给计算机

网络地址分类和主机的地址边界

只看首字节即可区分网络地址占用的字节数

  • A 类地址首字节范围:0~127,网络地址占一个字节
  • B 类:128~191,网络地址占两个字节
  • C 类:192~223,网络地址占三个字节

等价于

  • A 类地址首位以 0 开始
  • B 类地址首位以 10 开始
  • C 类地址首位以 110 开始

端口号

计算机中有网卡(NIC, Network Interface Card),通过 NIC 向计算机内传输数据用到 IP,操作系统负责将数据适当分配给套接字,这时候要利用端口号(i.e. 通过 NIC 接受的数据内有端口号),操作系统参考这个端口号把数据传输给相应端口的套接字

端口号由 16 位构成,范围 0~65535,但是 0~1023 是知名端口号(Well-known PORT)一般分配给特定的应用程序,所以应该分配这个范围以外的值。另外,虽然端口号不允许重复,但 TCP 和 UDP 套接字不会共用端口号,所以允许重复。例如某个 TCP 套接字已经使用的端口号,另外的 TCP 套接字无法使用,但是 UDP 套接字可以

总之,目标地址同时包含 IP 地址和端口号,只有这样,数据才能被传输到最终的应用程序

表示地址信息

表示 IPv4 的结构体

struct sockaddr_in
{
    sa_family_t sin_family; // 地址族
    uint16_t sin_port; // 16位 UCP/TCP 端口号
    struct in_addr sin_addr; // 32位 IP 地址
    char sin_zero[8]; // 不使用
};

struct in_addr
{
    In_addr_t s_addr; // 32 位 IPv4 地址
};

sockaddr_in 成员

sin_family

每种协议使用的地址族不同,IPv4 使用 4 字节地址族,IPv6 使用 16 字节地址族

地址族 含义
AF_INET IPv4 网络协议使用的地址族
AF_INET6 IPv6 网络协议使用的地址族
AF_LOCAL 本地通信中采用的 UNIX 协议地址族

sin_port

该成员保存 16 位端口号,以网络字节序保存

sin_addr

32 位 IP 地址信息,也以网络字节序保存

sin_zero

是为了使 sockaddr_in 的大小和 sockaddr 保持一致而插入的成员,必须填充为 0,因为作为 bind 参数的时候,&serv_addr 需要强转成 struct sockaddr *,但是直接填充 sockaddr 结构体很麻烦,其结构如下:

struct sockaddr
{
    sa_family_t sin_family;
    char        sa_data[14]; // 地址信息
};

bind 函数要求 sa_data 除了地址和端口号以外的信息全部填 0,继而就有了 sockaddr_in

网络字节序和地址变换

字节序与网络字节序

大端序:高位字节存放到低位地址,在一个数字中,左侧是高位,右侧是低位,但是对于地址而言,数字越小越是低位,所以一个数字 0x12345678 以大端序保存起来就是 0x12 0x34 0x56 0x78

小端序:高位字节存放到高位地址,举例 0x12345678 保存起来就是 0x78 0x56 0x34 0x12

每种 CPU 的数据保存形式不同,因此主机字节序(代表 CPU 保存方式)不同,目前 Intel 主要以小端序保存

但是字节序不同的计算机之间数据传输难以解析,因此在网络传输中统一约定方式,这种约定成为网络字节序(Network Byte Order),统一为大端序,就是说先把数据数组转化为大端序格式再传输

字节序转换

unsigned short htons(unsigned short);
unsigned short ntohs(unsigned short);
unsigned long htonl(unsigned long);
unsigned long ntohl(unsigned long);

htonsh 代表主机字节序,n 代表网络字节序,sshortllong(在 Linux 中占4个字节)

除了向 sockaddr_in 结构体变量填充数据外,其他情况无需考虑字节序问题

网络地址初始化和分配

字符串转网络地址序整型

sockaddr_in 保存地址信息的成员为 32 位整型,因此为了分配 IP 地址,需要表示为 32 位整型

#include <arpa/inet.h>

in_addr_t inet_addr(const char * string);
// 传入字符串,成功返回 32 位大端序,失败返回 INADDR_NONE

还有一个功能完全相同的函数,区别在于直接设置 in_addr 结构体

#include <arpa/inet.h>

int inet_aton(const char* string, struct in_addr * addr);
// 成功返回 1,失败返回 0

还有功能相反的函数

#include <arpa/inet.h>
char * inet_ntoa(struct in_addr adr);
// 成功返回字符串值,失败返回 -1

把网络字节序 IP 地址值转化为字符串形式,调用该函数后应立即将字符串信息复制到其他内存空间,因为先前的空间为函数分配,可能被覆盖

网络地址初始化

对先前介绍函数的应用

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));