> 文档中心 > 网络原理 | TCP/IP中的连接管理机制 重要协议与核心机制

网络原理 | TCP/IP中的连接管理机制 重要协议与核心机制


应用层、传输层、网络层、数据链路层、物理层

一、并实现一个应用层协

应用层 是程序猿最最经常打交道的一层

其他四层,都是操作系统、驱动、硬件,实现好了的,咱们不需要管 (除非你是系统工程师,驱动开发工程师…….)
在应用层这里,最最重要的事情,就是 “设计并实现一个应用层协议

这是一个非常简单,同时也是在工作中经常要做的事情

举个栗子:

你们公司在开发一个项目,点外卖的软件
当前要开发一个功能,叫做获取用户的订单历史 (在数据库里,服务器来拿),这样的功能,就需要涉及到:

前端 (客户端) 和 后端 (服务器) 彼此之间的交互

  • 前端 和 后端 就是通过网络来进行交互的
  • 在这个交互的过程中,就需要约定好,前端发怎样的数据,后端会对应的数据

前端发的请求需要包含:(前端发的请求)

  • 用户的id

  • 查询的起始时间

  • 查询的结束时间

  • 显示的条数

  • 回来的响应:(后端返回的数据)

  • 查询是否成功:

  • 如果失败,失败的原因,

  • 结果数组:

  • 数组的每个元素就包含了一些属性商品名

  • 商品单价

  • 商品数量

  • 店铺名称

  • 总的金额

格式:

形如这样的工作,就是在设计一个应用层协议
上面的这部分工作,其实就是在规划 请求 和 响应 之间要传递的信息

光约定了传的信息,还不够,还得约定一个具体的格式

请求的格式形如:
1234\t2022-04-01\t2022-04-09\t10

响应的格式:
OK
“”
蛋炒饭\t10\t1\t沙县小吃\t10\n
炒面\t8\t1\t沙县小吃\t8\n

当前只是给出了一种可能的格式
此处这里数据的组织格式,也是可以随心所欲的约定的
相比之下,只要能够让 客户端 和 服务器 之间都按照─样的格式来交互就行了

设计一个应用层协议,主要就是包含两个工作:

  • 明确传输的信息
  • 明确数据的组织格式

正是因为这里的应用层协议,可以随心所欲的来指定,这就导致两极分化非常严重

上面这种设计就是一个不太好的设计

  1. 输出传输效率并不高运行效率低
  2. 可读性也不好,开发效率也低

正因为如此,大佬们发明了一些比较好的协议的模板,可以让我们直接往上套
当下比较流行的一些这种协议的模板 (数据的组织格式)

  1. xml 可读性好,但是运行效率不高

  2. json 可读性好,但是运行效率不高

  3. protobuffer 可读性不好,运行效率很高


1、xml

xml 是属于一种比较老牌的数据格式了,现在虽然也在用,但是用的越来越少了
xml 的格式非常有特点:标签构成的

内容

:开始标签 要表示的值 结束标签

<request><userld>1234</userld><startTime>2022-04-01</startTime>    <finishTime>2022-04-09</finishTime>    <count>10</count></request>

标签名就是 key标签值就是 value

通过这些标签,就更好的体现了这个数据的可读性,尤其是哪个部分是啥意思,非常一目了然

虽然 xml 提高了可读性,但是又引入了太多的辅助信息,这些标签名啥。一个服务器程序,最贵的硬件资源就是网络带宽!! (贼贵 !!!)
对于 xml 来说,因为要表示这些辅助信息,就导致传输相同数目的请求的时候,占用的网络带宽是更高的
如果带宽固定,相同时间能传输的请求个数就是更少的

因此,xml 现在很少作为应用层协议的设计模板了,现在使用 xml 主要是作为一些配置文件


2、json

json 当下最流行的一种,设计应用层协议的数据格式,咱们后面再工作中也会经常用到这个

{:,:,......}

通过 {} 构成了键值对结构,一个 {} 中有很多个键值对键值对之间使用逗号分割,键和值之间使用冒号分割。

要求必须是字符串类型的;允许很多种 (数字,字符串,布尔,数组,另一个 json…)

