互联网结构与网络通信协议

Soul Lv2

这一篇我们从互联网的结构开始梳理一下网络世界的通信到底是怎样实现的。

网络模型

我们首先从 OSI 模型讲起,在这个模型中互联网的结构被分成了七层:

  • 应用层(Application Layer)

    • 功能:直接与最终用户交互,为应用程序提供网络服务。
    • 常见协议:HTTP, FTP, SMTP, SNMP, DNS, Telnet。

    我们常见的各种应用程序,都是工作在应用层

  • 表示层(Presentation Layer)

    • 功能:数据格式化,转换,和数据加密,确保数据能被应用层正确读取和理解。
    • 常见功能:数据压缩、加密与解密、数据格式转换。
  • 会话层(Session Layer)

    • 功能:负责建立、管理和终止会话(通信连接)。
    • 常见功能:会话管理、全双工、半双工、和单工操作模式、会话检查点和恢复功能。
  • 传输层(Transport Layer)

    • 功能:提供端到端的通信控制,确保数据可靠传输。
    • 常见协议:TCP, UDP。
    • 常见功能:流量控制、错误检测与纠正、数据重传。

    传输层也是我们需要重要学习的内容

  • 网络层(Network Layer)

    • 功能:负责路由和转发数据包,处理逻辑地址(如IP地址)。
    • 常见协议:IP, ICMP, ARP, RARP。
    • 常见设备:路由器。
  • 数据链路层(Data Link Layer)

    • 功能:提供节点到节点间的传输,处理物理寻址(MAC地址)、错误检测和纠正。
    • 常见协议:Ethernet, PPP, HDLC。
    • 常见设备:交换机,网桥。
  • 物理层(Physical Layer)

    • 功能:定义了物理设备的电气和物理规范,如线缆类型,连接器,频率等。
    • 常见标准:RJ45, Ethernet,光纤标准。

一般情况下,对于一次网络通信,信息的流向一般是从上到下的,先由应用层产生对应的数据,然后不断向下包装, 最终经过物理层被传输到其他的计算机中。在接收数据的一侧,又从物理层开始依次向下解包,最终获得想要的数据。
这一套模型是公认的国际标准,但是不知道你是否认为这一套模型过于复杂,我们在一般情况下并不会真的去考虑这么多,在大多数情况下,我们采用一种更简单的模型:TCP/IP 模型,其结构如下

链路层(Link Layer)

  • 作用

    • 数据封装与解封 :将网络层的 IP 数据报封装成帧,帧是链路层传输的数据单位,它在帧的首部和尾部添加了用于在链路上传输的控制信息。例如,以太网帧的首部包含目的 MAC 地址、源 MAC 地址、类型字段等信息,尾部则有帧校验序列(FCS),用于接收方检测帧在传输过程中是否出现错误。

    • 物理地址寻址 :负责在网络中的设备之间进行物理地址(MAC 地址)的识别和寻址,确保数据帧能够正确地从一个设备传输到另一个设备。MAC 地址是固化在设备的网卡上的 48 位地址,每个网卡都有一个唯一的 MAC 地址,链路层通过 MAC 地址来确定数据传输的源和目的端 。

    • 链路管理 :负责建立、维护和管理链路的连接,包括链路的建立过程中的初始化、参数协商等操作,以及在链路出现故障时的检测和恢复处理。例如,在以太网中,链路层协议会检测物理链路的连接状态,如是否插好网线、链路是否正常工作等。

    • 错误检测与纠正 :链路层提供了一定的错误检测和纠正机制,通过帧校验序列(FCS)等技术,对接收到的帧进行错误检测,如果发现帧有错误,可以要求发送方重新发送该帧,从而保证在链路上传输的数据的正确性。

网络层(Internet Layer)

  • 作用

    • IP 寻址与路由选择 :为网络中的设备提供统一的逻辑地址(IP 地址),使得不同网络中的设备能够相互识别和定位。IP 地址是由一组数字组成的独特标识符,用于标识网络中的每一个设备。网络层的核心功能是根据 IP 地址进行路由选择,即确定数据从发送方到接收方的最优传输路径。路由器是网络层的关键设备,它根据路由表中的信息,将数据包转发到下一个合适的路由器或目标网络。

    • 数据包的分片与重组 :由于不同网络的链路层帧的最大传输单元(MTU)不同,网络层负责将较大的 IP 数据报分割成较小的片段,以便在 MTU 较小的链路上进行传输。当数据报到达目的地后,再由接收方的网络层将这些片段重新组装成原始的数据报。

    • 无连接的数据传输 :网络层提供的是无连接的服务,即在数据传输之前不需要建立专门的连接,每个数据包独立地进行传输和路由选择。这意味着每个数据包都可能通过不同的路径到达目的地但,这也增加了网络的灵活性和容错性,因为如果某条路径出现故障,数据包可以自动选择其他可用的路径进行传输。

