网络原理md

网络原理

网络初始 , 是对于网络有一个直观的大体的认识

网络编程 , 让我们真正通过代码感受网络通信程序

网络原理 , 进一步的理解网络是如何工作的(理论为主)

应用层

​ 应用层和代码直接相关一层 , 决定了数据要传输什么 , 拿到数据之后如何使用

​ 应用层这里 , 虽然存在一些现有的协议 (HTTP) , 但是也有很多情况 , 需要程序员自定制协议

比如QQ 发送消息 , 构成一个应用层的数据报(举例子)
在这里插入图片描述

约定应用层数据报 , 数据格式 , 就是在自定义协议

但是 , 如何约定呢?

  1. 确定要传输哪些信息 (根据需求走的)

    比如叫外卖

    请求 : 用户id 用户位置

    响应 : 商家信息 : 商家名字 , 商家位置 , 类型

  2. 确定数据按照啥样的格式来组织 ( 随意约定的 )

    ​ 网络上传输的 , 本质上都是 0101 , 视为二进制字符串 , 需要把上述这些信息整合成一个字符串

    一个简单的方案 , 直接基于分隔符

    请求 : 用户 id , 用户位置;

    响应 : 商家名字 , 商家位置 , 类型\n

    ​ 商家名字 , 商家位置 , 类型\n

    ​ 商家名字 , 商家位置 , 类型;

    可以使用任意符号作为分隔符 , 只要分隔符不会在正文中出现即可

    属性之间使用分隔 , 整个请求以 ; 结尾

    属性之间使用 , 分隔

    每个商家使用 \n 分隔

    最终结束使用 ; 标识

    只要发送方按照这套格式来组织数据

    接收方按照这套格式来解析数据 , 两者能对上 , 这样的格式就是可行的

    实际开发中 , 有一些现成的格式 , 可以直接使用的

    一种典型的格式 : XML

传输层

UDP

UDP的报文格式

在这里插入图片描述

2字节表示的范围 0 -> 65535 =>64KB

一个UDP 报文最大长度 , 就是64KB

万一需要传输一个比较大的数据 , 怎么办?

1.可以把一个大的数据拆分成多个部分 , 使用多个 UDP 数据报来传输 , 虽然可行 , 但是比较复杂

2.不用UDP , 直接使用TCP , TCP没有限制

注意: 使用 udp 编程的时候要注意 udp 数据报不能太长, 否则会有问题

校验和

网络传输并非那么稳定 , 可能会出现问题

通过网线传输 , 电信号 , 电信号使用高低电平 表示 01

但是 , 如果外部环境干扰 , 强磁场之类的 , 就可能导致低电平 -> 高电平 高电平 -> 低电平

校验和存在的意义 , 就是用来判定一下 , 当前传输的数据是否出现出错

如果校验和不对 , 此时你的数据一定不对

如果校验和对 , 但是数据也有一定的概率是错的

为了让校验和能够识别率更高一点 , 计算的识货通常会以数据内容作为参数来进行计算 , 数据内筒发生改变 , 校验和也会改变

校验和往往就是去内容 / 内容的一部分 , 通过一些算数运算 , 数学公式变换 , 得到一个数值 , 如果内容发生改变 , 得到的校验和也就发生了改变

发送方 , 把载荷数据带入到校验和算法中, 计算生成得到的校验和结果 , 设置为sum1

在这里插入图片描述

发送方就把这一整串数据发送给接收方了

接收方收到的数据 , 既有载荷 , 也有校验和sum1

接收方就可以把载荷按照同样的算法 , 再计算一遍校验和 , 得到了sum2

对比 sum1 和 sum2 是否相同 , 如果不相同 , 数据一定出问题了

前提 : 输入的内容一样 , 按照一个算法得到的校验和 , 结果也是一样的

TCP

  • 有连接

    Socket clientSocket = serverSocket.accept();
    socket = new Socket(serverIp, port);
    
  • 可靠传输

    TCP存在的初心 , 最核心的机制

  • 面向字节流

  • 全双工

    try (InputStream inputStream = clientSocket.getInputStream();
        OutputStream outputStream = clientSocket.getOutputStream()) {
        
    }
    
确认应答机制

实现可靠性的最核心机制

举一个生活中的小例子~~

​ 你和妈妈在家 , 你想让妈妈做饭吃

后发先至情况:

​ 先收到了 “自己做去” , 后收到 “好啊好啊”