{userld: 1234,startTime: '2022-04-01',    finishTime: '2022-04-O9",count: 10}
  • json 中表示字符串:单引号或者双引号都可以 (类似于 SQL)
  • 最后一个键值对,后面可以有逗号,也可以没有 (标准要求是没有的,但是一般的 json 解析器,都不会在意这个细节)
  • json 要求 key 一定是字符串,因此 key 这里的引号可以省略,除非 key 中包含了一些特殊符号 (比如像空格,或者– …) 必须要加上引号

相比于 xml,json 同样能保证可读性,同时又没有 xml 那么繁琐 (占用的带宽要更少一点)


3、protobuffer

json 虽然传输效率比 xml 要高,但是仍然要多传递一些冗余信息,就是 key 的名字。
这一点在表示数组的时候,尤为明显,例如使用 json 表示响应格式:

{ok: true,    reason: "",    data: [{name: '蛋炒饭',     price: 10,count: 1,     totalPrice: 10 },{name: "炒面',     price: 8,count: 1,     totalPrice: 8 }]}

上面是一个带有数组,带有嵌套的 json
当表示一个更加复杂的数据,比如数组的时候,此时这里的很多 key 就会重复出现 N 次, 也就占用了更多的额外带宽

  • protobuffer 应运而生,是一种二进制格式的数据
  • protobuffer 的数据中,不再包含上面 key 的名字了,而是通过顺序以及一些特殊符号,来区分每个字段的含义
  • 同时再通过一个 IDL 文件描述这个数据格式 (每个部分是啥意思), IDL 只是起到一个辅助开发的效果,并不会真正的进行传输,传输的只是二进制的纯粹的数据

这是一个简化的表示版本:
13\3 蛋炒饭\210\21\210N3炒面\28\2\1\28

通过二进制的数据重新对这里的内容进行编排,甚至可能还会进行一些数据压缩
这样做传输效率会更高,但是也会让这个数据肉眼难以观察调试起来就不方便了

json 的应用范围要比 protobuffer 更广,开发效率 > 运行效率

开发效率包含了 开发 和 调试
光论开发, protobuffer 有IDL (protobuffer 约定的一种文件格式,类似于 C 结构体的写法)

message Response {bool ok = 1,string reason = 2,repeated Data data = 3,}

如果线上环境出问题
如果是用 json,出问题的请求和响应,一目了然。如果是用 protobuffer,二进制的数据,没法肉眼看,就得自己开发一个专门的程序来解析这个数据,分析这里的问题

设计应用层协议,是一件非常普遍的事情,也是一件并不复杂的事情

设计应用层协议,要做的工作:

  1. 明确**传输的信息 **(根据需求)
  2. 明确传输的格式 (参考现有模板, json,xml,protobuffer)

但是除此之外,业界也有一些现成的,已经被设计好,已经被广泛使用了的应用层协议
(也不是所有的时候,都需要从零设计,很多时候,可以直接基于现成的协议,稍加修改,稍加扩充,进行这种二次开发,类似于B站上的哪些鬼畜视频,二次创作)

其中最最知名的应用层协议,当属 HTTP


二、传输层重要协议

虽然传输层,是操作系统内核实现,程序猿不需要直接和传输层打交道。但是传输层对我们来说仍然意义重大!! 进行网络编程都要用到 socket。一旦你调用了 socket,代码就进入到传输层的范畴
如果一切顺利,就还好。一旦代码出现一些 bug,为了解决理解这些 bug,传输层的一些知识就是必要的了
传输层的协议,也是面试中特别爱考的 TCP 协议

详见此篇博客:【Java 网络编程】网络通信原理、TCP、UDP 回显服务

端口号:

整数,0 - 65535 之间的整数

知名端口号:把 0 - 1024 这些端口号,给划分出了一些具体的作用
很多网络服务是属于非常常用,非常广泛的服务。为了更好的管理,就给这些服务分配一些专门的端口号,并不是强制要求,而只是建议

80 http 服务器
443 https 服务器
22 ssh
23 ftp

如果你自己部署 http 服务器,可以把他绑定到 80,也可以绑定到其他端口上。像 java 中知名的 http 服务器 tomcat,使用的默认端口就不是80,而是 8080

传输层的协议有很多,其中最常见的就是 UDPTCP
学习一个协议,很多时候就是在研究报文格式


三、UDP 协议

1、UDP 协议端格式

很多计算机网络的书上都会有,但是这种画法,其实是不对的 (容易给人误导),之所以这么画,只是为了印刷的时候排版

在这里插入图片描述

真正的情况:所谓的把应用层数据报分装成 UDP 数据报,本质上就是在应用层数据包的基础上,添加了 8 个字节的报头

在这里插入图片描述

**以 UDP 客户端为例:**代码中写的端口号,就会被打包到这样的 UDP 数据报中 (在报头中体现的)

在这里插入图片描述

此处报文长度是 2 个字节,范围 0 - 655350 - 64k (1k = 1024byte,64 * 1024 = 65536)
64k 大,还是小?够不够用?

  • 64k 大小在现代的互联网程序中,非常捉襟见肘了
    这是 UDP 使用中一个非常致命的缺陷,无法表示一个比较大的数据报,

如果确实需要传一个大的数据
可以在应用层,针对大的数据报,进行分包 (拆成多个部分),然后再通过多个 UDP 数据报分别发送,这个时候,接收方再把收到的几个包重新拼接成完整的数据

  • 下策!太麻烦了
    拆包组包的代码,写起来非常复杂,要考虑到很多情况 (包丢了咋办,包的顺序错了咋办…)
  • 上策,就是改成 TCP,TCP 没有这样的长度限制

例如使用浏览器出现的广告:解决方案,就是把 UDP 直接改成 TCP

在这里插入图片描述


2、校验和

是用来验证网络传输的这个数据是否是正确

  • 网络上传递的数据,本质是光信号和电信号
  • 但是,如果有一些外界干扰,磁场之类的… 就可能会导致原有的一些传递的信息发生了改变
    • 正常发一组连续的高频信号,因为遇到了一个强磁场,就可能导致其中的某些高频信号变成低频…
      (不考虑是否符合物理原理,只是打个比方)
    • 就可能导致 0 - 1 数据就错了,bit 翻转
  • 需要尽可能的识别出数据是不是错了,不能将错就错
    校验和 就能帮助我们发现数据中的错误

举个例子:

我去楼下买菜:黄瓜,鸡蛋,西红柿,芹菜

我拎着:黄瓜,鸡蛋,西红柿,韭菜

一共买四样

  • 校验和正确,不能保证数据 100% 就是对的,但是校验和不正确,一定认为数据是有问题的

  • 虽然如此,校验和仍然是广泛使用成本非常低,效果非常好

  • 计算校验和的时候,也不一定单纯的就是使用 “个数”
    还可以使用数据内容参与运算如果是基于数据内容得到的校验和,那么如果数据出错,被识别出来的概率是更高的

现在使用的一些用来生成校验和的算法 crc,md5,sha1 等等,这些操作都是基于内容来生成的校验

UDP 的校验和是针对 UDP 数据报检验了一次,以太网这里校验和是针对以太网数据帧校验了一次

述一个网络数据的时候,用了这几个词:
包,报,段,帧.……

  • 传输层的数据,通常叫做段 “segment”

  • 网络层的数据,通常叫做 包 / 报 “packet”

  • 数据链路层,通常叫做 帧 “frame”

很多时候咱们都不会去明确区分这几个概念

UDP 的特点:

UDP 传输的过程类似于寄信。

无连接,不可靠传输,面向数据报,全双工

无连接
知道对端的IP和端口号就直接进行传输,不需要建立连接;

不可靠
没有任何安全机制,发送端发送数据报以后,如果因为网络故障该段无法发到对方,UDP协议层也不会
给应用层返回任何错误信息

面向数据报
应用层交给UDP多长的报文,UDP原样发送,既不会拆分,也不会合并;
用UDP传输100个字节的数据:

  • 如果发送端一次发送100个字节,那么接收端也必须一次接收100个字节;而不能循环接收10次,
    每次接收10个字节

全双工

一条链路,双向通信

缓冲区:

UDP 只有接收缓冲区,没有发送缓冲区:

  • UDP 没有真正意义上的 发送缓冲区。发送的数据会直接交给内核,由内核将数据传给网络层协议进行后续的传输动作;
  • UDP 具有接收缓冲区,但是这个接收缓冲区不能保证收到的 UDP 报的顺序和发送 UDP 报的顺序一致;如果缓冲区满了,再到达的UDP数据就会被丢弃;

大小受限

UDP协议首部中有一个16位的最大长度。也就是说一个UDP能传输的数据最大长度是64K(包含UDP首部)。

基于UDP的应用层协议

  • NFS:网络文件系统
  • TFTP:简单文件传输协议
  • DHCP:动态主机配置协议
  • BOOTP:启动协议(用于无盘设备启动)
  • DNS:域名解析协议

当然,也包括你自己写UDP程序时自定义的应用层协议


四、TCP 协议

1、TCP 协议段格式

TCP,即 Transmission Control Protocol,传输控制协议。人如其名,要对数据的传输进行一个详细的控制。
TCP 是一个非常非常重要的协议,不光在实际开发中广泛使用,同时也是面试中的高频问题

源/目的端口号:表示数据是从哪个进程来,到哪个进程去

在这里插入图片描述


2、TCP中的一些核心机制

有连接,面向字节流,全双工:

  • 这三点在代码中都能体现出来

可靠传输:

  • 虽然代码上不能直接体现出来,但是这个却是 TCP 最最核心的机制
    引入 TCP 的关键原因,就是为了保证可靠传输!
    因此 TCP 中的很多机制,都是围绕这个可靠传输来展开

3、确认应答(安全机制)

3.1、举例:

TCP 对数据传输提供的管控机制,主要体现在两个方面:安全和效率。
这些机制和多线程的设计原则类似:保证数据传输安全的前提下,尽可能的提高传输效率。

  • 确认应答机制,就是保证可靠传输的核心机制
  • **可靠性:**发送方发出去数据之后,能够知道对方有没有收到
  • 关键就是接收方收到消息之后,给发送方,返回一个应答报文 (ACK, acknowledge),表示自己已经收到了

举个栗子:

我给女神发个短信,十几年前 07 左右的时候,手机短信是很容易发丢了的

在这里插入图片描述

当下这个确认应答,还有点小问题,如果我发了多个消息

在这里插入图片描述

如果数据就是按照这样的方式来传输,倒还好。
我先发的吃麻辣烫,后发的做女票;我先收到的是 “好啊好啊”,后收到的是"滚"

网络上,数据接收的顺序,不一定和发送的顺序完全一致!!! 存在后发先至的情况!!

在这里插入图片描述

滚明明是后发的,但是反而先到了,

后发先至,对于网络来说,太常见不过的情况了,网络环境非常复杂,连续发的两个包,不一定就是走同一条路

如果大家参加过婚礼,接亲活动,也会遇到这种情况,等头车先到


3.2、编号

为了解决上述问题,办法也很简单:对消息进行编号

在这里插入图片描述

就是32位序号/32位确认号,确认序号:表示当前这个应答报文,是针对哪个消息进行的确认应答

TCP 的针对消息的序号,还有说法,并不是按 "消息条数” 来进行编号的,而是按照字节来编号

TCP 针对每个字节的数据都分别进行了编号。即为序列号

在这里插入图片描述

每一个 ACK 都带有对应的确认序列号,意思是告诉发送者,我已经收到了哪些数据;下一次你从哪里开始发。

在这里插入图片描述

A 给 B 发送了 1000 个字节,序号是 1 - 1000。发送的数据是 1 - 1000,意思是 TCP 报头中的序号是 1,报文长度是 1000,通过这个信息,来明确的范围。

主机 B 给 A 返回的应答报文 (ACK) 就会带有一个确认序号,叫做1001。小于 1001 的数据都已经被主机 B 收到了,
接下来主机 A 应该从 1001 这个序号开始往后进行传递

1 - 1000 这是同一个 TCP 数据报,这一个 TCP 数据报通过层层封装变成了一个以太网数据帧,进行传输,
如果传了多个数据报,分装成了多个以太网数据帧多个数据帧之间才会出现后发先至


4、超时重传机制(安全机制)

4.1、举例:

相当于对确认应答进行了补充
确认应答是网络一切正常的时候,通过 ACK 通知发送方我收到了,如果出现了丢包的情况,超时重传机制就要起到效果了

在这里插入图片描述

当我发出去消息之后,就担心这个消息是不是发丢了,我就在等这个 ACK
如果确实消息发丢了对方直接就没收到,我这里肯定也没法收到 ACK

在这里插入图片描述

另一个情况就是 ACK 丢了,虽然对方收到了消息,但是我收不到 ACK
对于发送方来说,我无法区分是哪种原因导致的没有收到 ACK,我就直接往坏了想,我就认为是对方压根没收到!!!

重新再发一次
这里的重发也不是立即就重发的,得等一会 (给点反应时间)
如果我等了 10 分钟 (超出了等待时间),还没有收到这个 ACK,就重发一次 (进行重传)!


4.2、去重

如果是 ACK 丢了,此时触发了超时重传,就会导致接收方收到了重复的消息!!
如果这里进行的操作不是请吃麻辣烫,而是转账,就会引起麻烦

TCP 内部就会有一个去重操作

  • 接收方收到的数据,会先放到操作系统内核的 “接收缓冲区” 中,接收缓冲区可以视为是一个内存空间,并且也可以视为是一个阻塞队列
  • 收到新的数据,TCP 就会根据序号,来检查看这个数据是不是在接收缓冲区中已经存在了,如果不存在,就放进去,如果存在,直接丢弃
  • 保证应用程序调用 socket API 拿到的这个数据一定是不重复的! 应用程序感知不到超时重传的过程的!

重发的这个数据是会重新封装
发送出去的数据,这是 TCP 负责发送的
调用一个 socket 中的 write 操作,本质上是把这个数据写到 TCP 的 “发送缓冲区” 中,由内核从发送缓冲区取数据,通过网卡传输 (也就是封装的过程)
当触发了超时重传的时候,内核再把刚才缓冲区的内容,重新再进行封装,重新传输
如果顺利收到了 ack ,就可以把这个数据从发送缓冲区中删掉
发送缓冲区也可以视为是一个阻塞队列,重传这个事情都是 TCP 干的事


4.3、重传时间

正常情况下,连续发丢了两次的概率是比较低的!!! 因此我就期望第二次发送是能够成功的,
如果网络不是有太大的问题,一般重传都是能成功的

超时重传,重传的数据,一定会成功嘛?肯定不是 100%

  • 如果只是因为网络抖动了一下,这个时候重传还是很容易成功的,如果是网络遭受了严重的伤害,可能没那么容易恢复,重传也成功不了了:网线断了 / 没有网费…,在这个情况下,重传不会一直进行

重传如果失败,可能还会再尝试,也不会无休止的重传
连续几次重传都不行,就认为这个网络可能是遇到了严重的情况,再怎么重传怕是也不行了,就只能放弃了 (自动的断开 TCP 的连接)
重传的这个时间间隔,也不是固定的,一般来说会逐渐变大 (重传的频率会逐渐降低)

—次传输失败的概率,本身就是比较小,连续两次传输都失败,概率就是小上加小。这个时候 TCP 基本就不太指望能够重传成功了,重传频率再高,大概率也是没啥用的,还不如省点力气
假设丢包概率为 10% (相当高的数字),连续两次丢包的概率就是 10% * 10% = > 1%
连续两次都丢包意味着当前丢包的概率已经远远高于 10% 了,接下来继续再发,能够顺利发过去的可能性也会是比较小的

如果超时的时间如何确定?

  • 最理想的情况下,找到一个最小的时间,保证 “确认应答一定能在这个时间内返回”。
  • 但是这个时间的长短,随着网络环境的不同,是有差异的。
  • 如果超时时间设的太长,会影响整体的重传效率;
  • 如果超时时间设的太短,有可能会频繁发送重复的包;

TCP为了保证无论在任何环境下都能比较高性能的通信,因此会动态计算这个最大超时时间

  • Linux中(BSD Unix和Windows也是如此),超时以500ms为一个单位进行控制,每次判定超时重发的超时时间都是500ms的整数倍。
  • 如果重发一次之后,仍然得不到应答,等待 2*500ms 后再进行重传。 *
  • 如果仍然得不到应答,等待 4500ms 进行重传。依次类推,以指数形式递增。
  • 累计到一定的重传次数,TCP认为网络或者对端主机出现异常,强制关闭连接

5、连接管理机制(安全机制)

也是 TCP 保证可靠性的一个机制

[非常经典的面试题] TCP连接管理,是网络部分最高频的面试题,没有之一!!!

5.1、建立连接:三次握手

5.1.1、过程

在正常情况下,TCP 要经过三次握手建立连接,四次挥手断开连接

客户端和服务器之间,通过三次交互,完成了建立连接的过程,“握手” 是一个形象的比喻

在这里插入图片描述

在这里插入图片描述

客户端是主动发起连接请求的一方,客户端先发送一个 SYN 同步报文段,给服务器

SYN 同步报文段:

网络原理 | TCP/IP中的连接管理机制 重要协议与核心机制

如果 SYN 这一位为 1,表示当前报文就是一个 “同步报文段”主机 A 和主机 B 之间要建立连接

在这里插入图片描述

这个过程就像表白,双向奔赴的过程,双方都像对方表白,这个才算是正式确立关系

如果 ACK 这一位为 1,表示这个报文就是一个 “确认报文段”,(刚才说的确认应答报文,也是 ACK 为 1)

看起来是四次,但是中间的这两次,服务器发送的 ACK 和 SYN,是一定会合二为一的!!!
每次要传输的数据,都要经过一系列的封装和分用,才能完成传输
封装两次肯定不如封装一次来的更高效
例如我在某个淘宝店铺上,买了两件衣服,肯定是打一个包裹发给我更好的
ACKSYN 合在一起也容易,让他们都是 1,所以是三次交互

在这里插入图片描述


5.1.2、三次握手有何作用?和可靠性有什么关系?

  1. 三次握手,相当于是 “投石问路”,检查一下当前这个网络的情况是否满足可靠传输的基本条件
    如果你网络本身就效果非常差,强行进行 TCP 传输,也会涉及到大量丢包

例如相亲,先简单了解一下对方的情况,有没有房子,有没有车,收入有多少,有没有啥不良嗜好…

更具体的说,可以认为,三次握手其实也是在检测通信双方,发送能力和接收能力是否都正常
有的时候,可能我给同学们解决问题的时候,打字说不清,就需要QQ电话
打QQ电话的时候,我上来第一句话,喂喂喂,能听见嘛?

这个过程就是在验证通信双方的发送和接收能力是否都正常,验证麦克风喇叭是不是都好使

在这里插入图片描述

  1. 让双方能够协商一些必要的信息

TCP 通信过程中,需要客户端和服务器之间,有一些共同的信息的
因此在三次握手的同时,也会相互之间交互一些必要的内容

在这里插入图片描述


5.1.3、经典的面试题:

描述 TCP 三次握手的过程

为什么握手是三次?两次?四次?

针对这个问题,要画图
如果是线上面试(牛客),面试平台一般都是支持在线画图,如果是线下面试,你要记得自己带上纸币

在这里插入图片描述

TCP 三次握手的图,有很多版本,这是最简单的一个版本
还有在 SYN 和 ACK 上面标记上序号,和确认序号,还有标记上 TCP 状态转换的
还有标记上对应的 socket API 的

四次:

  • 行!但是没必要

  • 分开传输降低效率,不如合在一起好!

两次:

  • 不行!

  • 意味着缺少最后一次

    此时客户端这边,关于发送接收能力正常的情报是完整的,但是服务器这边是残缺的,服务器不知道自己的发送能力是否ok,也不知道客户端的接收能力是否ok

    此时此刻,服务器对于当下能否满足可靠传输,心里是没底的!!! 这第三次交互,就是为了给服务器吃一个定心丸


5.2、断开连接:四次挥手

5.2.1、过程

三次握于,就让客户端和服务器之间建立好了连接
其实建立好连接之后,操作系统内核中,就需要使用一定的数据结构来保存连接相关的信息!! 保存的信息其实最重要就是前面说的"五元组"
而且客户端,服务器都要保存五元组
源IP,源端口,目的IP,目的端口,TCP
既然是保存了信息就需要占用系统资源 (内存)

有朝一日,连接断开了,不复存在了)
此时之前保存了连接信息就没意义了,对应的空间也就可以释放了

在这里插入图片描述

双方各自向对方发送了 FIN (结束报文段)请求,并且各自给对方一个 ACK 确认报文,FIN 为 1 就表示这是一个结束报文段

网络原理 | TCP/IP中的连接管理机制 重要协议与核心机制


5.2.2、与三次握手的不同

  • 三次握手,一定是客户端主动发起的 (主动发起的一方才叫客户端)

    四次挥手,可能是客户端主动发起,也可能是服务器主动发起

  • 三次握手,中间两次能合并

    四次挥手,中间两次有时候合并不了 (有时候是能合并)

不能合并的原因,在于,B 发送 ACK 和 B 发送 FIN 的时机是不同的!!!

四次挥手中:

  • B 给 A 发的 ACK,是内核负责的;B 给 A 发的 FIN用户代码负责 (B的代码中调用了 socket.close() 方法,才会触发 FIN)
    收到 FIN,立即就由内核返回 ACK
    执行到用户代码中 close 才会触发,取决于用户代码咋写的
  • 如果这两操作之间的时间差,比较大,就不能合并了!
    如果时间差比较小,这是可能会合并的 (延时应答和捎带应答,后面再说)

三次握手中:

  • B 发送的 ACK 和 SYN 同一时机,就能够合并
  • 此处的 B 给A 发送的 ACKSYN 都是操作系统内核负责进行的

5.2.3、服务端状态转化

在这里插入图片描述

关于状态转换的详细情况,不深入讨论,仍然要认识两个重要的状态

CLOSE_WAIT:

  • 四次挥手挥了两次之后出现的状态,这个状态就是在等待代码中调用 socket.close 方法,来进行后续的挥手过程。

  • 正常情况下,一个服务器上不应该存在大量的 CLOSE_WAIT,如果存在,说明大概率是代码 bug , close 没有被执行到

TIME_WAIT:

  • 谁主动发起 FIN,谁就进入 TIME_WAIT,起到的效果,就是给最后一次 ACK 提供重传机会

  • 表面上看起来,A 发送完 ACK 之后,就没有 A 的啥事了,
    按理说,A 就应该销毁连接,释放资源了,但是并没有直接释放,而是会进入 TIME_WAIT 状态等待一段时间,一段时间之后,再来释放连接

  • 等这一会,就是怕最后一个 ACK 丢包!!!
    如果最后一个 ACK 丢包了,就意味着,B 过一会就会重传 FIN

    示例:

如果最后一个 ACK 丢了 B,就收不到 ACK
B 是无法区分是 FIN 丢了,还是 ACK 丢了,于是 B 就假设是 FIN 丢了,于是就重传 FIN (超时重传)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Uvf8xmRM-1650383527520)(C:\Users\Chloe\AppData\Roaming\Typora\typora-user-images\image-20220414182739195.png)]

假设在这里 A 就释放了连接
当重传的 FIN 到达 A 之后,此时就无人能处理这个 FIN,也就无人能针对这个 FIN 进行应答了,
因此 A 的连接就不应该释放太早,要等待一段时间,确保 B 不会重传 FIN 了之后,再真正销毁连接

TIME_WAIT 应该持续多久呢?设定的时间是:2 * MSL
MSL 表示网络上任意两点之间,传输需要的最大时间,这个时间也是系统上可以配置的参数
一个典型的设置就是 60s (经验值)


6、滑动窗口(效率机制)

6.1、过程

刚才我们讨论了确认应答策略,对每一个发送的数据段,都要给一个ACK确认应答。收到ACK后再发送下一个数据段。这样做有一个比较大的缺点,就是性能较差。尤其是数据往返的时间较长的时候。

TCP 虽然可靠性是最高的机制,但是 TCP 也会尽可能的提高传输效率!!

既然这样一发一收的方式性能较低,那么我们一次发送多条数据,就可以大大的提高性能(其实是将多
个段的等待时间重叠在一起了)。

在这里插入图片描述

在这里可以看到,由于确认应答机制的存在,导致了当前每次执行一次发送操作,都需要等待上个 ACK 的到达,大量的时间都花在等 ACK 上了

滑动窗口,本质就是在 “批量的发送数据” 一次发一波数据,然后一起等一波 ACK

  • 窗口大小指的是无需等待确认应答而可以继续发送数据的最大值。上图的窗口大小就是4000个字节(四个段)。
  • 发送前四个段的时候,不需要等待任何ACK,直接发送;
  • 收到第一个ACK后,滑动窗口向后移动,继续发送第五个段的数据;依次类推;
  • 操作系统内核为了维护这个滑动窗口,需要开辟 发送缓冲区 来记录当前还有哪些数据没有
  • 应答;只有确认应答过的数据,才能从缓冲区删掉;
  • 窗口越大,则网络的吞吐率就越高;

在这里插入图片描述

TCP 是得保证可靠传输的,可靠传输的灵魂就是确认应答,如果没有这个 ACK,可靠传输就形同虚设。等,是不能不等!!

如果一次批量发送数据为 N,统一等待一波,此时这里 N 称为 “窗口大小”

“滑动” 的意思是,并不用把 N 组数据的 ACK 都等到了,才继续往下发送,而是收到一个 ACK,就继续往下发送—组

  • 当前是在等待 1001,2001,3001,4001 四组 ACK,不需要等到 4001 到了,才继续往下发
  • 只要 1001 到了,就可以往下多发一组 (4001 -5000),此时等待 ACK 的范围 2001,3001,4001,5001,
  • 如果是 2001 到了,就继续再往下发一组 (5001-6000),此时等待的 ACK 的范围 3001,4001,5001,6001

在这里插入图片描述


6.2、丢包

当前这个窗口大小越大,可以认为就是传输速度就越快窗口大了,同一份时间内等待的 ACK 就更多,总的等待 ACK 的时间就少了

当前有一个核心问题:丢包
在滑动窗口的背景下,如果丢包,该如何重传呢?

丢包分成两种情况:

**情况一:**数据包已经抵达,ACK 被丢了

在这里插入图片描述

在这个图上,画的情况是 6 组 ACK,丢了 3 组

1、在发送 4001 之前,发现收到了一个 2001,此时没有收到 1001。2001 表示的意思就是:2001 之前的数据都已经确认收到了,1001 能否收到已经无足轻重了
ACK 确认序号的特定含义,就保证了后一条 ACK 就能涵盖前一条

因此,2001 意味着 1 - 2000 都收到了,既然如此,1 - 1000 更是已经收到了

2、当发送方收到 5001 的时候,意味着 1 - 5000 的数据都确认收到了。3001 和 4001 被丢包,也毫无影响,只要收到了 5001,就涵盖了 3001 和 4001 表达的信息

ACK 这里更像在闯,闯到了最后一关,说明前面的关都解锁了

如果是批量发送 1 - 4000 这些数据,当 ACK 收到了 4001 之后,意味着窗口一下就往后挪四个格子

情况二:数据包 就直接丢了

在这里插入图片描述

1、由于 1001 - 2000 这个数据丢了,所以 B 就再反复索要 1001 这个数据,即使 A 给 B 已经往后发了,这个时候仍然是在索要 1001,当索要若干次之后,A 就明白了,就触发了重传

2、当 A 重传了 1001 - 2000 之后,B 的接收缓冲区,就把缺口给补上了。
后续的 2001 - 7000 这些数据都是已经传输过了的,这些数据就不必再重传。接下来 B 就向 A 索要 7001 开始的数据

这里的重传只是需要把丢了的那一块数据给重传了即可
其他已经到了的数据就不必再重传了,整体的重传效率还是比较高的:快重传

这个快速重传就像看电视剧,以前大家看电视剧是使用电视看的,每天都是固定时间播放的,如果某天你有事了,其中某一集没看成,就只能继续往后看
过两天,发现另外一个电视台,也在放这个电视剧,就可以通过另外一个电视台,把缺了的那集给补完就可以了


7、流量控制(安全机制)

  • 是滑动窗口的延伸,目的是为了保证可靠性
  • 在滑动窗口中,窗口越大,传输速率就越高,但是不光要考虑发送方,还得考虑接收方,接收端处理数据的速度是有限的
  • 如果发送端发的太快,导致接收端的缓冲区被打满,发送方发的贼快,接收方根本处理不过来了,这个时候如果发送端继续发送,接收方就会把新收到的包给丢了,造成丢包,继而引起丢包重传等等一系列连锁反应,发送方是不还得再重传
  • 因此 TCP 支持根据接收端的处理能力,来决定发送端的发送速度。这个机制就叫做 流量控制(Flow Control);

流量控制的关键,就是得能够衡量接收方的处理速度
此处,就直接使用接收方接收缓冲区的剩余空间大小,来衡量当前的处理能力

在这里插入图片描述

这样的数据传输过程,可以理解成 “生产者消费者模型”:

A:就是生产者
B:的应用程序就是消费者
B:的接收缓冲区,就是交易场所

接收缓冲区肯定有一个总大小

随着 A 发送数据,接收缓冲区里就会逐渐放入一些数据,剩余空间就会逐渐缩小

如果剩余空间比较大,就认为 B 的处理能力是比较强,就可以让 A 发的快点
如果剩余空间比较小,就认为 B 的处理能力是比较弱,就可以让 A 发的慢点

这个过程就像一个水池:

水池总大小,假设是 100 立方米,此时进水的速度,就取决于水池当前的水位高不高,

  • 水位高 (剩余空间小了),进水就慢点
    水位低 (剩余空间大了),进水就快点

就直接用剩余空间的大小来制约进水的速度,当然,剩余空间的大小,也是在侧面描述出水的速度
动态平衡

接收端如何把剩余空间多少 (窗口大小) 告诉发送端呢?

  • 通过 ACK 报文来告知,回忆我们的 TCP 首部中,有一个16 位窗口字段,就是存放了窗口大小信息;

在这里插入图片描述

通过这个 16 位窗口大小,来衡量当前接收方剩余空间的大小,

发送方收到这个数据之后,就会灵活的调整发送速度 (调整窗口大小)

那么问题来了,16位数字最大表示 65535,那么TC P窗口最大就是 65535 字节么?
虽然这里是 16 位,实际上这里的窗口大小不止是 64k,还可以更大
实际上,TCP 首部 40 字节选项中还包含了一个 窗口扩大因子 M,实际窗口大小是 窗口字段的值左移 M位;

在这里插入图片描述

**当窗口大小为 0,是否意味着 A 就不再发送数据了?**A 确实是不发送了,但是又不是完全不发送,水池满了,就不应该继续注水了
但是放水是不仍然在进行 (满并不是一直满,满一会就不满)

虽然 B 反馈的窗口大小是 0,但是 A 也不能完全不发数据
需要定期的发送一个探测报文,探测报文不传输实际的数据,只是为了触发 ACK,只是为了知道当前的窗口大小是多少


8、拥塞控制(安全机制)

  • 也是滑动窗口的延伸,也是限制滑动窗口发送的速率

    拥塞控制衡量的是,发送方到接收方,这整个链路之间,拥堵情况 (处理能力)

  • 虽然 TCP 有了滑动窗口这个大杀器,能够高效可靠的发送大量的数据。但是如果在刚开始阶段就发送大量的数据,仍然可能引发问题

  • 因为网络上有很多的计算机,可能当前的网络状态就已经比较拥堵。在不清楚当前网络状态下,贸然发送大量的数据,是很有可能引起雪上加霜的

  • TCP 引入 慢启动机制,先发少量的数据,探探路,摸清当前的网络拥堵状态,再决定按照多大的速度传输数据

在这里插入图片描述

A 能够发多快,不光取决于 B 的处理能力,也取决于中间链路的处理能力

A 和 B 之间的中间节点,有多少个?不知道,很多,就很难对这些设备逐一衡量

拥塞控制的处理方案,就是通过 “实验" 的方式,逐渐调整发送速度,找到一个比较合适的值 (范围)

A 开始的时候以一个比较小的窗口来发送数据,如果数据很流畅的就到达了,逐渐加大窗口大小,如果加大到一定程度之后,出现了丢包 (丢包就意味着通信链路出现拥堵了),这个时候再减小窗口

通过反复的增大 / 减小过程,逐渐就摸到了一个合适的范围拥塞窗口就在这个范围中不断变化,达到 “动态平衡”

具体这个拥塞窗口是如何变化的呢?

在这里插入图片描述

此处引入一个概念程为拥塞窗口
发送开始的时候,定义拥塞窗口大小为 1;
每次收到一个ACK应答,拥塞窗口加 1;
每次发送数据包的时候,将拥塞窗口和接收端主机反馈的窗口大小做比较,取较小的值作为
实际发送的窗口;

像上面这样的拥塞窗口增长速度,是指数级别的。“慢启动” 只是指初使时慢,但是增长速度非常快。

为了不增长的那么快,因此不能使拥塞窗口单纯的加倍。
此处引入一个叫做慢启动的阈值
当拥塞窗口超过这个阈值的时候,不再按照指数方式增长,而是按照线性方式增长
当 TCP 开始启动的时候,慢启动阈值等于窗口最大值;
在每次超时重发的时候,慢启动阈值会变成原来的一半,同时拥塞窗口置回1;

少量的丢包,我们仅仅是触发超时重传;大量的丢包,我们就认为网络拥塞
当 TCP 通信开始后,网络吞吐量会逐渐上升;随着网络发生拥堵,吞吐量会立刻下降;

拥塞控制,归根结底是TCP协议想尽可能快的把数据传输给对方,但是又要避免给网络造成太大压力的折中方案。
TCP拥塞控制这样的过程,就好像 热恋的感觉


9、延时应答(效率机制)

  • 相当于流量控制的延伸。
  • 流量控制是踩了下刹车,使发送方,发的不要太快
    延时应答,就想在这个基础上,能够尽量的再让窗口更大一些

如果接收数据的主机立刻返回 ACK 应答,这时候返回的窗口可能比较小。

  • 假设接收端缓冲区为 1M。一次收到了 500K 的数据;如果立刻应答,返回的窗口就是 500K;
  • 但实际上可能处理端处理的速度很快,10ms 之内就把 500K 数据从缓冲区消费掉了;
  • 在这种情况下,接收端处理还远没有达到自己的极限,即使窗口再放大一些,也能处理过来;
  • 如果接收端稍微等一会再应答,比如等待 200ms 再应答,那么这个时候返回的窗口大小就是 1M;

一定要记得,窗口越大,网络吞吐量就越大,传输效率就越高。我们的目标是在保证网络不拥塞的情况下尽量提高传输效率

那么所有的包都可以延迟应答么?肯定也不是;

  • 数量限制:每隔 N 个包就应答一次;
  • 时间限制:超过最大延迟时间就应答一次;

具体的数量和超时时间,依操作系统不同也有差异;一般 N 取 2,超时时间取 200ms

举个栗子:

在这里插入图片描述

注水的同时也在出水

每次注入一波水,就会询问当前水池里剩余空间有多少

采取的策略就是不立即回答,而是稍微晚一会回答,迟一点回答,意味着在这个延时间里,就会出更多的水
如果立即回答,可能回答一个剩余 20 吨,如果延时一会回答,就可以说,剩余 30 吨 (延时这个时间里出了 10 吨水)

这个操作就是在有限的情况下,又尽可能的提高了一点传输速度


10、捎带应答(效率机制)

  • 延时应答的延伸
  • 在延迟应答的基础上,我们发现,很多情况下,客户端服务器在应用层也是 “一发一收” 的。意味着客户端给服务器说了 “How are you”,服务器也会给客户端回一个 “Fine, thank you”;
    那么这个时候 ACK 就可以搭顺风车,和服务器回应的 “Fine,thank you” 一起回给客户端

客户端和服务器之间的通信,有以下几种模型:
1、一问一答:客户端发一个请求,服务器返回一个对应的响应
2、多问一答:上传文件
3、一问多答:下载文件
4、多问多答:直播,串流…

用浏览器上网,打开网页,主要就是这种模型一问一答

在这里插入图片描述

因为延时应答的存在,导致 ACK 不一定是立即返回的!!
如果当前的延时应答,导致 ACK 的返回时机 和 应用代码中返回的响应时机 重合了!!! 就可以把这个 ACK 和响应数据,合二为一


11、其他特性:面向字节流

11.1、其他特性

其他特性:面向字节流
其他特性:缓冲区
其他特性:大小限制

创建一个TCP的socket,同时在内核中创建一个 发送缓冲区 和一个 接收缓冲区

  • 调用write时,数据会先写入发送缓冲区中;
  • 如果发送的字节数太长,会被拆分成多个TCP的数据包发出;
  • 如果发送的字节数太短,就会先在缓冲区里等待,等到缓冲区长度差不多了,或者其他合适的时机发送出去;
  • 接收数据的时候,数据也是从网卡驱动程序到达内核的接收缓冲区;
  • 然后应用程序可以调用read从接收缓冲区拿数据;
  • 另一方面,TCP的一个连接,既有发送缓冲区,也有接收缓冲区,那么对于这一个连接,既可以读数据,也可以写数据。这个概念叫做 全双工

由于缓冲区的存在,TCP程序的读和写不需要一一匹配,例如:

  • 写100个字节数据时,可以调用一次write写100个字节,也可以调用100次write,每次写一个字节;
  • 读100个字节数据时,也完全不需要考虑写的时候是怎么写的,既可以一次read 100个字节,也可以一次read一个字节,重复100次;

11.2、粘包问题

(不仅仅 TCР 存在粘包(nian / zhan),其他的面向字节流的机制,也存在,比如读文件)
TCP 粘包指的是,粘的是应用层数据报,在 TCР 接收缓冲区中,若干个应用层数据包混在一起了,分不出来谁是谁了

在这里插入图片描述

解决方案: 关键就是要在应用层协议这里,加入包之间的边界 (粘包问题,说是在 TCP 这里讲的实际上这是个应用层的问题)

例如,约定每个包以 ; 结尾
如果你是基于一些库 / 框架来完成网络通信,一般来说粘包问题已经被库 / 框架处理了,如果你是需要自己实现一些库 / 框架,直接使用了 TCP,就需要考虑到粘包了

  • 对于定长的包,保证每次都按固定大小读取即可;例如上面的Request结构,是固定大小的,那么就从缓冲区从头开始按sizeof(Request)依次读取即可;
  • 对于变长的包,可以在包头的位置,约定一个包总长度的字段,从而就知道了包的结束位置;
  • 对于变长的包,还可以在包和包之间使用明确的分隔符(应用层协议,是程序猿自己来定的,只要保证分隔符不和正文冲突即可);

思考: 对于UDP协议来说,是否也存在 “粘包问题” 呢?

  • 对于UDP,如果还没有上层交付数据,UDP的报文长度仍然在。同时,UDP是一个一个把数据交付给应用层。就有很明确的数据边界。
  • 站在应用层的站在应用层的角度,使用UDP的时候,要么收到完整的UDP报文,要么不收。不会出现"半个"的情况。

五、TCP 异常情况

1、进程终止

  • 在进程毫无防备的情况下,偷袭他,突然结束进程,这个时候该进程的 TCP 连接是咋样的??
  • TCP 连接,是通过 socket 来建立的,socket 本质上是进程打开的一个文件,文件其实就存在于进程的 PCB 里面,有个文件描述符表,每次打开一个文件 (包括 socket),都在文件描述符表里,增加一项
    每次关闭一个文件,都在文件描述符表里,进行删除一项
  • 如果直接杀死进程,PCB 也就没了,里面的文件描述符表也就没了,此处的文件相当于 '自动关闭’ 了,这个过程其实和手动调 socket.close() 一样,都会触发 4 次挥手,

2、机器关机

按照操作系统约定的正常流程关机
正常流程的关机,会让操作系统,杀死所有进程,然后再关机,还是一样


3、机器掉电 / 网线断开

台式机,直接拔电源,偷袭成功!!!

操作系统不会有任何反应时间,更不会有任何的处理措施

接收端认为连接还在,一旦接收端有写入操作,接收端发现连接已经不在了,就会进行 reset。即使没有写入操作,TCP自己也内置了一个保活定时器,会定期询问对方是否还在。如果对方不在,也会把连接释放

在这里插入图片描述

如果 B 断电
意味着 A 发送的数据不再有 ACK 了,A 进入超时重传逻辑
重传几次之后,A 认为这个连接已经出现严重故障了,尝试重新建立连接,重连失败之后就会放弃连接 (A 主动释放曾经和 B相关的连接信息)

在这里插入图片描述

如果 A 断电
B 就不知道当前是 A 挂了,还是 A 休息会再继续
B 就会时不时的给 A 发送一个小的报文 (探测报文,心跳包,不带有实际的数据,只是为了触发 ACK)
通过探测报文,发现 A 不再返回 ACK 了,因此 B 就认为 A 出现了问题

另外,应用层的某些协议,也有一些这样的检测机制。例如HTTP长连接中,也会定期检测对方的状态。例如QQ,在QQ断线之后,也会定期尝试重新连接


总结:

可靠性:

  • 校验和
    序列号(按序到达)
    确认应答
    超时重发
    连接管理
    流量控制
    拥塞控制

提高性能:

  • 滑动窗口
    快速重传
    延迟应答
    捎带应答

其他:

  • 定时器(超时重传定时器,保活定时器,TIME_WAIT定时器等)

基于TCP应用层协议

  • HTTP
    HTTPS
    SSH

  • Telnet
    FTP
    SMTP

当然,也包括你自己写TCP程序时自定义的应用层协议;

为什么TCP这么复杂?因为要保证可靠性,同时又尽可能的提高性能 ,可靠性的基础上,保证效率

  • TCP vs UDP 对比:
    • 什么时候使用TCP?对可靠性有一定要求,应用于文件传输,重要状态更新等场景; (日常开发中的大多数情况,都是基于 TCP)
    • 什么时候使用UDP?对可靠性要求不高,对高速传输和实时性要求较高的通信领域,例如,早期的QQ,视频传输等。另外
      UDP可以用于广播; (典型的例子,机房内部的主机之间通信,分布式系统中)
    • 归根结底,TCP和UDP都是程序员的工具,什么时机用,具体怎么用,还是要根据具体的需求场景去判定
  • 传输层的协议,只有 TCP 和 UDP 嘛?
    • 像当下常见的,LOL,Dota2,吃鸡,王者荣耀…这些对抗性很高的游戏,底层是使用 TCP 还是 UDP

**经典面试题:**基于 UDP 如何实现可靠传输??

  1. UDP 本身是无连接,不可靠,面向数据报的协议,如果要基于传输层 UDP 协议,来实现一个可靠传输,应该如何设计?

  2. UDP 大小是受限的,如果要基于传输层 UDP 协议,传输超过 64K 的数据,应该如何设计?

以上两个问题答案类似,都可以参考 TCP 的可靠性机制在应用层实现类似的逻辑:[其实在考 TCP]

抄作业!!! 本质上就是,在应用层基于 UDP 复刻 TCP 的机制

例如:

  • 引入序列号,保证数据顺序;
  • 引入确认应答,确保对端收到了数据;
  • 引入超时重传,如果隔一段时间没有应答,就重发数据;
    ……

六、网络层 —— IP 协议

1、IP 协议段格式

在复杂的网络环境中确定一个合适的路径。

完成两方面工作:

  1. 地址管理

  2. 路由选择

在这里插入图片描述

3)TOS说是8位,其实只有4位是有效的一