传输层(Transport Layer)

  • 作用

    • 端到端的通信 :建立在进程到进程之间进行数据传输的通道,为应用程序提供端到端的通信服务。进程是操作系统中程序执行的一个实例,传输层通过端口号来标识不同的进程,使得不同的应用程序可以同时在网络上进行通信而互不干扰。

    • 连接管理与可靠传输(TCP) :TCP 协议在传输层提供了面向连接的、可靠的传输服务。在数据传输之前,需要建立一个 TCP 连接,通过三次握手的过程来建立可靠的通信通道。在连接建立后,TCP 采用确认、重传、排序等机制,确保数据的可靠传输。接收方收到数据后,会发送确认信息给发送方,如果发送方在一定时间内未收到确认信息,则会重发相应数据。同时,TCP 还可以对收到的乱序数据进行排序,保证数据按正确的顺序交付给上层应用程序。

    • 无连接的快速传输(UDP) :UDP 协议提供的是面向无连接的、不可靠的传输服务。UDP 不需要在传输前建立连接,数据直接以数据报的形式发送,因此它的传输延迟相对较低,适用于对实时性要求较高但对数据丢失不太敏感的应用场景,如音频、视频流媒体等。但由于 UDP 不提供可靠传输机制,数据可能会出现丢失、重复或乱序的情况。

应用层(Application Layer)

  • 作用

    • 应用程序接口(API) :为各种网络应用程序提供了编程接口和协议支持,使得应用程序能够方便地使用网络服务。例如,HTTP 协议为网页浏览应用提供了访问和传输网页内容的接口;FTP 协议为文件传输应用提供了上传、下载文件的功能;DNS 协议为域名解析应用提供了将域名转换为 IP 地址的服务等。

    • 网络服务提供 :应用层包含了各种常见的网络服务协议,如文件传输、电子邮件、远程登录、域名解析、网络新闻等,为用户提供了丰富的网络应用服务。这些协议规定了应用程序之间进行通信时的格式、规则和过程,使得不同用户的应用程序能够在网络上相互协作和交互,共同完成各种网络应用任务。

接下来我们就分别讲讲各层相关的一些比较重要的事情

链路层

在链路层,我们从 MAC 地址或者说物理地址讲起?我们的设备能够实现网络通信是通过我们设备的网卡实现的,而每个网卡再出厂时都会被分配一个全球唯一的 MAC 地址,一般被表示为一个 12 位 16 进制数,例如下面这张就是我当前连接网络的信息

MAC 地址通常是局域网范围内不同设备的唯一标识。局域网中通信一般实现是这样的:诸多的设备共同连接在同一台交换机上,交换机会将自己的不同端口与不同的 MAC 地址相互绑定,而设备上也存储了不同设备的 MAC 地址,如果想要和某设备通信就告诉交换机自己想要和某个 MAC 地址对应的端口通信,交换机会将信息传递给对应的端口。

在局域网特别是比较小的,受信任的局域网内这么搞肯定是没问题的,但如果在大规模的网络内指望通过 MAC 地址实现全网的通信,就会存在不小的问题:

  1. MAC 地址是取决于网卡的,也就是说我完全可以随便编一个假的 MAC 地址来掩盖我的身份或者更进一步我可以把自己包装为别人从而监听他人的信息。
  2. MAC 地址取决于网卡,而一块网卡可能被卖到全球的任何地点,而且这块网卡的位置是随时可能会发生改变的,也就是说必须存在一台设备在任何时刻都知道某块网卡位于哪里,才能将信息点对点的发送过去
    为了解决这个问题,人们搞出了 IP 地址这一网络层的地址

网络层

相较于链路层,网络层开始脱离实际存在的物理设备,而是更加关注网络拓扑环境。根据网络拓扑环境为不同的设备分配不同的 IP 地址。当前的 IP 协议可分为 IPv4 以及 IPv6,我们先来看看 IPv4 地址的规则
IPv4 地址的规则主要体现在以下几个方面:

地址格式

  • 点分十进制表示法 :IPv4 地址采用 32 位二进制数表示,为了便于人类阅读和书写,通常将其分为 4 个 8位(1 字节)的段,每段转换为十进制数,段与段之间用点(.)分隔开,这就是所谓的点分十进制表示法。例如,一个典型的 IPv4 地址可能是 192.168.1.1,其中每个十进制数的取值范围为 0 到 255。

地址分类

  • A 类地址

    • 范围 :1.0.0.0 - 126.255.255.255

    • 特点 :最高为位 0,网络号占 1 个字节(8 位),主机号占 3 个字节(24 位),一个 A 类地址可容纳的主机数量最多,理论上有 2^24-2≈16777214 个主机(减去网络地址和广播地址),但 A 类地址的网络数量相对较少,适用于大型网络。

  • B 类地址

    • 范围 :128.0.0.0 - 191.255.255.255

    • 特点 :前两位为 10,网络号占 2 个字节(16 位),主机号占 2 个字节(16 位),一个 B 类地址可容纳的主机数量为 2^16-2=65534 个,适用于中型规模的网络。

  • C 类地址

    • 范围 :192.0.0.0 - 223.255.255.255

    • 特点 :前三位为 110,网络号占 3个 字节(24 位),主机号占 1 个字节(8 位),一个 C 类地址可容纳的主机数量为 2^8-2=254 个,适合小型网络。

  • D 类地址

    • 范围 :224.0.0.0 - 239.255.255.255

    • 特点 :前四位为 1110,用于多播(组播)通信,即一个数据包可以同时发送给多个特定的接收者 。

  • E 类地址

    • 范围 :240.0.0.0 - 255.255.255.255

    • 特点 :前五位为 11110,目前保留用于实验和特殊用途。

