TCP/IP网络编程_第3章地址族与数据序列 3.1-3.3

TCP/IP网络编程_第3章地址族与数据序列

第2章中讨论了套接字的创建方法, 如果把套接字比喻为电话, 那么目前只安装了电话机. 本章将着重讲解给电话机分配号码的方法. 即给套接字分配 IP 地址和端口号. 这部分内容也相对有些枯燥, 但并不难, 而且是学习后续那些有趣内容必备的基础知识.

3.1 分配给套机字的 IP 地址 与 端口号

IP 是Internet Protocol (网络协议) 的简写, 是为收发网络数据而分配给计算机的值. 端口号并非赋予计算机的值, 而是为区分程序中创建的套机字而分配给套接字的序号. 下面逐一讲解.

网络地址(Internet Addrsee)
为了使计算机连接到网络并发数据, 必需向其分配 IP 地址. IP 地址分为两类.
在这里插入图片描述
IPv4 与 IPv6 的差别主要是代表 IP 地址所用的字节数, 目前通常的地址 IPv4. IPv6 是为了应对2010 年前后 IP 地址消耗尽的问题而提取的标准, 即便如此, 现在主要还是使用 IPV4 , IPV6 的普遍即将需要更长时间.

IPV4 标准的4字节IP 地址分为 网络地址和主机(指计算机)地址, 且分为A, B, C, D, E等类型. 图3-1展示了 IPV4 地址族, 一般不会使用已被预约的 E 地址, 故省略.
在这里插入图片描述
网络地址(网络ID)是区分网络而设置的一部分 IP 地址. 假设向 WWW.SEMI.COM 公司传输数据, 该公司内部构建了局域网, 把所有计算机连接起来. 因此, 首先应向 SEMI.COM 网络传输数据, 也就是说, 并非一开始就浏览所有 4字节IP 地址, 进而找到目标主机; 而是仅浏览4字节IP 地址的网络地址, 先吧数据传到 SEMI.COM 的网络. SEMI.COM的网络(构成网络的路由器)接收到数据后, 浏览器传输数据主机地址(主机 ID ) 并将数据传给目标计算机. 图 3-2展示数据传输过程.
在这里插入图片描述
某主机向203.211.172.103 和 203.211.217.202 传输数据, 其中203.211.172和203.211.217 为该网络的网络地址(稍后给出网络地址区分方法). 所以, “向相应网络传输数据” 实际是是向构成网络的路由器(Router)或交换机(Switch) 传输数据, 由接收数据的路由器根据数据中的主机地址向目标主机传递数据.
在这里插入图片描述
若想构建网络, 需要一种物理设备完成外围与本网主机之间的数据交换, 这种设备便是路由器或交换机. 他们实际上也是一种计算机, 只不过是为了特殊目的而设计运行的, 因此有了别名. 所以, 如果在我们使用计算机上安装适当的软件, 也可以将其用作交换机. 另外, 交换机比路由器功能要简单一些, 而实际用途差别不大.

网络地址分类与主机地址分界

只需通过 IP 地址的第一个字节即可判断网络地址占用的字节数, 因为我们根据 IP 地址的边界区分网络地址, 如下所示.
在这里插入图片描述
还有如下这种表述方式.
在这里插入图片描述
正因如此, 通过套接字接收发数据时, 数据传到网络后即可轻松找到正确的的主机.

用于区分套接字的端口号

IP 用于区分计算机, 只要有 IP 地址就能向目标主机传输数据, 但仅凭这些无法传输给最终的应用程序. 假设各位欣赏视频的同时在网上冲浪, 这是至少需要1个接收视频数据的套接字和**1个接收网页信息的套接字.**问题在于如何区分两者. 简言之, 传输到计算机的网络数据是发给播放器, 还是浏览器? 让我们更精确地描述问题. 假设各位开发了如下应用程序:

“我开发了收发数据的 P2P 有了一定了解, 即便不清楚也无所谓. 如上所述, 若想接收多台计算机发来的数据, 则需要相应个数的套机字. 那么如何区分这些套接字呢?”