4 位版本号(version)

指定 IP 协议的版本,当前只有两个取值:4 和 6,0100,0110。对于 IPv4 来说,就是 4

4 位头部长度(header length)

IP 的报头和 TCP 类似,都是可变的,带有选项,4 位的取值范围 0 - 15,这里的单位也是 4 字节,如果取值是 1111 => 15,实际表示的首部长度就是 60 字节。IP 头部的长度是多少个32bit,也就是 length * 4 的字节数。4bit表示最大的数字是 15,因此 IP 头部最大长度是 60 字节

8 位服务类型(Type Of Service)

  • TOS 说是 8 位,其实只有 4 位是有效的。3 位优先权字段(已经弃用),4 位 TOS 字段,和 1 位保留字段(必须置为 0)。4 位 TOS 分别表示:最小延时,最大吞吐量,最高可靠性,最小成本 (同一时刻,只能取一种状态),这里的 TOS 相当于是切换形态 (类似于变身效果),

  • IP 协议能够规划处两点之间一条比较合适的路径,翻译翻译,啥叫合适??

  • 这四者相互冲突,只能选择一个。对于 ssh / telnet 这样的应用程序,最小延时比较重要;对于 ftp 这样的程序,最大吞吐量比较重要。

16 位总长度(total length)

  • IP数据报整体占多少个字节。
  • UDP好像也有类似的情况,16位 => 最大长度 64k
    因此,单个 IP 数据报最大长度确实不能超过 64k
  • 如果要构造一个更长的数据报 (比如搭载的载荷部分已经超过 64k了,咋办?)
    IР 协议自身实现了分包组包这样的操作