特殊地址

  • 网络地址 :每个网络的起始地址,网络号部分固定,主机号部分全为 0,用于标识一个网络,不能分配给主机使用。

  • 广播地址 :每个网络的结束地址,网络号部分固定,主机号部分全为 1,用于向该网络中的所有主机发送广播信息。

  • 回环地址 :127.0.0.0 - 127.255.255.255,通常用于环回测试,当主机需要向自身发送数据时会使用这个地址,最常用的回环地址是 127.0.0.1。

  • 私有地址 :也称为专用地址,用于私有网络内部通信,这些地址不会在互联网上被分配给任何组织或个人,包括:

    • A 类私有地址 :10.0.0.0 - 10.255.255.255

    • B 类私有地址 :172.16.0.0 - 172.31.255.255

    • C 类私有地址 :192.168.0.0 - 192.168.255.255

我们常说的公网 IP 一般指的就是 IPv4 地址,因为一个 32 位二进制数的最大表示范围大概是 43 亿,或许在创建这套规则的时代这是一个比较大的数字,但是现在我们每个人需要联网的设备有多少,这完全不够用,该怎么解决这个问题呢?如今的办法就是按照上面所说的分配一部分私有地址,这部分地址在局域网内部被分配,局域网中存在一个路由器,这个路由器占据实际的公网地址,在访问互联网时局域网内所有的设备对外来说都在使用路由器的 IP,由路由器将不同的信息转发给不同的设备。(当然,国内现实的情况是一般一个小区只有一个公网 IP,我们家里的路由器也只使用被分配的私网 IP,所有信息由更上层的路由器转发),比如我当前电脑的 IP 就是 192.168 开头,说明是一个私有地址

然后再讲讲这个 ABCDE 类别划分是什么鬼,对于 A 类地址,在分配时会只规定前 8 位,剩余 24 位不做分配,然后将整个网段交给某个组织使用,而 B 类地址会规定前 16 位,依次类推。我们一般情况下获得的公网IP 地址其实隶属于移动联通电信的 A 类网段

再来看看这张图

当我们连接路由器时一般情况下会通过 DHCP 协议自动分配 IP 地址,完成相应的配置,但同样也支持手动进行配置,我们发现我们需要手动配置 DNS,地址,子网掩码,网关,跃点。这些东西都是什么呢?

先讲讲 DNS 服务,一般情况下我们不会直接通过 IP 地址访问某个网页,而是通过某个网址,那么我们又如何知道哪个网址对应那个 IP 呢,这就需要 DNS 服务了,DNS 服务器内保存了网址与 IP 地址的对应关系,在向某个网址发送请求时会先向 DNS 服务器发送服务器获得网址对应的 IP 地址,常用的 DNS 服务包括移动联通电信提供的 DNS 服务以及几个知名企业提供的 DNS 服务,比如 Public DNS+的 119.29.29.29,114 DNS 的 114.114.114.114,阿里的 233.5.5.5,当然还有不少,需要的可以自己上网上找找

接下来是地址,也就是想要给当前设备分配的地址,只要不和别的设备冲突,192.168 后面随便写

然后是子网掩码,子网掩码其实就是用来分辨网络类型的,比如说 A 类网络的默认子网掩码为 255.0.0.0,转换为二进制就是 11111111 00000000 00000000 00000000,将子网掩码与实际的 IP 地址进行按位与运算可以得到对应的网络号,例如将上面的掩码与 8.8.8.8 进行按位与运算或者说与 00001000 00001000 00001000 00001000 进行按位与运算得到的是 00001000 00000000 00000000 00000000 即 8.0.0.0,说明当前 IP 属于 8.0.0.0 这个网段,进一步的将子网掩码先按位取反再与 IP 地址进行按位与操作得到的就是当前 IP 在网段内的编号。一般情况下,A 类网络的子网掩码为 255.0.0.0,B 类位 255.255.0.0,C 类为 255.255.255.0 但这不是固定的,有些时候我们希望将一个 B 类网划分为多个 C 类网,那么久选择 255.255.255.0 作为子网掩码,或者要将网络按照特定的数量划分,我们也可以按照上面的逻辑编写更复杂的子网掩码。此外,有些时候子网掩码与 IP 地址会被合并在一起书写为 192.168.5.6/24 的形式

接着是网关,也就是我们的路由器的 IP,一般是当前网段的 1 号。