​ 为了解决上述问题 , 就需要针对信息进行编号 , 给发送的消息分配一个 “序号” , 同时应答报文 , “给出确认序号”

​ 真实的 TCP 数据传输也是引入了 序号 和 确认序号

​ TCP 是针对每个字节都去编号 , 从前往后 , 把每个字节分别分配一个编号

注意:

确认序号的规则:

不是发送方发过来的序号是啥 , 确认序号就是啥

而是取的是发送方发过来的所有数据 , 最后一个字节的下一个字节的序号

确认序号 1001 的含义:

1.小于1001 的数据 , 我已经收到

2.我接下来想向发送方索要从 1001 开始的数据

接收方就可以通过 ack 的确认序号 , 告诉发送方哪些数据已经收到了

TCP 会有一个接收缓存区 (一块内核中的内存空间 )

每个socket 都有一份自己的缓冲区

如果一切顺利 , 就可以直接确认应答了 , 可靠性自然得到了支持

超时重传

为什么会丢包 :

如果中间任何一个节点 , 出现了问题 , 都可能导致丢包

每个设备 , 都是在承担很多的转发任务的 , 且转发能力都是有上限的

某一个时刻 , 某个设备 , 上面的流量达到峰值 , 就可能会引起部分数据被丢包

如果包丢了 , 接收方就收不到了 , 自然就不会返回 ack

发送方就迟迟拿不到应答报文 , 等待一段时间之后 , 还是没有收到 应答报文 , 发送方就视为刚才的数据丢包了 , 就会重新发一遍

发送方对于丢包的判定 , 是一定时间内 , 没有收到 ack

1.数据直接丢了 , 接收方没有收到 , 自然不会发ack

2.接收方收到数据了 , 返回的 ack 丢了

发送方式区分不了这两种情况 , 只能都重传

这种情况下 , 主机B会收到重复的数据 , TCP 已经处理好此类问题 , 会在接收方缓冲区中根据收到的数据的序号 , 自动去重 , 保证了应用程序读到的数据仍然只有一份 .

那重传后的数据有没有可能又丢了呢?

​ 也是有可能的 , 一旦出现连续丢包 , 很可能网络出现了问题