这三个字段就是用来进行分包和组包的:

网络原理 | TCP/IP中的连接管理机制 重要协议与核心机制

第二个没有 TCP 报头,对于 IP 数据包来说,根本不关心载荷里是啥,只是单纯的对数据进行了切分了,也不知道这里有没有 TCP 报头

如何区分这多个 IP 包,是从同一个数据拆分来的呢??

在这里插入图片描述

16 位标识(id)

唯一的标识主机发送的报文。如果 IP 报文在数据链路层被分片了,那么每一个片里面的这个 id 都是相同的。例如快递单号,即使是拆成多个包裹,多个包裹的快递单号也是一样的,多个被拆出的IР数据报,的16位标识的值是相同的

3 位标志字段

  • 3 位中只有一位是好使的,0 表示还有后续,1表示这是最后一个包了 (结束标记)

  • 第一位保留(保留的意思是现在不用,但是还没想好说不定以后要用到)。

  • 第二位置为 1 表示禁止分片,这时候如果报文长度超过MTU,IP模块就会丢弃报文。

  • 第三位表示 “更多分片”,如果分片了的话,最后一个分片置为1,其他是0。类似于一个结束标记。

13 位分片偏移(framegament offset)

  • 通过这个 13 位片偏移,来描述这多个包谁先谁后
  • 是分片相对于原始 IP 报文开始处的偏移。其实就是在表示当前分片在原报文中处在哪个位置。实际偏移的字节数是这个值 * 8 得到的。因此,除了最后一个报文之外,其他报文的长度必须是8的整数倍(否则报文就不连续了)。