最后是跃点,这个值一般情况下不用设置,在数据包的传输过程中每经过一个有路由功能的设备就称之为经过了一个跃点,如果我们设置了跃点,那么所有的数据会被强制先经过设置的跃点。

那么了解了这些之后,请回答我的一个问题,既然有了 IP,那么 MAC 地址还有存在的必要性吗?答案是当然有,在当今的局域网中,分配私有 IP 的过程正是依赖与 MAC 地址,借助 MAC 地址来实现的,交换机或者路由器必须通过 MAC 地址来识别不同的设备。

此外,在网络层,我们还应当了解端口的概念,考虑一个问题:我们的设备中有这么多应用在同时运行,这些应用都需要网络连接,那么操作系统在收到一段信息后该如何确定到底这个信息该传递给哪个应用?这就需要使用到端口号了,端口号通常由 16 位二进制数组成,实际范围为 0-65535,所有的信心在发送时都会被表明向对应 IP 的哪个端口发送,不同的应用占据不同的端口,操作系统只需要将收到的信息传递给与对应端口绑定的应用即可。一般情况下,有些端口被固定的服务占用,不推荐使用,比如 HTTP 占用 80 端口,HTTPS 占用 443 端口,ssh 服务占用 22 端口。如果我们想使用,除了创建一些约定成俗的服务,比如 MySQL 走 3306 端口,Tomcat 占 8080 端口,一般情况下都选择一些过万的端口,不怎么会被占用

在了解了一些基本的网络概念后我们来到传输层

传输层

传输层最为重要的就是两个协议,TCP 协议与 UDP 协议,这两个协议支撑起了当今整个互联网的主要通信。我们今天会尽可能详细的讲一讲这两个协议。先从比较简单的 UDP 协议开始吧。

UDP 协议

UDP 全称 User Datagram Protocol 用户数据报协议,一种典型的无连接协议,当下通常被用于直播,视频电话等对实时性要求比较高且可以接受一定程度的数据损失的方式。这种协议在放弃了一定的安全性的基础上实现了低成本高速率的网络通信方式,其报文的结构大概长这样

也就是说一个标准的 UDP 报文包括 8 字节的头部和一个不定长的有效载荷。通常情况下头部包含下面这些内容

  • 源端口(16 位)

    • 这是用来标识发送进程的端口号。端口号的作用是区分不同的应用程序进程。例如,一个计算机上可能同时运行着多个使用 UDP 协议的程序(如一个下载软件和一个即时通讯工具),源端口就可以区分数据是从哪个进程发出的。当接收方收到数据后,可以根据源端口将响应数据返回给正确的进程。
  • 目的端口(16 位)

    • 该字段用于标识接收进程的端口号。发送方需要知道将数据发送到接收方的哪个应用程序进程,目的端口就起到了这个作用。例如,在网络中的一个服务器上运行着多个服务(如 Web 服务和 FTP 服务),目的口端可以确定数据报应该交给哪个服务来处理。
  • 长度(16 位)

    • 表示 UDP 报文的长度,包括首部和数据部分的总长度。其最小值为 8 字节(当数据部分为空时,只有 UDP 首部)。这个字段可以让接收方知道整个 UDP 报文的大小,从而正确地读取和处理报文。例如,如果某个 UDP 报文的首部长度为 8 字节,数据部分长度为 1000 字节,那么长度字段的值就是 1008(8 + 1000)。
  • 校验和(16 位)

    • 用于对 UDP 报文的完整性进行校验。发送方会根据一定的算法对 UDP 报文(包括首部和数据部分)进行计算,得到一个验校和值,然后将其放入校验和字段中。接收方收到报文后,再按照相同的算法计算校验和,如果计算结果和报文中的校验和值不一致,则表明报文在传输过程中可能出现了错误。UDP 中的校验和算法相当简单粗暴,就是将所有数据的值相加得到一个数字,哪怕溢出也无所谓,因为接收方在计算时也会溢出,最终得到相同结果,但这种算法存在多个数据发生改变或换位导致数据本身改变但校验和不收影响的缺陷

我猜你可能会问一个问题:这里面并没有明确的说明到底该给哪个 IP 地址传,那么 IP 地址是如何被确定的呢?IP 地址被存放在由 IP 协议确定的 IP 数据报中,是由网络层确定的内容。

现在打包好了数据报,该怎么发送信息呢,直接发!我管你这的那的,反正我直接闭着眼把数据向对应地址发出去就完事了,不管实际有没有收到,有没有出错,极致的不确定换来极致的高效。

当然,在当下的实现中,如果选择使用 UDP 协议,那么一般会对 UDP 协议进行魔改,在有效载荷中继续添加一些用于校验的机制,并实现在一方收到信息时返回收到信息。尽可能在保留 UDP 协议高效性的同时保证数据的准确性

TCP 协议

接下来讲讲 TCP 协议,TCP 协议即Transmission Control Protocol,传输控制协议,相较于 UDP 协议,TCP 协议添加了大量的措施来保证数据传输的完整性与准确性。我们的 Socket 体系就是基于 TCP 构建的