``TCP针对于多个包丢失 , 处理的思路是 , 继续超时重传`

​ 但是每丢包一次 , 超时等待时间都会变长 , (重传的频率降低了) , TCP觉得重传也没有用处(网络出现大问题)

​ 连续多次重传 , 都无法得到 ACK , 此时TCP就会尝试重置连接(相当于充实重连) , 如果重置连接也失效 , TCP 就会关闭连接 , 放弃网络通信了)

一切顺利 ,使用确认应答保证可靠性

出现丢包 , 使用超时重传作为补充 ~~~这两个机制 是TCP 可靠性的基石

连接管理
TCP建立连接 : 三次握手

​ 握手 指的是通信双方 , 进行一次网络交互 , 相当于 客户端 , 服务器 之间 , 通过三次交互 , 建立了连接关系

​ 上述过程内核自动完成, 应用程序干预不了 , 等到连接完成了 , 服务器 accept 把建立好的连接从内核中拿到应用程序中

syn : 称为 同步报文段

​ 意思就是一方要向另一方 , 申请建立连接

什么叫做 syn 报文(观察TCP 报头结构)

这 6 个特殊的比特位 , 这几位默认是 0

如果设为 1 , 则表示特定含义 .

其中第二位 , 是ACK , 如果这一位是1 , 表示当前TCP 数据报是一个应答报文

其中第五位, 是SYN , 如果这一位是1 ,表示当前TCP数据报是一个同步报文

如果一个TCP 数据报, 第二位和第五位都是1 , 则当前这个报文 是 SYN + ACK

​ **三次握手这个过程 , 本质上是投石问路 , 验证了客户端 和 服务器 , 各自发送能力和接收能力是否正常.**确认了客户端和服务器 各自的发送能力和接收能力 都正常 , 这就是后续可靠传输的基础

两次握手行不行 ?

不行 , 服务器不能确定客户端是否能接收到消息

四次握手行不行 ?

把中间的 syn 和 ack 拆开分别发送 , 可以但没必要

分成两次发送 , 效率不如合并成一次

TCP断开连接 : 四次挥手

通信双方 , 各自给对方发送一个 FIN(结束报文) , 再各自给对方返回ACK

建立连接 , 一定是客户端主动先发起

断开连接 , 客户端和服务器都有可能先发起

ACK 和 FIN 能合并吗

有一定概率合并成一个 , 但是通常情况下 , 不能合并的

为啥什么三次握手 的syn和ack能合并, 而四次挥手不能?

三次握手 : ack 和 syn 是同一个时机触发的 (都是内核来完成的)

四次挥手 : ack 和 fin 则是不同时机触发的

​ ack 是内核完成的 ,会在收到 fin 的时候第一时间返回

​ fin 则是应用程序代码控制的 , 在 调用到 socket 的 close 方法的时候才会触发fin

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

滑动窗口

提高可靠性 , 往往意味着损失效率

在这里插入图片描述

此时 主机A 这边就花费了大量的时间在等待ACK , 想要提高效率 , 就需要缩短等待时间 , 批量发送数据 , 一次发多条数据 , 一次等多个ack

​ 这里就是批量发送 4 条数据 , 发完之后 , 同一等待 ack , 每次收到一个 ack 就立即发下一条 , 使用一份时间 , 等待多个 ack , 总的等待时间缩短了 , 整体的效率就提升了

上述批量传输数据的过程, 称为滑动窗口

​ 批量不是无限发送 , 是发送到一定程度 , 就等待 ack , 不等待直接发送的数据量是有限的 , 每回来一个 ack 就立即发送下一条 , 相当于总的要批量等待的数据是一致的. (把批量等待数据的数量 , 就称为 “窗口大小”)

批量发送了四条数据 , 就等待着四个ack , 白色区域 , 就相当于等待的窗口

当收到 2001 这个 ack 意味着 1001-2000 这个数据得到了确认 , 此时就会立即发下一个5001-6000这个数据

此时看到的效果 , 就好像窗口还是这么大 , 但是往后挪了一个格子 , 如果收到 的 ack 非常快 , 此时这个窗口就在快速的往后滑动

  • 批量发送的过程中 , 如果出现丢包怎么办?

这个图中 , 相当于一半的 ack 都丢了 , 相当高的丢包率

注意 : 这种情况对于可靠性没有任何影响

​ 确认 序号的含义 , 表示 , 该序列号之前的数据都已经收到了 , 后一个 ack 能够涵盖前一个 ack 的意思

当收到2001 这个 ack 的时候 , 此时发送方就知道了 , 2001 之前的数据都收到了 ,1-1000 这个数据也收到了 , 1001 这个 ack 丢了就丢了 , 不影响

如果是最后一个丢了 , 照常超时重传 ,滑动窗口是锦上添花

这就是确认序列号的目的

由于1001-2000 这个数据丢了 , 所以接收方仍然继续索要1001 , 不会因为收到的是2001-3000 就返回3001

接下来几次的数据的 ack ,确认序号都是 1001 , 主机B 再向主机A反复索要 1001这个数据 , 主机A 这边连续收到几个 1001 之后 ,就知道 需要重传 1001-2000这个数据

当主机A把 1001-2000 这个数据重传 , 主机 B收到之后 , 返回的 ack 确认序号是7001 , 而不是2001 , 因为 2001-7000 这些数据 , 主机B都已经都到过了

​ 上述重传过程中 , 没有任何冗余的操作 , 丢了数据才会重传 , 不丢数据不必重传 , 整体速度是比较快的 , 这个重传过程也称为快速重传

​ 滑动窗口 , 快速重传 , 是在批量传输大量数据的时候 , 会采取的措施 , 如果你就只传输一条两条 , 少量的,低频的操作 , 就不会按滑动窗口这么执行, 仍然是前面的确认应答和超时重传

流量控制

也是保证可靠性的机制

滑动窗口 , 批量发送 , 窗口越大, 相当于批量的数据越多 , 整体的速度就越快

如果发送的太快 , 瞬间把接收方的接收缓冲区给打满了 , 接下来继续发送 , 此时数据就会丢包 ,这种情况得不偿失

通过流量控制 , 本质上就是让接收方 , 来限制一下发送方的速度

具体是如何控制的呢?

让ack 报文中 ,携带一个 “窗口大小” 这样的字段

​ 当 ack 为 1 的时候 , ack 报文此时窗口大小字段就会生效 , 这里的值就是建议发送方的窗口大小 . 接收方直接拿接收缓存区的剩余空间作为窗口大小

拥塞控制

滑动窗口的大小取决于流量控制(衡量了接收方的处理能力)和拥塞控制(衡量了传输路径的处理能力)

例如 :主机A和主机B进行通信

很明显 , 传输路径上 , 任何一个设备 , 处理能力如果遇到瓶颈都会对整体的传输速率产生明显的影响.

拥塞控制 做的事情 , 就是衡量中间节点 传输的能力

(拥塞控制 , 是要衡量中间路径 . 中间路径上有多少个节点 , 每个节点当前的情况 , 每次传输所有的路径不同)

通过实验的方式 , 找到一个合适的发送速率

开始的时候 , 按照一个小的速率发送 ,

如果不丢包 , 就可以提高一下速率(扩大窗口的大小)

如果出现丢包 , 则立即把速率调小

重复上述过程

有两个窗口 : 拥塞窗口(拥塞控制试验出来的窗口) , 流量控制的窗口

实际发送方的窗口大小 = min(拥塞窗口 , 流控窗口)

延迟应答

提高传输效率!!!

TCP 可靠性的核心是确认应答 .

ACK 要发 , 但是不是立即发 , 而是稍微摩擦一会再发

TCP 中决定传输效率的关键元素就是 窗口大小

延迟应答的效果 , 就是通过这个延迟 , 让接收方应用程序 , 趁机多消费点数据 , 此时反馈的 窗口大小 , 就会更大一丢丢 , 此时发送方的发送速率也就能快一点 , (同时也能满足让接收方能够处理过来)`