8 位生存时间(Time To Live,TTL)

  • 数据报到达目的地的最大报文跳,表示一个 IP 数据报,在网络上还能存在多久,这里的单位不是 s 或者 ms,而是转发次数
    IP 数据报被发送的时候,会有一个初始的 TTL (比如常见的取值,128 或者 64),IP 数据报每次经过了一个路由器,TTL 就会 –1,如果 TTL 减到 0 了,此时收到这个包的路由器就会把这个包给丢弃,

  • 主要就是有些包里面的 IP 地址可能是永远也到不了的,像这样的包,不可能在网络上无休止的转发(占用硬件资源太多了),一直减到0还没到达,那么就丢弃了。这个字段主要是用来防止出现路由循环

  • 正常的 IP 数据报都会在既定的 TTL 内来到达

8 位协议

表示上层协议(传输层) 的类型,TCP 或者是 UDP 都有不同的取值

16 位头部校验和

也是使用 CRC 来校验数据是否正确,头部是否损坏。

网络原理 | TCP/IP中的连接管理机制 重要协议与核心机制

32 位源地址和 32 位目标地址

  • 表示发送端和接收端。源 IP 表示发件人地址,目的 IP 表示收件人地址
  • 对于 IPv4 来说,一个 IP 地址本质上是 32 位的整数,通常会使用 “点分十进制” 这样的方式来表示这个 IP 地址,
    三个点,把 32 位整数分成 4 个部分,每个部分 1 个字节,每个部分的取值就是 0-255,
    如:192.168.0.5
    给人看的 IP 通常就是点分十进制表示的,给机器存储的 IP,在底层仍然是按照 4 个字节整数来表示