计算机中一般配有 NIC (Network Interface Card (网络接口卡) 数据传输设备. 通过 NIC 向计算机内部传输数据时会用到 IP. 操作系统负责把传输到内部的数据适当分配套接字, 这时就要利用端口号. 也就是说, 通过 NIC 接收的数据内部有端口号, 操作系统正是参考此端口号把数据传输给相应的套接字, 如图3-3 所示.
在这里插入图片描述
端口号就是在同一个操作系统内区分不同套接字而设置的, 因此无法将1个端口号分配给不同套接字. 另外, 端口号由16 位构成的, 可分配的端口号范围0-65535, 但 0-1023 是知名端口号( Well-known PORT), 一般分配给特定应用程序, 所以应当分配次范围之内的值. 另外, 虽然端口号不能重复, 但 TCP 套接字 和 UDP 套机字不会共用端口号, 所以允许重复. 例如: 如果某 TCP 套接字使用 9190 号端口, 则其他 TCP 套接字就无法使用该端口号, 但 UDP 套接字可以使用.

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

3.2 地址信息的表示

应用程序中使用的 IP 地址 和 端口号以结构体的形式给出了定义. 本节将以 IPV4 为中心, 围绕此结构体讨论目标地址的表示方法.

表示 IPV4 地址的结构体
填写地址信息时应以如下提问为线索进行, 各位读过下列对话和也会同意这一点.
在这里插入图片描述
在这里插入图片描述
结构体定义为如下形态就能回答上述提问, 此结构体将作为地址信息传递给bind 函数.
在这里插入图片描述
给结构体中提到另一个结构体 in_addr 定义如下, 它用来存放32 位 IP 地址.
在这里插入图片描述
讲解以上2个结构体前先观察一些数据类型, uint16_t , in_addr_t 等类型可以参考 POSIX (Portable Operating system Interface , 可移植操作系统接口). POSIX 是为 UNIX 系列操作系统设立的标准, 它定义了一些其他数据类型, 如表 3-1 所示.
在这里插入图片描述
从这些数据类型声明也可掌握之前结构体的含义. 那为什需要额外定义这些数据类型呢?如前所述, 这是考虑到扩展性的结果. 如果使用 int32_t 类型的数据, 就能保住在任何时候都占用4字节, 即使将来用64 位表示 int 类型也是如此.

结构体 sockaddr_in 的成员分析

接下来重点观察结构体成员的含义及其包含的信息.

成员 sin_family
每种协议适用的地址族均不同. 比如, IPV4 使用4字节地址族, IPV6 使用16字节地址族.可以参考表 3-2 保存 sin_family 地址信息.
在这里插入图片描述
AF_LOCAL 只是为了说明具有多种地址族而添加, 希望各位不要感到太突然.

成员sin_port
该成员保存16位端口号, 重电在于, 它以网络字节序保存(关于这一点稍后将给出详细说明).

成员sin_addr
该成员保存32 位 IP 地址信息, 且也以网络字节序保存. 为理解好该成员, 应同时观察结构体 in_addr . 但结构体 in_addr 声明为 uint32_t , 因此只需作为32 位整数即可.

成员sin_zero
无特殊含义. 只是为使结构体 sockaddr_in 的大小与 sockaddr 结构体保持一致而插入的成员. 必须填充为0, 否则无法得到想要的结果. 后面会另外讲解 sockaddr.

从以前介绍的代码也可看出, sockaddr_in 结构体变量地址将以如下方式传递给 bing 函数.稍后将给出关于 bind 函数的详细说明, 希望各位重点关注参数传递和类型转换部分的代码.
在这里插入图片描述
此处重要的是第二个参数的传递. 实际上, bind 函数的第二个参数期望得到 sockaddr 结构体变量地址值, 包含地址族, 端口号, IP 地址等. 从下列代码也可看出, 直接向sockaddr 结构体填充这些信息会带来麻烦.
在这里插入图片描述
此结构体成员 sa_data 保存的地址信息中需要包含 IP 地址和端口号, 剩余部分因填充0, 这也是bind 函数要求的. 而这对于包含地址信息来讲非常麻烦, 继续而就有了新的结构体 sockaddr_in 若按照之前的讲解填写 sockadr_in 结构体, 则将生成符合bind 函数要求的字节流. 最后转换为 sockaddr 型的结构体变量, 再传递给bind 函数即可.

在这里插入图片描述
socket_in 是保存 IPV4 地址信息的结构体. 那为何还需要通过sin_family 单独指定地址信息呢? 这与之前之前讲过的 sockaddr 结构体有关. 结构体sockaddr 并非只为 IPV 4设计, 这从保存地址信息的数组 sa_data长度为 14 字节也可以看出. 因此, 结构体sockaddr 要求在sin_family 中指定地址族信息. 为了与 sockaddr 保持一致, sockaddr_in 结构体中也有地址信息.

3.3 网络字节序与地址变换

不同 CPU 中, 4 字节整数型值1 在内存空间的保存方式是不同的. 4 字节整数型 值 1 可用2进制表示 如下.
在这里插入图片描述
有些 CPU 以这种顺序保存都内存, 另外一些 CPU 则以 倒序保存.
在这里插入图片描述
若不考虑这些就收发数据则会发生问题, 因为保存顺序的不同意味着对接收数据的解析顺序也不同.

字节序(Order)与网络字节序

CPU 向保存数据的方式有2种, 这意味着CPU 解析数据的方式也分为2种.
在这里插入图片描述
仅凭描述很难解析清楚, 下面通过实例进行说明. 假设在 0x20 号开始的地址中保存4字节int 类型数 0x12345678. 大端序 CPU 保存方式如图 3-4 所示.
在这里插入图片描述
整数0x12345678 中, 0x12是最高位字节, 0x78是最低字节. 因此, 大端序中先保存最高位字节0x12 (最高字节0x12存放到低位地址). 小端序保存方式如图3-5所示.
在这里插入图片描述
先保存的是最低位字节0x78. 从以上分析可以看出, 每种CPU 的数据保存方式均不同. 因此, 代表 CPU 数据保存方式的主机字节序(Host Byte Order) 在不同 CPU 中也各不相同. 目前主流的 Intel 系列 CPU 以小端序方式保存数据. 接下来分析2台字节序不同的计算机之间进行数据传递中可能出现的问题, 如图 3-6 所示.
在这里插入图片描述
0x12和0x32 构成的大段序系统值与0x34和0x12 构成的小端序系统值相同, 换言之, 只有改变数据保存顺序才能被识别为同一值. 图 3-6 中, 大端序系统传输数据 0x123时未考虑字节序问题, 而直接以 0x12 、 0x34 的顺序发送. 结果接收端以小端方式保存数据. 因此小端序接收的数据变成 0x3412, 而非0x1234. 正因如此, 在通过网络传输数据时约定统一方式, 这种约定称为网络字节序(Network Byte Order), 非常简单–统一为大端序.

即, 先把数据数组转换成大端序格式再进行网络传输. 因此, 所有计算机接收数据时应识别该数据是网络字节序格式, 小端序系统传输数据时应转化为大端序排序方式.

字节序转换(Endian Conversions)

相信大家已经理解了为何要在填充 sockaddr_in 结构体前将数据转换成网络字节序. 接下来介绍帮助转换字节的函数.
在这里插入图片描述
通过函数名应该能掌握其功能, 只需了解以下细节.
在这里插入图片描述
另外, s指的是 short, 1指的是long (Linux 中 long 类型占用4字节, 这很关键). 因此, htons 是h、to 、n 、s 的组合, 也可以解释为 “把short 型数据从主机字节序转化为网络字节序”.

再举个例子, ntohs 可以解析为 “把short 型数据从网络字节序转化为主机字节序”.

通常, 以s作为后缀的函数中, s代表2个字节short, 因此用于端口号转换; 以 l 作为后缀的函数中, l 代表4字节, 因此用于 IP 地址转换. 另外, 有些读者有如下疑问:

“我的操作系统是大端序的, 为sockaddr_in 结构体变量赋值前就不需要转换字节序了吧?”

这么说也不能算错. 但我认为, 有必要编写与大端序无关的统一代码. 这样, 即使在大端序系统中, 最好也经过主机字节序转换为网络字节序的过程. 当然, 此时主机字节序与网络字节序相同, 不会有任何变化. 下面通过实例说明以上函数的调用过程.

#include <stdio.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
    /* 各保存2个字节, 4个字节的数据. 当然, 若运行程序的CPU 不同, 则保存的字节序各序也不同 */
    unsigned short host_port = 0x1234;
    unsigned short net_port = net_port;
    unsigned long host_addr = 0x12345678;
    unsigned long net_addr;

    /* 变量host_port和host_addr中的数据转化为网络字节序, 若运行环境为小端序CPU, 则按改变之后的字节序保存 */
    net_port = htons(host_port);
    net_addr = htonl(host_addr);

    printf("Host orderd port: %#x \n", host_port);
    printf("Network orderde port: %#x \n", net_port);
    printf("Host ordered addrress: %#lx \n", host_addr);
    printf("Network ordered address: %#lx \n", net_addr);

    return 0;
}

运行结果:
在这里插入图片描述

这就是在小端序 CPU 中运行的结果. 如果在大端序 CPU 中运行, 则变量值不会改变. 大部分朋友都会得到类似的运行结果, 因为 Intel 和 AMD 系列的 CPU 都采用小端序标准.

在这里插入图片描述
也许有读者认为: “既然数据传输采用网络字节序, 那在传输前应直接把数据转换成网络字节序, 接收的数据也需要转换成主机字节序再保存.” 如果数据收发过程中没有自动转换机制, 那当然需要程序员手动转换. 这关想想就让人觉得可怕, 难道真的要强求程序员做这些事情吗? 实际上没有必要, 这个过程是自动的. 除了向 sockaddr_in 结构体变量填充数据外, 其他情况无需考虑字节序问题.

你可以在下面网站下载这本书:
https://www.jiumodiary.com/

时间2020:05:24

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章