捎带应答

基于延迟应答

客户端服务器 通信模式:

  • 一问一答 . 绝大部分服务器都是这样的
  • 多问一答 . 上传大文件
  • 一问多答 . 下载大文件
  • 多问多答 . 游戏串流

客户端服务器 , 之间的通信模式 , 通常是 “一问一答” 这种模式的

为什么四次挥手 , 有可能是三次挥完 . 捎带应答起到的效果 . 因为有可能 ack延迟应答 , 和fin同时了

面向字节流

举个例子 粘包问题

主机A的视角 : 主机B说的话从哪里到哪里

主机B的视角 : 主机A说的话从哪里到哪里

粘包问题:

​ 所谓 “一句话” 就相当于一个 “应用层数据报”

​ 当 A 给 B连续发送了多个应用层数据报之后 , 这些数据就都积累到 B 的接受缓冲区中 , 仅仅挨在一起 , 此时 B 的应用程序在读数据的时候 , 就难以区分从哪到哪是一个完整的应用层数据报 , 就很容易读出 半个包 / 一个半包

解决上述问题有两个方案 :

1.定义分隔符 , 粘包问题有效解决方案

2.约定长度 , 约定数据的前4 个字节 , 表示整个数据报的长度

这两个都是 自定义应用层协议 的注意事项 !(像 xml , json ,本质上都是通过分隔符 的方式来实现)

异常情况

1.进程关闭 / 进程崩溃

进程没了 , socket 是文件 , 随之被关闭 , 虽然进程没了 , 但是连接还在 , 仍然可以继续四次挥手

2.主机关机 (正常流程关机)

先杀死所有的用户进程

也会触发四次挥手 , 如果挥完 , 更好. 如果没挥完 , 比如 , 对方发的 fin 过来了 , 咱们没来得及 ack 就关机了 , 此时对端就会重传 fin , 重传几次后 , 发现没有 ack , 尝试重置连接 , 如果还不行 , 就直接释放连接

3.主机掉电 (拔电源)

瞬间主机关机 , 来不及进行任何挥手操作

  1. 对端是发送方 : 对端就会收不到 ack => 超时重传 =>重置连接=>释放连接

2)对端是接收方: 对端是没法立即知道 , 你这边还没来得及发新的数据 , 还是直接没了 , TCP 内置了 心跳包 , 保活机制

心跳包

a)周期性

b)如果心跳没了 , 挂了

虽然对端是接收方 , 对端会定期给咱们发一个心跳包 (ping) , 咱们返回一个 (pong)

如果每个 ping 都有及时的 pong , 这个时候说明当前对端的状态良好 , 如果 ping 过去之后 , 没用pong , 说明心跳没了

4.网线断开

同上

TCP小结

TCP 和 UDP 应用场景的差别:

TCP 可靠传输 , 效率没那么高 , 绝大部分场景下 , 都可以使用 TCP

UDP不可靠传输 , 效率高 , 对于效率要求较高 , 对于可靠性不高的情况下

网络层

地址管理

每个网络上的设备 , 要能分配一个地址 (唯一)

IP 地址 , 本质上就是一个 32位 的整数