选项字段(不定长,最多40字节)

略。


2、IP 地址

2.1、子网掩码

IP地址(Internet Protocol Address)是指互联网协议地址,又译为网际协议地址

IP地址是IP协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地
址,以此来屏蔽物理地址的差异

IP 地址是一个点分十进制构成的数据,咱们把 IP 地址分成了两个部分:网络号 + 主机号

  • 网络号:描述当前的网段信息(局域网的标识)

  • 主机号:区分了局域网内部的主机

  • 192.168.0.5
    网络号 主机号

要求,同一个局域网里主机之间的网络号是相同的主机号不能相同两个相邻的局域网 (同一个路由器连接的),网络号也是不同的

到底前多少个 bit 位是网络号?怎么规定的呢?固定 3 个字节嘛?? 其实是不固定的 !!!
引入了一个 “子网掩码" 这样的概念,来表示多少个 bit 位是网络号

网络原理 | TCP/IP中的连接管理机制 重要协议与核心机制

子网掩码:

  • 也是一个 32位,点分十进制 表示的整数
  • 子网掩码的左侧都是 1,右侧都是 0 (不会 1 0 混着排列)
  • 左边的这些 1 就表示哪些位是网络号,剩下 0 就是表示哪些位是主机号

255 => 1111 1111 1111 1111 1111 1111 0000 0000
IP 地址中的前 24 个 bit 位就是网络号,后 8 个 bit 位就是主机号

