JavaEE 网络原理

本文探讨了应用层自定义协议在网络上传输中的原理,比较了XML、JSON和Protobuf等数据格式,深入解析了TCP和UDP的特性,包括三次握手、四次挥手和滑动窗口在TCP可靠传输中的作用,以及流量控制策略。

应用层

应用层自定义协议
网络上传输本质上是传输一个(二进制)字符串
Java写代码 是各种对象 最后发送数据的时候把对象转化成字符串(二进制)[序列化]
接收数据时也(二进制)字符串换回字符串[反序列化]

类似于一个外卖软件的应用层自定义协议
外卖软件有 商家名称 好评率 距离 评分等等
可以做出以下设计

  1. 明确规定请求和响应包含的信息
    请求: 用户身份 用户当前位置
    响应: 商家名称 好评率 距离 评分
  2. 具体格式
    请求 : 用户8848,80 90\n
    响应 : 张亮麻辣烫 1.jpg 98% 1.1km 4.5\n
    隆江猪脚饭 2.jpg 97% 0.8km 4.6\n

这里的格式就是构造出一个字符串后续字符串就可以作为tcp或者udp的payload进行传输
另一方面服务器就可以解析出逗号前面是用户id后面是经纬度

在实际开发中虽然可以进行自定义协议式可以任意 但是为了避免出现奇怪的设计
就有了一些通用格式

  1. xml是以成对的标签来表示"键值对"信息同时标签支持嵌套就可以构成一些复杂的树型数据结构
    请求:
    < requst >
    < userld >用户8848 < /useld >
    < position >80 90< position >
    < /requst >
    响应:
    < response>
    < shops>
    < shop>
    < name>张亮麻辣烫 < /name>
    < image> 1.jpg< /image>
    < rate> 98% < /rate>
    < star> 4.5 < /star>
    < /shop>
    < /shops>
    < /response>

优点: xml 非常清晰的把结构数据表示出来了
缺点: 表示数据需要引入大量的标签 看起来繁琐 同时也会占用不少的网络宽带

  1. json
    本质上就是键值对
    当前最主流使用的一种网络传输数据的格式,未来在实际开发中经常用到json格式的数据,json对于换行并不敏感,如果这些内容全部放在同一行 也是可以的
    例如
    请求:
    {
    userid: 1234
    position: “100 80”
    }
    响应:
    {
    {
    name:‘张亮麻辣烫’,
    image:‘1.jpg’,
    distance:‘1km’,
    rate:96%
    star:4.7
    },
    }
    json中 使用{}表示键值对使用[ ]表示数组 数组里的每个元素 可以是数字也可以是字符串 还可以是其他的{}或者[]
    一般网络传输的时候 会对json进行压缩(去掉不必要的换行和空格) 同时把所有数据都放到一行去整体占用的宽带就更降低了

优势:相对于xml 表示数据简洁很多 可读性非常好 方便观察中间结果

劣势:终究是需要花费一定的宽带传输key的名字

  1. protobuffer
    谷歌提出的一套 二进制数据序列化方式
    使用二进制的方式 约定某几个字节 表示属性
    最大程度接收空间不必传输key 根据位置和长度 区分每个属性
    优点: 节省宽带 最大化效率
    缺点: 二进制数据 无法肉眼直接观察 不方便调试 需要专门写一个proto文件 描述的格式有一系列语法规则

传输层

UDP

基本特点 无连接 不可靠传输 面向数据包 全双工
udp协议报文格式
udp数据报
在这里插入图片描述
udp报头
在这里插入图片描述
2个字节16个比特位2的16次方-1 表示64kb的数据(65535) 一个udp的数据报最多就是这么长

合法的端口号
有效范围是 0~65535不能(实际上0不会使用)

知名端口号
1~1024 系统赋予了特定的含义 一般不建议使用

一次通信涉及5元组
源端口 目的端口 源ip 目的ip 协议类型