我们先来看看 TCP 协议的头部内容

与 UDP 协议相比,TCP 协议的头部就复杂了许多,我们来解释一下

  • 序列号:表示所发送数据载荷的第一个字节的编号,例如总计要发送 300 字节的内容,考虑到网络情况,将内容分 0 到 99,100 到 199,200 到 299 三部分发送,那么三次的序列号分别为 0,100,200.
  • 确认号:表示期望接收到的数据的编号起始,例如接收方收到了 0 到 99 的数据,那么确认号就是 100
  • 首部长度:由于总体上 TCP 头部长度可变,所以保留四位用于表示首部数据的长度
  • 保留:保留数据位,用于将来可能得功能
  • 标志位(6 位):
    - URG(紧急指针):此位置为 1 表示当前包中有紧急数据,优先处理此包中的数据
    - ACK(确认):当 ACK 为 1 是确认号才有效,用于告知发送方已经正确的接受数据
    - PSH(推送):此位置为 1 时表示应当尽快将包中数据交给应用层,而不用等待缓冲区满后再传递
    - RST(复位):此位置为 1 表示将连接复位,当出现严重错误时添加此标识可强制关闭连接
    - SYN(同步):用于建立连接,在建立连接阶段 SYN 位会被设置为 1
    - FIN(结束):用于结束连接,在关闭连接阶段 FIN 位会被设置为 1
  • 窗口大小:用于接收方向发送发告知本方最大的缓冲大小,避免发送方发送过量数据造成接收方溢出或不能正确接受数据
  • 校验和:通过特定算法得出的特定数字串,用于校验数据是否发生了损坏
  • 紧急指针:只有当标志位的 URG 为 1 时才有效,表示数据中紧急数据接收的位置
  • 选项字段:这部分不定长,由通信双方约定,内部可以放置一些约定的内容

怎么样,TCP 的头部是不是复杂了很多,但这还远远不够,我们上面提到 UDP 发送数据时直接向目标 IP 发送,不管对方是否实际接受到了数据,是一种无连接的通信协议。而 TCP 协议是一种面向连接的协议,我们首先来看看建立连接的过程

TCP 建立连接的流程被称为三次握手,具体机制图如下:

  1. 客户端向服务器发送一个 TCP 包要求开启连接,此时 SYN 位被设置为 1,序列号为一个一个随机数 A
  2. 服务器向客户端发送一个 TCP 包表示接收到连接请求,此包中 SYN 与 ACK 位被设置为 1 ,确认码被设置为 A+1,同时服务端产生一个随机数 B 作为序列号
  3. 客户端向服务器发送一个 TCP 包表示接收到连接确认,此包中 ACK 位被设置为 1,序列号为 A+1,确认号位 B+1

考虑一下为什么要这样设计,如果只执行第一步就开始发送数据,无法确定服务器能否收到客户端数据,如果执行前两步,服务器无法确定客户端能否正确接收服务器的数据。三步全部完成且标志位正确,序列号与确认号符合预期说明当前连接相对可靠,可以进行通信

接下来是数据发送的过程,大概如下:

建立连接后双方中的任何一方都可以发送数据与接受数据,只需要恰当的变动己方的序列号与确认号就可以保证数据的有序。为了简化情况,我们上图中以只有一方发送数据为例:

第一次通信发送方发送了 1460 字节数据,序列号为 1,在收到信息后接收方回返回一个数据包,确认号为 1461,表示前面的 1460 字节数据已收到,要求可能存在的 1461 号字节

第二次发送方又发送了 1460 字节的数据,但注意此时出于某种意外接收方没有返回正确的序列号为 1 确认号为 2921 的包

第三次发送方发送的数据出于某种意外没有传递给接收方,发送方不知道这件事,但继续按照计划发送数据

第四次发送了从 4381 到 5840 的数据,但直到此时接收方仍然没有回信

第五次发送了从 5841 到结束的数据,在完成此次发送后接收方终于回信表示收到了第二次发送的 1461 到 2920 的数据,就像图中那样,即使接收方收到了第五段数据,但第二段到第四段直接不连续,此时回信中只会表示自己收到了前两段,要求发送第三段

已发送方的视角看,从 2921 开始的 1460 字节的数据接收方始终没有收到,在等待一定的时间后发送方会重发对应的数据包,在上图的最后,接收方表示收到了截止 7300 的数据,发送方也得到信息,对方完成了所有数据的接受,不再重复发送数据

上图中还有一种情况没有展示:接收方正确接受到了全部数据,但表示第三段数据正确接受的包中途丢了,表示第五段数据正确接受的包还没有到达,此时发送方会再发送一次第三段数据,当接受方收到后,接收方可以通过序列号判断出这事重复的包,然后将其丢弃

当然,数据的重传是有一个对应的次数限制的,在超过一定的次数后仍然没有回话就会发送 RST 包强行终止连接