一般家用的场景中,一个局域网设备很少 (不会超过 255),常见的子网掩码就是2 55.255.255.0
如果一个局域网设备多了,子网掩码就会出现一些其他值

需要根据实际需要来确定网段如何划分,(每个设备的 IP 都是可配置的,包括子网掩码啥的,都是可以修改的)

负责配置网络的人,有一个专门的岗位:网管!!! 网络管理员,网管 != 网管儿
负责一些大型网络的维护的

—些特殊的 IP 地址:

  • 如果 IP 的主机号为全 0,该 IP 就表示网络号 (局域网里的一个正常的设备,主机号不能设为 0)
  • 如果 IP 的主机号为全 1 (255),该 IP 就表示 “广播地址”,往这个广播地址上发的消息,整个局域网中都能收到
  • IP 地址是 127 开头的,该 IP 都表示 “环回IP”,表示主机自己
    127.0.0.1 (环回IP中的典型代表)
  • IP地址是 10 开头,192.168 开头,172.16 - 172.31 开头,表示该 IP 地址是一个局域网内部的 IP (内网 IP),
  • 除此之外,剩下的 IP 称为 外网 IP (直接在广域网上使用的 IP)
    要求外网 IP 一定是唯一的,每个外网 IP 都会对应到唯一的一个设备,
  • 内网 IP 只是在当前局域网中是唯一的,不同的局域网里,可以有相同的内网 IP 的设备

内网IР是可以重复的,本来咱们预期 IP 地址应该就表示一个网络上的唯一位置,结果为什么同一个 IP 能表示不同设备了呢??

当前 IPv4 协议,使用的 IP 地址是 32 位的整数,32 位能表示的数据范围
42 亿 9 千万
如果给每个设备都分配一个唯一的 IP 地址,意味着世界上的设备就不能超过 42 亿 9 千万,随着网络的发展,现在世界上的设备越来越多,已经超过了 42 亿 9 千万,让每个设备都有唯一的 IP 地址不现实了 (现在尤其是移动互联网的兴起),如何解决这个问题?


2.2、解决 IPv4 不够用:

方法一:动态分配 IP 地址:

让每个设备连上网的时候,才有IP,不联网的时候就没 IP (这个 IP 就可以给别人用)
但是这个方案不能从根本上解决问题 (设备没有减少,IP 地址也没有增加…) 治标不治本

方法二:NAT 机制

  • 让多个设备共用同一个 IP (外网 IP) [当前网络环境]
  • 把网络分成了内网(局域网) 和 外网(广域网),要求外网 IP 必须表示唯一的设备,同时内网中的若干个设备,可以共用同一个外网 IP !!!
  • 这个时候每个外网 IP 都可能表示着几千个,甚至上万个设备,这个时候 IP 地址的压力就缓解了很多了

运营商的路由器这里就会修改 IP 数据报,从内网发出去的源 IP 改成外网 IP 带有这样功能的路由器设备,也称为 NAT 设备,

NAT 机制,把 IP 分成了内网和外网,也就隐含了一个重要的结论:
对于一个外网 IP,可以在互联网的任意位置都能访问到
对于一个内网 IP,只能在当前局域网内部访问,局域网 1 的设备,不能使用 内网 IP (内网 IP 可以重复出现,只有在当前局域网内才是唯一的) 访问 局域网 2 的设备

在这里插入图片描述

例如:192.168.0.5,
这个 IP 只有在我自己家里的局域网中才能访问,别人想跨越局域网来访问我的这个 IP 这个是做不到的!!!
如果咱们之间要想通信咋办? 就需要有一个带有外网 IP 的机器,在这里其实就是 cctalk 服务器
cctalk 服务器是有 外网 IP,咱们都能访问到 cctalk,我这边的机器,访问了 cctalk,把我的电脑的画面推送到 cctalk 服务器上,有人也问了 cctalk,从 cctalk 上拿我的直播画面
如果没有 NAT (如果所有的 IP 都是唯一的),就可以做到,不通过 cctalk,我直接在我自己的电脑上架设一个直播服务器,大家直接访问我的 IP 就行了

在这里插入图片描述

NAT 也是存在极限的,端口号的个数 65535,如果一个局域网里的连接超过了 65535,这个时候,NAT 就不一定能好使了,端口号就不够用了,NAT 只是续命了一波,但是不是从根本上解决问题

方法三:lPv6

IPv6 在报头中使用了一个更长的字段,来表示 IP 地址,16个字节,128 位,2 * 128
看起来好像是 16 个字节比 4 个字节,大了 4 倍,其实不是,而是大了很多很多
4 个字节,32 位:2 ^ 32 * 2 ^ 32 * 2 ^ 32 * 2 ^ 32
号称 IPv6 可以给地球上的每个沙子都分配一个 IP 地址,lPv6 是真正从根本上解决了 IP 地址不够用的问题!!!

网络原理 | TCP/IP中的连接管理机制 重要协议与核心机制

每个数字都是一个十六进制的数字(4bit),每个冒号分割了 2 个字节

既然 IPv6 非常好,为什么现在还是在用 IPv4 + NAT 呢?

这里最大的问题在于,IPv6 和 IPv4 是不兼容的!!!
对于一个设备来说,支持 IPv4 和 IPv6 需要两个截然不同的机制,现有的大量的网络设备(路由器…)很可能都是只支持 IPv4,不支持 IPv6,因此,要想升级到 IPv6,就得更换设备,换设备就得加钱!!!

中美贸易战,美国针对中国各个方面都进行了封锁,IPv4 的外网 IP 的分配权限就是在美手里的,一旦要是再 IP 分配上动手脚,就会对中国互联网发展产生很大的影响

IP 地址网段划分,是通过子网掩码的方式划分的…
在历史上(有子网掩码之前)
是简单粗暴的通过 “分类” 的方式来划分的,把 IP 地址分成了 A,B,C,D,E
这五类,每一类,分别都有几位是网络号,几位是主机号…(淹没在历史长河中了)


3、路由选择

所谓路由,即在复杂的网络结构中,找出一条通往终点的路线
网络通信(网络数据传输),路由器中的路由功能,就类似于规划路线,往哪个方向行进能更快到达目的地。
两个设备之间,要找出一条通道,能够完成传输的过程,要想找出通道,前提是,得先认识路

例如我要去某个地方,只知道大致的方向,于是先乘坐 128 路公交车,到站后问路,问了一个人,他也不知道具体路线,但知道可以 33 路到终点站,接下来我就继续问了一个人,直到目的地在哪里,于是就找到了

IP 协议的路由选择也是类似的,IP 数据报中的目的地址,就表示了这个包要发到哪里去
这个目的地址,如果当前路由器直接认识,就直接告诉你路了,如果当前路由器不认识,,会告诉你一个大概的方向,让你走到下一个路由器的时候再来问问
依次往后走,其实也是在离目标越来越近
这个时候就总会遇到一个认识这个地址的路由器,于是就可以具体的转发过去了
有的时候,不光遇到了一个认识这个地址的路由器,并且它还认识多个路,就可以选一个更合适的路了

什么叫路由器 “认识” 这个IP地址:

  • 在路由器内部维护了一个数据结构,路由表

  • 路由表里面就记录了一些网段信息(网络号),(目的 IP 就再这些网络号中匹配),以及每个网络号对应的网络接口 (网辂接口其实就对应到路由器里面具体的端口)

路由表内部的结构不复杂,复杂的是路由表是咋来的,(有一系列专门的路由表生成算法,自动生成一波,还可以手动配置)

具体实现细节复杂,思想上还比较清晰:
某个路由器上线(接入网络之后),就会和相邻的设备进行通信,“认识一下”,在这个认识的过程中,路由器就来构造这个路由表,这个认识不一定就是紧挨着的,路由器是可能会认识到更多的 “朋友”

路由表中的一个默认表项起到的效果,当 IP 地址在路由表的所有表项中都匹配不上的时候,此时就会走这个下一跳


七、数据链路层

1、以太网

“以太网” 不是一种具体的网络,而是一种技术标准;既包含了数据链路层的内容,也包含了一些物理层的内容。例如:规定了网络拓扑结构,访问控制方式,传输速率等;

例如以太网中的网线必须使用双绞线;传输速率有10M,100M,1000M等;
以太网是当前应用最广泛的局域网技术;和以太网并列的还有令牌环网,无线LAN等;

数据链路层主要的协议,叫做 “以太网”。像平时咱们插的网线,就叫做 “以太网线”

以太本来是物理学上的概念
之前大家认为光也是一种波,必须要依靠介质才能传播(类似于声波)真空中,光也能传播
于是就脑补出了一个介质:以太
迈克尔逊-莫雷实验,证明了光速在各个方向上,都是一致,推翻了以太 => 狭义相对论