UDP的校验和具体是怎么实现的?
使用了一种CRC检验算法(循环冗余校验)
把udp数据报中的每个字节 都依次进行累加 把累加结果保存到两个字节变量中 所有字节都加一遍 最终就得到了校验和 传输数据源的时候 就会把原始数据和校验和一起传递过去 接收方收到数据 同时也收到了发送端送过来的校验和(旧的校验和)接收方按照同样的方式再算一遍得到新的校验和
如果旧的校验和 和新的检验和相同 就可以看作在传输过程中数据不出错

不过随着时代的发展许多发送的文件都轻易的超过了64kb此时一个udp数据报不能够完全表示一个数据此时国际规定的默认以两个字节表示数据报长度不好改变然后 把数据拆成多组会出现很多问题 如何拼接 某部分丢失 某部分错了怎么办?
此时使用tcp代替udp tcp没有要求报文长度

TCP

tcp特点
有连接 可靠传输 面向字节流 全双工
内核实现可靠传输写代码是感知不到的

tcp报文
tcp的报文时可变长的 tcp报头最长是60个字节
tcp报头的前20个字节是固定的需要首部长度来确认报头到哪里就结束了 载荷是从哪里开始

端口号属于传输层的一员 知道了端口号才能进一步确定数据报交给那个程序

确认应答 是TCP保证可靠性的最核心机制
例如有以下场景

A 给 B发了一条消息
A:你现在人在哪里
B:我在北京

当需要连续发送两条信息的时候
A:你现在人在哪里
A:吃饭了吗
B:我在北京
B:吃了
当连续发送多条数据的时候 可能会出现后发先至的情况 一个数据报 是先发的 另一个是后发的 后发的反而先到了 就会出现牛头不对马嘴的情况

A:你现在人在哪里
A:吃饭了吗
B:吃了
B:北京

为什么会出现后发先至的情况?
因为A发信息给B中间的路径有很多 两个包从A->B走的路线不一样 另外 每个节点繁忙程度不一样 这样的转发过程 就会存在差异

如何解决后发先至的情况?
针对数据进行编导
A:1.你现在人在哪里
A:2.吃饭了吗
B:1.北京
B:2.吃了

由于tcp是面向字节流 不是按照"条"位单位来传输

例如
A:数据(1~1000)
B:确认应答(1001~2000)(确认1001号数之前的据都受到了 接下来应该要从1001开始发)
A:数据(1001~2000)
B:确认应答(下一个是2001)

是针对字节进行编号 而不是针对条

我们就需要方法区分当前报文是普通报文还是确认应答报文
acknowledge(应答)
ACK位0表示这是一个普通的报文 此时只有32位序列号是有效的
ACK位1表示这是一个应答报文 此时报文的序号和确认序号都是有效的

超时重传 是tcp可靠性机制的有效补充
丢包 网络上发送一个数据然后丢失了
此时就会进行重新发送数据叫做超时重传
相当于针对确认应答 进行重要补充

去重 根据数据序号去重
接收方 也会在接收缓冲区 对收到的数据进行排序 也能处理后发先至的问题

报答应文重传丢包了重传会重复重传(所以接收方接收数据之后需要对数据进行去重 把重复的数据丢弃掉 保证应用程序调用inputStream.read的时候 读到的不会出现重复)

如何进行去重 如何高效判定当前收到的数据是否是重复的?
直接使用tcp的序号来作为判定依据

tcp会在内核中给每个socket对象安排一个内存空间相当于一个队列 也称为"接收缓冲区" 收到的数据都会被放在接收缓冲区了并且按照序号排列号顺序 此时就可以很容易查到新接收的数据是否重复了

连接管理

建立连接 三次握手
在这里插入图片描述
三次握手 是保证可靠性的机制
tcp想要保证可靠传输 可靠传输的前提 是网络路径畅通
tcp三次握手是要验证网络通信是否畅通 以及验证每个主机的发送能力和接收能力是否正常