还有一点需要指出:我上面的图展示的过程中总是一次发送一次接受一一对应,实际的情况是会连续的发送几个 ACK 包,每个包对应一个计时器,如果在限定时间范围内还没有得到对应的回应就进行重传。

当一个连接中一方没有数据发送需求且连续一定时间(一般是两个小时)后没有收到对方信息时,会触发保活机制,会向对方发送包活探测报文,通常是一个空的 ACK 包,如果对方一定时间(一般为 75 秒)没有回应则再次重复,反复一定次数(一般为 8 次)后发送 RST 复位包,强行终端通信

当然,双方也可以选择主动中断连接,这一机制被称为四次挥手

  • 客户端向服务端发送一个 FIN 标志位为 1 的包,表示数据传输完成,但此时客户端还保留着发送与接收数据的能力,对于超时的包仍然会重发,也可以正确的接受服务器的 ACK 包,但从此客户端不会发送新的数据,只会重发超时包
  • 在收到客户端的 FIN 包后,服务器将回应一个 ACK 包,确认号为 FIN 包序列号加一,但这里只表示服务器收到了停止连接的请求,服务器还可能有数据没有发送完
  • 在服务器发送 FIN 包之前,双方的通信仍然是正常的,服务器还可以继续向客户端发送数据,客户端也能正确回应,在服务器完成数据发送后也会发送一个 FIN 包,表示己方数据发送完成,此时服务器的超时重发等能力仍然被保留
  • 客户端收到 FIN 包后返回一个确认号为 FIN 包序列号加一的 ACK 包表示完成了对服务器所有的数据的接受,可以终止连接
  • 接收到客户端的 ACK 包后服务器会立刻终止连接,释放相关资源,而客户端会等待一段时间后才真正释放相关资源停止连接
    为什么客户端需要等待一段时间再停止呢,这是因为如果服务器在回应了 ACK 而没有回应 FIN 期间又发送了一些数据,由于网络原因,这些数据在 FIN 之后抵达,如果连接已经终止,这些数据将被丢失,需要等待一定的时间来保证没有类似的情况发生。此外,还有这样的可能,客户端向服务器发送的最后一个 ACK 丢失了,那么服务器会认为是自己的 FIN 包丢失了,会尝试再次重发 FIN 包,此时如果客户端还没有关闭还可以帮服务器处理这种异常。

到此 TCP 协议的基本机制讲解完成,或许你会认为上面不少情况都考虑的太过极端,但实际上某些情况下出错几率远大于我们的想象,有时网络供应商为了降低负载,是会主动丢包的,所以我们必须尽可能的考虑所有情况。

应用层

讲完了传输层,我们接下来聊一聊关于应用层的东西,应用层我们主要看一看当下使用最广泛的 HTTP 协议。

HTTP 协议,即超文本传输协议,是当今互联网世界使用最为广泛的协议,出于稳定性的考虑,HTTP 协议是基于 TCP 协议实现的。在 HTTP 刚被使用时,每个请求都会创建一个 TCP 连接,这无疑添加了许多不必要的消耗,所以从 HTTP/1.1 开始 HTTP 协议默认采用持久连接,一般在初次访问某个网站时建立连接,在关闭该网站的全部页面时再由浏览器主动将连接关闭。

当我们尝试打开某个网站时浏览器会自动尝试生产对应的请求,这个请求一般基于我们输入的 URL 生成,比如说下面这个
https://soulmate.org.cn/archives/
关于什么是 URL,我们先要从 URI(Uniform Resource Identifiers)讲起,URI,译作统一资源标识符,是 HTTP 协议中用来定义某种资源的位置的数据,而 URL(Uniform Resource Locator)统一资源定位符是一种特殊的 URI,包含了访问一个资源所需要的全部信息,以下面这个我随便编的 URL 为例

http://example.co:6543/abc.jpeg?id=12&name=asd#section1

这个 URL 中包含以下信息:

  1. http: 表示当前使用的是 http 协议,可用的协议还包括 https,ftp 等,后面跟一个无含义的分隔符//
  2. 域名部分,即上面的 example.co,这里的域名将通过 DNS 解析服务被替换为实际的 IP 地址,当然我们也可以直接写 IP 地址
  3. 端口部分,即:6543,表示这里使用 6543 端口,这部分大部分时候都会被省略,所有的 HTTP 服务默认采用 80 端口,HTTPS 服务采用 443 端口,浏览器会自动补全
  4. 虚拟地址部分,即/abc.jpeg,我们访问的每个页面实际上都对应服务器的某些资源,所以我们需要具体的声明这些资源到底在哪里,例如上面我们就是在访问/abc.jpeg 这一资源
  5. 查询部分, ?Id=12&name=asd 这部分相当于携带的查询参数,告诉服务器用户 id 为 12,name 为 asd,以?代表查询参数的开始。用&分割不同的参数。此外查询参数中的空格用+替代,非 ASCII 字符会通过 Base 64 按照字符集编码进行转换
  6. 锚部分,即 # section1, 用于标识页面的具体位置,我们有时候在进入某个页面时不会跳转到某个页面的开头,而是直接跳转到中间部分,锚正是用来确定这一位置的,这同样不是必须的