以太网这个协议不仅仅规定了数据链路层的内容,也规定了物理层的内容


2、以太网帧格式

在这里插入图片描述

通过 6个 字节 来表示源地址和目的地址,这个就要比 IPv4 更长,长了 6w 多倍,此处的源地址 / 目的地址,使用 mac 地址 / 物理地址

mac 地址做到了每个设备都是唯一的 (每个网卡都是唯一的),是在网卡出厂的时候就写死了

例如,以太网网卡的物理地址:
物理地址… … … … . . : 00-EO-4C-8D-46-E6
蓝牙网卡的物理地址~
物理地址… . . … . . . …: 74-4C-A1-76-26-D4

物理地址,也不是完全不能改,有些网卡是支持手动配置的

已经有 IP 地址了为什么还要物理地址??
这是一个美好的误会!!!
当年网络层协议和数据链路层协议,是各自独立研发出来的!!!
mac 地址 和 IP 地址,就有点重复了 (按理来说,一套地址就够了)

现在的现状,就是当前 mac 地址 和 IP 地址同时使用,表示不同的功能,

  • IP 用来表示一次传输过程中的起点和终点 (不考虑 NAT 的情况,一个 IP 数据报中的源 IP 和 目的 IP 是固定的)
  • mac 用来表示传输过程中,任意两个相邻节点之间的地址 (一个以太网数据帧,在每次转发过程中,源 mac 和 目的 mac 都会改变)

类型:

  • 0800:一般情况,完整的 IP 数据报
  • 0806,8035:特殊的情况

CRC:

帧尾就是一个基于 CRC 算法的校验和,循环冗余算法:把数据的每个字节依次讲行累加 (溢出就溢出,没关系)

46-1500:

  • MTU. 一个以太网数据帧能够承载的数据范围,这个范围取决于硬件设备
  • 以太网是和硬件也密切相关的。但是其他的硬件设备,对应的数据链路层协议,可能又不一样,MTU 也不相同
  • 数据链路层考虑的是,相邻节点之间的数据传输
    考虑这个细节的时候就需要关注到交通工具是啥,不同的交通工具,能够搭载的数据量就不太相同
  • 如果数据报超过了 MTU 怎么办?
    IP 层能够分包,IP 层的分包其实不是给 IP 的报头 64k 准备的…更多的是为了适应数据链路层的 MTU

3、MTU

MTU 相当于发快递时对包裹尺寸的限制。这个限制是不同的数据链路对应的物理层,产生的限制

  • 以太网帧中的数据长度规定最小46字节,最大1500字节,ARP数据包的长度不够46字节,要在后面补填充位;
  • 最大值1500称为以太网的最大传输单元(MTU),不同的网络类型有不同的MTU;
  • 如果一个数据包从以太网路由到拨号链路上,数据包长度大于拨号链路的MTU了,则需要对数据包进行分(fragmentation);
  • 不同的数据链路层标准的MTU是不同的

MTU对IP协议的影响:

  • 由于数据链路层MTU的限制,对于较大的IP数据包要进行分包
  • 将较大的IP包分成多个小包,并给每个小包打上标签;
  • 每个小包IP协议头的 16位标识(id) 都是相同的;
  • 每个小包的IP协议头的3位标志字段中,第2位置为0,表示允许分片,第3位来表示结束标记(当前是否是最后一个小包,是的话置为1,否则置为0);
  • 到达对端时再将这些小包,会按顺序重组,拼装到一起返回给传输层;
  • 一旦这些小包中任意一个小包丢失,接收端的重组就会失败。但是IP层不会负责重新传输数据;

MTU对UDP协议的影响:

  • 一旦 UDP 携带的数据超过1472(1500 - 20(IP首部) - 8(UDP首部)),那么就会在网络层分成多个IP数据报。
  • 这多个IP数据报有任意一个丢失,都会引起接收端网络层重组失败。那么这就意味着,如果UDP数据报在网络层被分片,整个数据被丢失的概率就大大增加了

MTU对于TCP协议的影响:

  • TCP 的一个数据报也不能无限大,还是受制于MTU。TCP的单个数据报的最大消息长度,称为MSS(Max Segment Size);
  • TCP在建立连接的过程中,通信双方会进行 MSS 协商。
    • MSS:TCP 中在 IР 不分包的前提下,做多搭载多少载荷
      MTU 也取决于 TCP 和 IP 的报头(报头都是变长的)
  • 最理想的情况下,MSS的值正好是在IP不会被分片处理的最大长度(这个长度仍然是受制于数据链路层的MTU)。
  • 双方在发送SYN的时候会在TCP头部写入自己能支持的MSS值。
  • 然后双方得知对方的MSS值之后,选择较小的作为最终MSS。
  • MSS 的值就是在TCP首部的40字节变长选项中(kind=2);

MSS和MTU的关系:

分包的开销还是有的,MSS 对于 TCP 起到一定的提示效果,当当前传输 TCP 的数据长度不超过 MSS 的时候,是属于最高效的状态

在这里插入图片描述


4、ARP 协议

虽然我们在这里介绍ARP协议,但是需要强调,ARP不是一个单纯的数据链路层的协议,而是一个介于数据链路层和网络层之间的协议,不是用来传输数据的,只是起到一个辅助的效果

路由器这样的设备在转发数据的时候,首先拿到的是一个 IP 地址(目的 IP),通过 IP 地址来决定接下来这个数据如何走 (从哪个端口出去,发到哪个设备上),因此就得决定,接下来封装的以太网数据帧,目的 mac 是什么。

需要根据 ARP 协议,建立起 IP -> mac 这样的映射关系 (类似于 hash 表这样的键值对结构)

但是这个表不一定是在内存里,也可能是直接在硬件中存储,
当设备启动的时候,就会向局域网中,广播 ARP 报文,每个设备收到之后,都会给出一个应答
应答的信息中就包含了自己的 IPmac
发起广播的那一方,就可以根据这些回应,建立起这个映射表

例如:我想统计每个同学的邮箱,我就群里喊一声,都把自己的名字和邮箱,私信给我,大家收到这个广播消息,就纷纷私信,然后我把这个得到的结果,整理到一个表格上


八、应用层协议补充

  • DNS 是一个应用层协议
  • DNS,即Domain Name System,域名系统。DNS是一整套从域名映射到IP的系统。
  • TCP / IP 中使用 IP 地址来确定网络上的一台主机,但是 IP 地址不方便记忆,且不能表达地址组织信息,即使写成了点分十进制,于是人们发明了域名,并通过域名系统来映射 域名和 IP 地址
  • 域名 和 IP 地址之间,是一个一一对应的关系

域名是一个字符串,如 www.baidu.com

域名系统为一个树形结构的系统,包含多个根节点。其中:

  1. 根节点即为根域名服务器,最早 IPv4 的根域名服务器全球只有13台,IPv6 在此基础上扩充了数量。

  2. 子节点主要由各级 DNS 服务器,或 DNS 缓存构成。

    • DNS域名服务器,即提供域名转换为IP地址的服务器。

    • 浏览器、主机系统、路由器中都保存有DNS缓存。

    • DNS系统,最开始的时候,只是一个普通的文件,称为 hosts 文件

      Windows 系统的 DNS 缓存在 C:\Windows\System32\drivers\etc\hosts 文件中,
      Mac/Linux 系统的 DNS 缓存在 /etc/hosts 文件中。

      这个是最早的域名解析系统:

网络原理 | TCP/IP中的连接管理机制 重要协议与核心机制

140.82.114.3 github.com:每一行,就描述了 IP 和 域名 之间的关系,后续再其他程序(浏览器)中使用这个域名,就会被自动转换成 IP 地址

现在,hosts 文件已经不再使用了(虽然仍然是好使的),全世界的域名那么多,总不能天天在这对这个 hosts 改来改去,

干脆,就专门成立一个机构,负责维护这里的 域名 和 lP 的应对关系,比如你要申请一个域名,就去这个机构报备即可

这个机构维护一组服务器,把 hosts 文件存到这个服务器里域名解析服务器 —— 根 域名解析服务器
你自己的电脑想要进行域名解析,就访问一下这个人家的服务器就行了

全世界的设备这么多,每个设备上网,都要去访问这个域名解析服务器嘛? 域名解析服务器不会压力很大嘛??
全世界各个地方的国家,地区,城市,有各自的网络运营商,这些网络运营商会就近架设 域名解析 镜像 服务器
咱们平时上网的时候,一般就是就近访问了咱们的镜像服务器

日常生活中就会遇到一种场景:QQ 能上,但是网页打不开,是直接写死的 IР 地址来访问 QQ 的服务器,不涉及域名解析

在这里插入图片描述

勾选了自动获取,其实就是从运营商这里获取 (也就是运营商的域名解析服务器),手动改成其他的域名解析服务器就可以了

当我们的主机,查询了一次 DNS 之后,主机就会把这个查询结果缓存一定的时间 (浏览器来进行缓存)
下次再访问到同一个域名的时候,就可以省略查询 DNS 的过程了,因为 DNS 域名 和 IP 的对应关系,是很少变化的