三次握手 恰好三次能验证双发的发送和接收能力均正常 并且把这样的信息同步给了双方

两次握手 明显不可以 虽然可以验证完成通信能力的正确性 但是服务器不知道验证通过的消息

四次握手 可以但没有必要降低了效率

三次握手 还能起到"消息协商"的效果
通信的时候涉及到一些参数 需要双方保持一致的 通过协商 来确定参数具体是多少

比如双方的序号从几开始 一般不会从0/1开始 这样做主要是保证两次连接 消息的序号能够有较大的差异从而好去判定出某个消息是否属于这个连接的 网络上传输的消息可能是后发先至 极端情况下 某个消息迟到了很久
当消息到达对端的时候 服务器和客户端已经断开了上一个连接 重新建立连接 这个时候 就可以通过序号 明显识别出这个是上一个连接的消息 就可以丢弃了

综上所诉

  1. 验证通信路径是否通畅 双方的发送/接收能力是否正常
  2. 协商必要的参数 使客户端和服务器使用相同的参数进行消息传输

断开连接 四次挥手
连接 通信双发 各自在内存中保存对端的相关信息
如果不需要连接 就得及时释放上述存储空间
三次握手 必然是客户端发起
四次挥手 也可以使服务器先发起 大多数是客户端发起
在这里插入图片描述
四次挥手为什么会是四次 三次行不行?
四次挥手有时候的确可以三次完成 但是有的时候不行
中间这两次 不一定能和并
FIN的触发 是应用程序代码 来控制的 调用socket.close() 或者进程结束 就会触发FIN 相比之下 ack则是内核瞬发的 FIN要用户代码控制需要取决代码是怎么写的(什么时候执行close不确定) 如果客户端一断开 服务器就会感知到并且结束循环 触发close这时就可以和并成三次 万一服务器还需要做很多其他收尾工作close执行的实际就会很慢就只能分开传输了

如果服务器始终不进行close会怎样? 客户端的连接就始终不关闭吗?
此时我们服务器的状态就处于close_wait状态

在这里插入图片描述
另一种情况close处bug执行不到或者没写此时客户端 迟迟等待不到FIN 也会进行等待 如果一直登 都等不到 就会单方面放弃连接 客户端就会直接把自己保存的信息删除 释放了 目标释放资源 能双方都释放最好 如果条件不允许 那也不影响单方面释放

如果通信过程中出现了丢包了又怎么处理?
涉及超时重传 三次握手 四次挥手 也是带有重传机制
尽可能重传 如果多次失败就会单方面释放

在这里插入图片描述
如果最后一个ack丢失 服务器就会重新传一个过来fin
此时如果客户端已经连接释放 重传fin就无人能够进行ack了
因此就需要让a在发出最后一个ack之后 让连接在等一会(主要就是看等的过程中会不会收到重传的fin 对方还没有重传就认为ack已经被对方收到了)此时a才能正确释放连接

客户端需要等待多久才能释放连接?
等待的时间就是网络上任意两点之间传输的数据的最大时间*2定义为MSL
如果客户端在等2MSL时间过程中 服务器在反复重传FIN多次 这些FIN都丢了 这个时候网络一定出现了严重故障 这个时候不具备"可靠传输" 客户端也就可以单方面释放资源了

TCP是如何实现可靠传输?
确认应答(真正决定性作用 每次传输的数据都是可靠的)
超时重传
连接管理(三次握手 四次挥手)表示开始时网络环境时畅通的 但不代表后面一直畅通

四 滑动窗口
提高传输效率(让TCP在可靠传输的前提下 效率不能太低)
使用滑动窗口使TCP比UDP缩小差距

在这里插入图片描述
没有滑动窗口 会带有确认应答 这样的传输能保证可靠性 带式大量的时间都消耗在等ack上了使用滑动窗口 就是为了缩短上述的时间
使用滑动窗口 批量的发送一组数据
在这里插入图片描述

