-------------------UDP协议+TCP协议-------------------------

#学习笔记

#学习记录

UDP协议

无连接: 知道对方的ip和端口号就能直接进行传输了,不需要建立连接

不可靠传输: 没有确认机制 和 重传机制, 请求发出去了 对方收没收到就不管那么多了

面向数据报: 每次读取数据的单位是一整个数据报, 应用层给传输层发多大的报文, udp就传输多大的,不会拆分也不会合并,不够灵活, 发100个字节给你 你就一次性读100个 不能一次10个10个的循环读取, 读取数据不灵活.

UDP协议报文格式

像TCP/UDP/IP协议的报文的报头部分都是二进制的, 而http协议的是文本格式的

16bit位也就是2字节 能表示的数据范围是0-65535, 但是1024以下的端口号我们一般留着, 实际开发过程中使用的端口是1024-65535之间

长度: 0-65535字节 ==> 64kb这样一个范围, 表示的是整个udp数据报的大小, 也可以说成载荷上限就是64kb 每个udp数据报最多带64kb的数据 显然这是udp的一大缺陷, 假设应用层数据特别大, 你就得拆成多个udp数据报来传输, 这样十分麻烦 还得考虑怎么去拆包组包

校验和: 就是将除校验和之外的其他部分都拿去通过算法计算得到一个校验和来确保数据传输过程中没有发送比特翻转这种问题 因为数据在传输过程中就是个二进制的数据 难免会受到外界环境的干扰 是0 变 1 1变 0 这种, 接收方拿到UDP数据报之后先计算一个校验和 然后和 原有的校验和进行比较 如果一样则证明没问题 不一样就说明这个UDP数据报有问题 直接丢弃掉

TCP协议:

有连接 面向字节流 可靠传输 全双工

TCP协议报文格式

16位源端口号和目的端口号16位校验和就不必多说了

4位首部长度 4bit 表示的数据是0-15 难道是15字节的首部长度吗?? 固定部分已经是20字节了还有选项部分 不可能是15字节 在此处他是以4字节为单位的 也就是 15 * 4=60字节, 20字节固定大小 另外40字节是可选的,可以选择变大也可以变小.

TCP的核心机制

(一)确认应答: 该机制可保障传输的可靠性, 发送一个请求, 接受方必须返回对应的响应来确认收到了请求

此时出现一个问题: 倘若我们发多个请求给B正常来说 A先问的明晚 然后应该先得到明晚集合的时间也就是7点 然后再得到今晚集合时间 但是如果返回的7点出现点问题 比6点后达到A那A就会误认为是明晚6点集合, 今晚7点集合 这显然是错误的理解 也就是后发先至问题

如何解决后发先至问题呢? ----->确认序号

如图A在发送数据时候将32位序号写上对应的数值来表示此处传输的数据的范围, 返回的报文标志位ack改为1表示此报文为应答报文, 并且加上确认序号 这个确认序号 是下一次发送数据范围的起点, 确认序号只有在ack为1的报文中才生效 ,也就是应答报文中生效

消息发送到接收方后, 先存在接受方的缓冲区内, 该缓冲区就类似一个带编号的数组, 传过来的数据在那个范围,那么该数据就会放到缓冲区的对应位置, 后需需要read数据就从该缓冲区内读, read也是按照顺序来读的,前面的数据还没读到, 就会阻塞, 不会跳过来读后面的数据.

(二)超时重传:

A在发B发送数据时可能发送出去的请求直接丢了, 或者 请求到达B了 B给A返回的ack丢了, 这两种情况 总之A都没有收到B的ack A也不知道具体什么情况 他就认为发出去的请求没有顺利到达B 认为丢包了, 就会进行重传, 这个超时时间也是随着重传的次数递增的, 一开是设置的超时时间可能太短了, 一下子就超时了, 现在把范围拉大, 容错率拉高, 超时时间变大了, 那么也就是意味着数据能够成功到达对方的概率变高了, 如果这样的话你还丢包只能说明丢包率特别大了, 网络出现了严重的故障,再一味的重传下去也没有意义了

现在出现一个问题:

如果是这种请求直接丢了的情况那重传没啥问题

但是如果是这种ack丢了的话, 请求其实是到达了B这边了的, 假如是进行扣款操作,那你重传不扣款了两次了吗 如何解决这种问题呢? 其实数据包发过来之后 先在缓冲区内找指定的位置0-1001 如果之前已经传过了并且顺利到达B了 那么该缓冲区就会有该数据, tcp会先去缓冲区找如果有了 就丢去你这个数据包, 没有就存入, 就不会出现重复操作的情况出现.