根据上面这些信息,浏览器会自动生产对应的 HTTP 请求,当然,在某个页面内执行请求时一般是通过前端的 js 脚本实现的请求生成。当然,这个标准不一定需要严格遵守,比如虚拟地址部分我可以选择不去对应资源而去对应功能,例如用/login 表示登录功能,然后将查询部分视为登录信息

我们来看看一下一个比较标准的 HTTP 请求,

1
2
3
4
5
6
7
8
POST / HTTP1.1
Host: www.wrox.com
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)
Content-Type: application/x-www-form-urlencoded
Content-Length:4 0
Connection: Keep-Alive

name=Professional%20Ajax&publisher=Wiley

我们来分析一下能够看出什么信息,

  • 第一部分,即请求行,也就是第一行,说明我们的请求方式为 POST ,接着是一个空格,/1.html 表示我们当前的访问路径,HTTP/1.1 是我们使用的协议

  • 然后是第二部分,称为请求头,即我们上面的第二到八行,代表了更多约定的信息,如上面的 HOST 表示目标主机,User-Agent 是当前访问网站使用的应用等等,这部分除了常见的,我们也可以自己定义,像塞进去什么都可以

  • 最后是主体, 在请求头之后必须空一行再写比如这里的主体是name=Professional%20Ajax&publisher=Wiley,即携带的内容为name=Professional%20Ajax 与publisher=Wiley 两部分
    你或许会想问请求头中不同的字段分别是什么意思,事实上这些字段都是可以随意定制的,只不过这部分字段我们事先约定好了对应的含义,如果有兴趣完全可以实现
    我们接下来看看各个不同的请求方法

    1. GET

      • 功能和用途 :用于向服务器请求指定的资源。它是最常用的 HTTP 请求方法之一,主要目的是获取数据。例如,当你在浏览器的地址栏输入网址访问网页时,浏览器就会向服务器发送一个 GET 请求来获取网页内容。
      • 特点
        • 请求参数会附加在 URL 地址后面,以 “?” 作为标记,后面跟着参数列表,参数之间用 “&” 分隔。例如,“https://example.com/page?id=1&name=abc”。这种方式使得参数在 URL 中是可见的,方便调试,但也会导致一些安全问题,如敏感信息可能会被记录在浏览器历史记录或服务器日志中。
        • GET 请求通常是幂等的。幂等性是指多次请求对服务器资源的影响是相同的,就像对同一个发起网页多次 GET 请求,不会改变服务器数据,只是获取网页内容。
        • GET 请求可以被缓存,这样可以提高访问效率。浏览器在再次访问相同 URL 时,如果资源没有发生变化,可以使用缓存的数据,而不是每次都向服务器发送请求。
    2. POST

      • 功能和用途 :向服务器提交数据以创建新的资源。例如,当你在网页表单中填写信息并提交注册信息、登录信息等操作时,通常会使用 POST 请求。服务器会接收这些提交的数据并对其进行处理,如存储到数据库等操作。
      • 特点
        • 请求参数放在请求体(body)中,不会显示在 URL 地址栏中,比 GET 请求更安全,适合传递敏感信息。
        • POST 请求是非幂等的,因为每次 POST 请求可能会导致服务器资源的改变,如创建新的用户账号等操作。多次发送相同的 POST 请求可能会产生不同的结果,如多次提交表单可能会导致重复记录。
        • 与 GET 请求相比,POST 请求可以传输大量的数据。因为 URL 的长度是有限的,而请求体的大小限制相对较大,能够满足向服务器发送较多数据(如文件上传等场景)的需求。
    3. PUT

      • 功能和用途 :用于向服务器更新指定资源的状态。它类似于 POST,但不同之处在于 PUT 通常是幂等的。例如,如果想要更新服务器上某个已知资源(如更新用户信息),可以使用 PUT 请求。
      • 特点
        • PUT 请求也是将数据放在请求体中发送给服务器。
        • 由于其幂等性,多次发送相同的 PUT 请求对服务器资源的影响是一致的,这使得在一些需要确保资源状态更新的场景下很有用。例如,在更新一个文档的内容时,即使请求被重复发送,最终文档的内容也会是最后一次请求指定的内容。
    4. DELETE

      • 功能和用途 :用于请求服务器删除指定的资源。例如,删除服务器上的某个文件或数据库中的某条记录。
      • 特点
        • DELETE 请求通常是幂等的,多次删除同一个资源(如果资源存在)后的结果是一样的,即资源被删除。
        • 它的操作相对简单,主要是通过 URL 地址指定要删除的资源。
    5. HEAD

      • 功能和用途 :请求获取与 GET 请求相同的资源,但不要返回资源的主体部分。主要用于获取资源的元信息,如资源的长度、最后修改时间等信息。这有助于客户端在不下载整个资源的情况下,先了解资源的一些基本信息。
      • 特点
        • HEAD 请求和 GET 请求的请求头部分相同,并且服务器在响应 HEAD 请求时也会返回和 GET 相同的响应头信息,只是省略了响应体。
        • 由于没有响应体数据,HEAD 请求通常比 GET 请求的传输数据量小,响应速度也更快。
    6. OPTIONS

      • 功能和用途 :用于获取关于服务器通信选项的信息。它可以请求服务器返回关于该资源所支持的 HTTP 方法等信息。例如,客户端可以使用 OPTIONS 请求来确定服务器是否允许使用 PUT 方法来更新某个资源。
      • 特点
        • OPTIONS 请求可以对整个服务器或特定的资源进行询问。
        • 服务器的响应会包含一个 “Allow -” 首部字段,其中列出了该资源所支持的 HTTP 方法。
    7. PATCH

      • 功能和用途 :用于对资源进行部分修改。与 PUT 不同,PUT 通常是整体替换资源,而 PATCH 是对资源进行部分更新。例如,如果只需要更改用户资料中的某个字段(如用户的邮箱地址),可以使用 PATCH 请求。
      • 特点
        • PATCH 请求是非幂等的,因为多次发送相同的 PATCH 请求可能会导致不同的结果。例如,对某个资源进行两次相同的部分更新,可能使资源的状态进一步改变。