通常 , 会把 32 位 的整数 , 转换成点分十进制的表达方式 , 三个点 , 把这个整数分成 4 个部分 , 每个部分 , 一个字节 , 每个部分的取值范围 0-255

32位 的整数 , 最多能表示多少个不同的地址 ? 42亿9千万(不够用)

如何解决上述问题 ?

1.动态分配IP 地址 : 设备上网才分配 , 不上网就不分配 , 此时就可以省下一大批IP地址 , 但是并没有增加 IP的数量 , 只能一定程度的缓解 , 不能彻底解决问题

2.NAT 机制

把所有的IP地址分成两大类

内网IP : 10.* 172.16.* -172.31.* 192.168.*

外网IP : 剩下的IP

外网 IP 必须是唯一的

内网 IP 则可以重复出现 ,(尤其是在不同的局域网中)

内网设备如果要访问外网 , 会给他分配一个外网IP

但是这个外网IP 不是这个设备独占的 , 而是这个内网中所有的设备都公用这一个外网IP了

很明显 , 百度的响应是不同的

怎么确定不同响应返回给主机是对应的?: 路由器会处理

3.IPV6

IPV4 是传统的 IP 协议 , 使用 4 个字节 , 32 位来表示 ip地址 2`32

IPV6 是更新一些的 IP 协议 , 使用16 个字节 , 128 位来表示 ip 地址 2`128 (地球上的没一粒沙子分配一个ip都是够用的)

IPV6 和 IPV4 并不兼容

一个普通的 IPV4 路由器 , 要想支持 NAT , 软件上升级下系统就行了

一个普通的 IPV4 路由器 , 要想支持 IPV6 , 光升级软件没有用 , 还得升级硬件

IP 地址的组成:

IP 地址分为两个部分 , 网络号和主机号

  • 网络号 : 标识网络 . 保证相互连接的两个网段具有不同的标识 (表示一个局域网)
  • 主机号 : 标识主机 . 同一网段内 , 主机之间具有相同的网络号 , 但是必须要有不同的主机号(标识一个局域网内部的主机)

对于网络号主机号的划分 , 主要有两种分类方式 :

1.IP地址分类 (ABCDE)

前缀用来区分类别 . 每个类别下 , 网络号和主机号长度都是固定的

2.子网掩码

特殊的 IP 地址 :

主机号为 0 的 ip : 192.168.0.0 就是网络号 , 局域网里不应该存在某个主机 , 主机号是0

主机号全为1 : 192.168.0.255 广播地址 , 往这个地址上发送 udp 数据报 , 此时这个数据报就会被转发给整个局域网中的所有主机

IP 为 127 开头的 : 127.* 称为环回IP 环回IP 对应特殊的虚拟网卡 lo. 通过环回 ip 传输的数据 , 走这个虚拟网卡 (这个过程没有 IO 操作的 , 纯内存操作的) , 要比一般的这种普通ip 的数据传输要快

主机号为1 : 192.168.0.1 一般作为"网关ip" ,

路由选择

路由选择做的事情 : 从主机A =>主机B , 之间的具体路线怎么走 .

​ 网络上 , 环境复杂 , 某个路由器是无法把整个网络环境都记录下来的 , 路由器只能记录周围的情况 ( 路由器内部使用的 路由表 这样的数据结构来记录邻居信息 ) , 实际转发过程中 , 是渐进式的 .

​ IP 数据报 , 在进行网络转发的过程中 , 就是一个 “逐渐问路” 的过程 , 每个路由器只能认识周围的情况 , 很可能问的目标并不知道(目的ip 在路由表汇总 , 没有匹配的结果) , 此时就会走路由器给你指出的一条默认路径 ( 路由表中的 “下一跳表项”)

数据链路层

更接近底层了 , 距离程序员更远

以太网 : 数据链路层 / 物理层

以外网数据帧

把这个数据链路层数据帧 , 最大载荷长度 , 称为 MTU

如果载荷的数据 , 长度超过 MTU , 就会在 IP 层进行分包 , 使每个分出来的结果 , 都能在 MTU 之内 .

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

虽然 MTU 有限制 , 但是没关系 , IP仍然可以保证传输一个更大的数据

虽然 IP 能拆包 吗仍然不能改变 UDP 最大长度是64k 的事实 , 由于拆出的这些 IP数据报中只有一份 UDP 首部 , 这个首部能够填写UDP长度的地方 , 也还是只有 2 个字节 , 64 k 这个限制还是存在的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值