确认应答 和 超时重传 两个TCP最核心的机制确保了TCP传输的可靠性

(三)连接管理

建立连接: 通过三次握手建立连接

在TCP报头的标志位中有一个标志位SYN 把该为设为1 就表示当前报文为同步报文 不携带载荷

连接的说明:此处的连接并不是说真正物理意义上的连接, 而是彼此双方互相保存一下对方的信息, 这样就建立了连接.

SYN:同步,这里指的是同步彼此的信息, A给B发SYN, 就是告诉B,我要和你建立连接, 你保存一下我的信息, 并且你也把你的信息发过来, 我也保存一下

三次握手过程具体过程:

这里其中的ACK 和 SYN 都是内核控制的, 请求A发的SYN一到B,B的内核就控制发送了SYN+ACK给A, 他两是同时发的, 所以就可以拿一个报文里面的标志位ACK 和 SYN 同时设置为 1 即可. 因为网络传输会涉及到一系列的拆包组包过程, 这个过程是需要消耗资源和时间的, 发一条就能解决的事何必发两条呢? 反而拖慢了网络传输的效率

三次握手的状态变化:

TCP建立连接三次握手作用:

1. 相当于"投石问路", 初步测试网络通信链路是否通畅

2. 验证通信双方的发送接受数据能力

3. 三次握手过程中可以协商一些关键信息, 例如待会通信32位序号从几开始? 一般不是从0开始,而且两次连接的初始序号都不一样, 并且相差很大.

想象一下这样的场景: 第一次建立连接初始序号是1001 其中发送了一条数据包范围是3001-40001, 当该数据包还未到达接受方之前 发送方 和 接收方 已经断开连接了, 然后发送方重新去建立连接, 初始序号还是从1001开始的话, 那么这条上一次连接中发送的数据包就会被第二次连接的接收方给接收了, 那这就有点不对了把? 第二次连接的接收方 和 第一次的 可能压根就不是一个程序了, 你让另外一个程序来处理你上一个程序的请求? 这显然是错误的, 因此我们必须在下一次建立连接时, 通过三次握手把待会数据传输的32位序号的初始值给协商清楚,并且这个初始值一定是要和上一次的相差很大, 即使还有上一次的数据包过来, 那也对不上号, 也就会丢弃,不会处理, 由此可见事先协商的重要性.

断开连接: 通过四次挥手来断开连接

FIN标志位: 该标志位必须通过代码逻辑里的socket.close 来触发, 不是由操作系统内核控制的

客户端和服务器都可以提出断开连接的FIN报文,不像三次握手, 一定是客户端向服务器发送请求, 然后客户端主动来和服务器建立连接. 这里的四次握手中的FIN和ACK不能合并到一个报文了, 因为ACK是内核控制的, 网卡收到FIN后自动就将ACK发送出去了, 而FIN什么时候发不知道, 可能是执行一段业务逻辑之后再socket.close 来断开连接, 他两压根就不在同一时机所以不能合并到同一个报文,但是也不是严格的不能三次挥手,其实如果将ACK延时了, 就是延时应答, 将ACK延时到和FIN一起返回回去也是可以实现合并的.

四次挥手过程中TCP状态的变化

谁先发起FIN谁就进入FIN_WAIT状态, 接收到FIN的发送完ACK后变成CLOSE_WAIT状态, CLOSE_WAIT状态表示等待close关闭, 也就是等待业务逻辑执行到socket.close这行代码 然后真正close掉, 发送FIN, 另外一方就变成TIME_WAIT状态 自身变成LAST_ACK 等待最后的ACK报文

正常来说连接的一方主动提出断开连接后, 另外一方应该尽快的执行close 将连接给释放掉,一般看不到CLOSE_WAIT状态, 这个状态持续的时间一般很短, 如果在开发中发现TCP状态长时间处于CLOSE_WAIT状态,那么一定是代码出问题了, 可能你忘写close 或者 close逻辑执行不到, 导致代码一直无法close释放连接, TCP一直处于CLOSE_WAIT状态.

所以A这边在发完ACK之后不能立即就断开连接, 而是要等一会, 确保B不会再重传FIN过来了, 再释放连接. 这个时间是 2 * MSL(网络上任意两个节点传输数据时消耗的最大时间) 这个值可以设置

(四)滑动窗口

前面所讲的确认应答和超市重传是用来确保TCP的可靠性的, 要实现可靠性就必须牺牲一部分的性能, 那么为了减少性能折扣的程度, 引入滑动窗口来减少性能的折损

具体内容:

发送方在发送数据时候前几个数据都是直接发送, 不等待, 就是按一个窗口的量的大小直接一批次全部发送出去, 不是发一个等一个ack再发下一个, 这种效率太低了. 不是按下图这种

而是按照这种:

批量数据不间断的连续发送出去之后 在同一时间等多份请求的ACK, 用了一份时间做了多件事, 这样效率肯定会提高.

当收到一个请求的的ACK之后立马就发下一条数据, 不需要等其他请求的ACK都过来了再发

如图 当A收到2001的ACK之后, 窗口就直接往后划,去发送后面的数据. 如果来的是3001的确认序号怎么办? 我们知道确认序号能够表示该序号之前的所有数据都接收到了, 那么就相当于前面的ack都返回了, 那么我们就可以直接往后直接移动三格.

滑动窗口的窗口越大,那么一次性发送的数据也就越多, 效率也越高, 但是效率和可靠性事不可兼得的, 窗口大了, 那么可靠性就不高了, 所以窗口也不是越大越好.

那么滑动窗口过程出现丢包怎么办?

第一种情况就是ack丢了, 这个其实不处理也没关系, 因为ack丢了 后面还有其他ack 只要一个没丢, 他就能覆盖前面的ack, 告诉主机A 主机B已经接收到了该确认序号之前的所以数据了, 那么该确认序号之前的ack丢了也就无所谓了.

情况二:数据包直接丢了

如果其中的某个数据包丢了, 比如1001-2000数据包丢了, 根据read按缓冲区顺序的特性我们知道, 如果1001-2000的这个坑还没填上read是不会往后面读的, 所以其他数据包的ack里的确认序号都是1001, 主机A一直接收到1001 直到最后一个数据包也收到1001, 那么此时主机A就会意识到不对, 然后对1001-2000这个数据包进行重发, 返回的ack里的确认序号是缓冲区里的最大序号. 表示该序号之前的所有数据都接收到了

(五)流量控制: 窗口大小不是越大越好, 太大会导致效率不高, 并且你太大了接收方也处理不过来就会造成丢包. 那么这个窗口大小多少合适呢? 这个是有接收方自行决定的, 接收方每次给发送方返回的ack中有个16位窗口大小的属性, 在这里面写下一次发送的数据量 也就是规定窗口大小, 接收方能够自行的控制发送数据的多少, 这样的一个过程就是流量控制.

16位窗口大小并不是指最大只能64kb的窗口的小, 而是在选项里还有个窗口扩展因子属性, 拿16窗口大小的值 << 窗口扩展因子 这样数据就是一个指数级的增长, 窗口能表示的范围就很大了.

还有一个问题假设接收方这边返回下一个窗口大小是0的话, 发送方就不会再发数据过来, 而是等接收方缓冲区有空闲在发, 那他也不知道什么时候有空闲啊? 那咋办? 这时发送方可以发一个窗口探测包, 去触发ack, 收到ack不就能知道缓冲区的空闲情况了吗 如果不为0就按照窗口大小发, 还为0那就继续等.

(六)拥塞控制

根据转发链路的传输能力来进行限制. 不再是流量控制中的依据 接收方缓冲区的空闲程度来限制

那链路中有n个节点, 我们肯定是要以最慢的那个为准, 因为搞一个很大的量,只要其中一个说我接受不了处理不了这么多请求, 那么就会导致丢包, 功亏一篑, 所以一定是以最慢的节点处理请求的量作为窗口大小

那怎样才能知道谁最慢呢?---->做实验

可以先弄个小窗口的,如果所有节点都说我能接受,那就继续加大,如果有人说接受不了那就减小,反复的增大减小, 直到找到最合适的那个量, 这就是对窗口限制的大小.

如图是拥塞控制下窗口大小变化的流程:

先慢启动--->指数增长--->然后线性增长(减缓速度因为可能会丢包了)---->一旦丢包就将窗口大小调小一点----->继续线性增长

(七)延时应答:

默认情况下接收方收到请求后立马就返回ACK,但是我们可以等一等, 等接收方多处理一会,腾出更多的缓冲区空间, 待会返回的ACK直接带一个较大的窗口大小, 那么下一次发送的数据就更多, 窗口更大, 效率更高. 当然也不是100%就能提高效率,可能你等一会缓冲区的空闲量变化不大呢?

也不是每个数据包都能延时应答, 因为假设所有包都延时了, 那就没有ack回去了啊, 那这个可靠性就无法得到保证了.

延时应答有:

数量限制:每隔N个包就延时应答一次, 因为这样即使前面的包延时没有返回ack,但是后面的数据包返回的ack能顺利的将前面的包给覆盖掉, 确保可靠性.