虽然上面的方法有很多,但实际开发中一般不会全部使用,只使用其中几个就够了,毕竟到底在收到请求后干什么是服务器说了算的,你完全可以只使用 POST 一种请求完成所有的功能,当然我个人推荐保留 GET,POST,PUT,DELETE 四种方法,这样做起来比较舒服

讲完了请求,再讲讲响应,一般来说响应体是这样的:

1
2
3
4
5
6
7
8
9
10
HTTP/1.1 200 OK
Date: Fri, 22 May 2009 06:07:21 GMT
Content-Type: text/html; charset=UTF-8

<html>
<head></head>
<body>
<!--body goes here-->
</body>
</html>

第一行是响应头,HTTP/1.1 代表协议,200 OK 是状态码与状态消息,然后是消息报头,类似于请求头,接着空一行就是对应的响应内容了,我简单的贴一点不同的状态码的含义
常见的 HTTP 状态码主要分为以下几类:

1. 信息类(1 xx)

  • 100 Continue(继续) :表示客户端应继续其请求。
  • 101 Switching Protocols(切换协议) :表示服务器已切换到客户端在请求的升级头部指定的协议。

2. 成功类(2 xx)

  • 200 OK(成功) :请求成功,响应的内容通常包含所请求的资源。
  • 201 Created(已创建) :请求成功并且服务器创建了新的资源,通常用于 POST 请求创建新资源后返回。
  • 204 No Content(无内容) :请求成功处理,但返回的响应体为空。

3. 重定向类(3 xx)

  • 301 Moved Permanently(永久重定向) :请求的资源已永久移动到新位置,客户端应使用新的 URI。
  • 302 Found(临时重定向) :请求的资源临时移动到另一个 URI,客户端应向新 URI 发起请求,但后续请求仍使用原 URI。
  • 304 Not Modified(未修改) :客户端缓存的资源未被修改,客户端可以使用缓存的版本。

    4. 客户端错误类(4 xx)

  • 400 Bad Request(坏请求) :请求出现语法错误,服务器无法理解。
  • 401 Unauthorized(未授权) :请求需要用户的身份认证。
  • 403 Forbidden(禁止访问) :服务器拒绝执行请求,通常是因为权限不足。
  • 404 Not Found(未找到) :服务器找不到请求的资源。

5. 服务器错误类(5 xx)

  • 500 Internal Server Error(内部服务器错误) :服务器内部错误,无法完成请求。
  • 501 Not Implemented(尚未实现) :服务器不支持该请求方法。
  • 503 Service Unavailable(服务不可用) :服务器当前无法处理请求,通常是服务器过载或维护。
    按照约定,不提供 600 以及以上的状态码

当然,这也是约定的,如果想,完全可以返回一个 666,这样做虽然浏览器无法正确处理,但前端仍然可以自行处理

在最后,HTTPS 是什么东西呢,HTTPS 是超文本传输安全协议,相比 HTTP 多了安全两字,即 HTTPS 实现了对 HTTP 通信的加密处理,在 HTTPS 中第三方能够看到的只有一个 URL,剩下的一概不可知。

结语

这篇就到这里吧,写的头疼。下一期我们将从 java 的角度来看看 http 通信的实现过程,然后真正的尝试自己手搓一个完整的 http 服务器

  • 标题: 互联网结构与网络通信协议
  • 作者: Soul
  • 创建于 : 2025-05-14 15:31:11
  • 更新于 : 2025-05-20 20:08:13
  • 链接: https://soulmate.org.cn/posts/4ee2495d/
  • 版权声明: 本文章采用 CC BY-NC 4.0 进行许可。