一次性发出一组数据 发这一组数据过程中 不需要等待ack就直接往前发 此时 就相当使用 一份等待时间 等四个ack(把等待时间重合了)
此时把一次发多少数据不用等ack的大小 就称为"窗口" 窗口越大 此时批量发送的数据就越多 效率就越高
如果A给B批量发送1001~ 2000 2001~ 3000 3001~ 4000 4001~ 5000就要等四个ack
这4个ack到达顺序 也会有先有后 按照 2001 3001 4001 5001顺序到达 当主机2001到达A之后不需要等到最后的5001到了才发送下一组消息 而是直接发送5000~ 6001 此时等待的ack 就是 3001 4001 5001 6001 此时还是等待4条ack窗口大小还是一样打只是往后挪了一条数据
滑动窗口 本质上就是批量发送数据

对于批量传输 中间丢包了会怎么样?
对于tcp来说提高效率 必然不能影响到可靠性

滑动窗口下的超时重传

  1. 数据丢了
    数据报丢失 必须要进行重传
    什么时候进行重传?
    具体怎么重传?
    在这里插入图片描述
    在1001 ~ 2000丢失之后2001 ~ 3000这个数据到达了B B返回ACK确认序号 仍然是1001 B仍然再向 A索要 1001这个数据 所以返回的ack确认序号都是1001 当A连续几次都收到了来自B的索要1001的数据 A就明白 1001丢失
    这时 A主机A就会重新传输1001 ~ 2000 这段数据 当重传的1001 到达B之后B返回的ACK就是7001了

接收方 有个缓冲区在接收数据
在这里插入图片描述
如果接收缓存区 这一块是少了的
返回ACK就会始终索要1001这个数据报
一旦1001这个数据报被补上

此时 1001~2000后面的数据哪里是否还有缺失 如果还有缺失 索要对应的数据如果没缺失 直接索要缓冲区最后一条数据的下一个即可

此时 就相当于使用最小的成本 来完成这个重传数据的操作 (只把丢失的数据重传了其他数据部分没有重传)快速重传(本质上还是超时重传)

滑动窗口 在涉及双方大规模传输时 是滑动窗口
如果双方传输的数据比较少 这个时候就不会滑动窗口了

  1. ACK丢了
    ACK如果丢了不用做任何处理也是能正确的
    在这里插入图片描述
    这里如果有1001确认序号丢了没有关系 因为下面的2001没有丢 表示2001之前的数据都收到了 也包含1~1000 虽然A没有收到1001这个ack但是2001这个ack涵盖了1001的含义(除非是所有的ack丢了 否则没有任何影响)

流量控制
滑动窗口越大 传输效率越高
但是窗口越大 使接收方处理不过来 就会出现丢包的 此时就得重传了 反而还会影响了效率

流量控制就是给滑动窗口踩刹车 避免窗口太大导致接收方处理不过来

数据传输类似于一个生产者消费者模型
A主机生产速度很快 B这边消费速度跟不上 接收的缓冲区就会满了 之后发送数据就会丢包
因此 流量控制 就是根据接收方的处理能力 来限制发送方的发送速度(窗口大小)

如和衡量接收方的处理速度??
此处就是用接收缓冲区剩余空间大小来作为衡量指标 如果剩余空间大 应用的消费程序数据的速度就快
此处就会直接把缓冲区剩余空间大小通过ack报文反馈给对方 作为发送发下一次发送数据 窗口大小的参考依据
在这里插入图片描述
16位窗口大小这个字段只对ack报文才有意义
这个数字就表示了当前接收方缓冲区剩余空间大小 这个数字返回给发送方 就可以作为下一轮发送参考依据了 选项中有一个选项 是窗口大小扩张因子实际窗口是16位窗口大小<<扩展因子

衡量接收方速度:
例如:接收缓冲区4000这么大

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值