时间限制: 超过最大延时时间就来一次延时应答.

(八)捎带应答:

有了延时应答, 那么我们可以将ack延时到和响应一起返回回去, 只需要在响应报文的ack设置为1即可, 这样就能起到减少包的发送,减少封装分用的过程,提高效率.

(九)面向字节流

通过字节流进行传输, 在读取数据的时候很容易混淆包和包之间的界限,导致接收方无法区分从哪到哪是一个应用层数据包 容易读到下一个数据包的内容,也就是读多了, 出现粘包问题 .其实这里主要是搞清楚读到哪是一个完整的应用层数据包.

从应用层角度来解决这个问题

  1. 约定包和包之间的分割符.

比如如果是get请求没有body那么header下面一行空行就是区分包的分隔符

  1. 约定包的长度

在HTTP报文的header中有Content-Length这样的属性来描述body的长度.后续在读取body就可以按照指定长度来读, 避免多度,出现粘包问题.

(十)异常处理情况

1某个进程突然崩溃了:

进程的崩溃和进程正常退出没有区别, 就是释放连接,释放资源,回收文件描述符表的资源, 触发四次挥手进行断开连接. 此处虽然进程关闭了, 但是还有对方的信息 只要你调用了close 那么就发出了FIN给对方, 然后后续的ACK 是由内核来操作的 和进程没有关系了.

2主机关机

正常流程的关机还是要先杀掉所有进程然后关机, 如果杀掉进程之后触发四次挥手, 在关机之前完成了那就相当于正常关闭进程,没什么问题. 但是如果四次挥手还没挥完其中一方就直接关机了呢?

假设B的FIN发来之前A就已经关机了, 此时B收不到A的ACK了, 然后他就会进行重传, 重传了若干次后他认为A发生了严重问题, 已经无法A已经无法给他正常响应了,就会主动放弃和A的连接,就是将A的信息删除掉. 最终B还是可以顺利的释放连接.

3主机掉电

就是台式机直接断开, 拔掉电源这种. 分两种情况 发送方掉电 和 接收方掉电.

接收方掉电:

B发现发送出去的数据包后续没有ACK了, 就会触发重传机制, 重传达到一定次数还是没有ACK之后B就会发一个复位报文 就是标志位的RST 设置为1 ,意思是尝试和A重新建立连接, 如果还是没有ACK B就自动释放掉该连接了

发送方掉电:

此时B也不知道A是掉电了还是说休息一会待会再发, B就只好等一会, 如果一会之后还是没有数据包过来, B就会给A发送一个特殊的数据包---心跳包, 不携带任何载荷信息, 只是为了去触发A的ACK,看看A是否还正常. 如果还是没有ACK 就触发RST 复位报文 还是没有ACK 那B就单方面释放连接.

4网线断开了

站在A的角度就是接收方B掉电了的情况

站在B的角度就是发送方A掉电了的情况

无论那种异常情况 最终都是能够自动释放连接资源的.

标志位内还有两个没有讲到 就是URG 和 PSH

URG:紧急指针位 正常来说TCP read数据包按照顺序来读, 紧急指针位相当于插队, 跳过前面的数据,直接从指定的序号来read

PSH:催促标志位: 发送方发来的数据包里该位为1, 接收方就会尽快的将该数据读到程序里.

相关推荐
思成不止于此2 小时前
【MySQL 零基础入门】DML 核心语法全解析:表数据的增删改操作篇
数据库·笔记·sql·学习·mysql
汝生淮南吾在北2 小时前
SpringBoot+Vue非遗文化宣传网站
java·前端·vue.js·spring boot·后端·毕业设计·课程设计
无名-CODING2 小时前
从零手写一个迷你 Tomcat —— 彻底理解 Servlet 容器原理
java·servlet·tomcat
速易达网络2 小时前
Java Web旅游网站系统介绍
java·tomcat
谷粒.2 小时前
AI在测试中的应用:从自动化到智能化的跨越
运维·前端·网络·人工智能·测试工具·开源·自动化
AI分享猿2 小时前
Java后端实战:SpringBoot接口遇袭后,用轻量WAF兼顾安全与性能
java·spring boot·安全·免费waf·web防火墙推荐·企业网站防护·防止恶意爬虫
invicinble2 小时前
关于认识,和优化idea开发
java·ide·intellij-idea
⑩-2 小时前
MVC-三层架构详解
java·架构·mvc
小刘不想改BUG2 小时前
LeetCode 56.合并区间 Java
java·python·leetcode·贪心算法·贪心