《协议栈的骨架:从Web请求到比特流——详解四层架构的可靠传输与流量控制》
前言
本篇博客将详细介绍网络原理(细~~~)
💖 个人主页:熬夜写代码的小蔡
🖥 文章专栏
若有问题 评论区见
🎉欢迎大家点赞👍收藏⭐文章
一.应用层
这里的应用层只是个开头,更详细的在下一篇文章
应用层是和程序员接触最密集的,在这里, 很多时候,都是程序员“自定义”应用层协议“(当然有一些现成的应用层协议)其实也就是这两点:
1.根据需求,明确要传输的信息
2.约定好信息按照什么格式来传输
在实际开发中,有这么几种常见的格式
1.xml
- 结构化格式:基于标签的文本格式(如
<person><name>John</name></person>
),支持嵌套和复杂数据。 - 可读性:可直接阅读,适合配置文件和文档。
- 灵活性:支持自定义标签和命名空间,扩展性强。
优点:严格的结构化,适合复杂数据场景
缺点:标签占用大量字符,数据体积大。
2.json
json是当下最流行的一种数据组织格式
- 轻量级键值对格式:语法简洁使用键值对结构,键值对之间使用”,”来分割,键和值之间使用“:”来分割(如
{"name": "John", "age": 30}
),支持数组、嵌套对象。 - 主流支持:几乎被所有编程语言原生或通过库支持(如Python的
json
模块、JavaJackson)。
优点:相比XML减少冗余字符,文本解析较XML高效。
缺点: 在网络传输中,消耗额外的带宽(需要把key也进行传输)
3.protobuffer
- 二进制序列化协议:由Google开发,数据以高效二进制传输。
优点:占用宽带最低,传输效率最高,非常适合对于性能要求比较高的场景
缺点: 可读性不好(二进制结构,肉眼无法直接阅读),一定程度影响开发效率
二.传输层(重点)
2.1UDP
2.1.1端口号
再来回顾一下端口号,写一个服务器,必须手动指定一个端口号,通过端口号来区分当前这个主机上的不同的应用程序,写一个客户端,客户端在通信中也会有一个端口号,是系统自动分配的。
端口号,固定就是2个字节,表示的数据范围0~65535,一般来说0是不用的,1~1023称为“知名端口号”,给一些知名的服务器预留的位置,像ssh:20,http:80,https:443,1024~65535 称为普通端口号,平时自己写代码还是用普通端口,不要和别人的端口号重复。
2.1.2UDP协议报文格式
在UDP的学习中,最主要的工作就是去理解协议报文格式
在UDP报头里有源端口号,目的端口号,UDP报文长度,UDP的校验和,UDP载荷数据部分是完整的应用层数据报,报头和载荷之间就可以认为是一个“字符串拼接”
UDP报文长度只有2个字节,16位表示的数据,表示的范围0~65535也就是64kb,能否对UDP进行升级,比如把2个字节变成4个字节来把UDP报文表示的范围变大?
不可以!不是技术上不可以,这是一个政治问题,要升级,就得通信上方都要升级,如果一方升级了,一方没有升级,数据就对不上了,肯定无法通信了,全世界的设备都得一起升级(升级操作系统内核,UDP是内核中实现的)
那我们熟悉的源IP,目的IP在哪??
不在传输层,而在网络层中(IP协议里)
校验和是什么???
在网络传输过程中,由于一些外部干扰,可能会出现数据传输出错的情况,因此就需要有办法来识别出出错的数据
校验和就是这样的一种检查手段~~
那怎么检查呢,是根据数据的大小吗?个数吗?
说现在一眨眼我就大二了,可是还要上早八,今天早上我起晚了,上午的课是计算机组成原理,计算机网络原理,我需要拿这两本书,然后我就拿了两本和他俩差不多厚度的书去上课了,到教室一看拿错书了,所以根据数据的大小,个数显然不是很靠谱,那如果我只记得拿计网计组我就知道是算机组成原理,计算机网络原理,这两本书
校验和,其实本质上也是一个字符串,体积比原始的数据更小,又是通过原始的数据生成的原始数据相同,得到的校验和就一定相同.反之, 校验和相同, 原始数据大概率相同 (理论上会存在不同的情况, 实际的概率非常低,可以忽略不计)
如何基于校验和来完成数据的校验???
1.发送方,把要发送的数据整理好(称为 data1),通过一定的算法, 计算出校验和 checksum1
2.发送方把 data1 和 checksum1 一起通过网络发送出去,
3.接收方收到数据,收到的数据称为 data2(数据可能和 data1 就不一样了),收到数据 cHecksum1
4.接收方再根据 data2 重新计算校验和 (按照相同的算法),得到 checksum2
5.对比 checksum1 和 checksum2 是否相同.如果不同,则认为 data2 和 data1 一定不相同.如果 checksum1 和 checksum2 相同,则认为 data1 和 data2 大概率是相同的(理论上存在不同的可能性,概率比较低,工程上忽略不计
校验和是怎么算的???
计算校验和,有很多种算法,此处 UDP 中使用的是 CRC 算法(循环冗余算法)把当前要计算校验和的数据,每个字节,都进行累加,把结果保存到这个 两个字节的 变量中.累加过程中如果溢出,也没关系,如果中间某个数据,出现传输错误,第二次计算的校验和就会和第一次不同~~
CRC这个算法其实不是特别的靠谱,导致两个不同的数据,得到相同的 crc 校验和的概率比较大(前一个字节恰好 少1,后一个字节恰好 多 1)
所以还是md5算法更加可靠~~~
这里有一系列的公式,来完成 md5 的计算,(咱们不需要考虑公式是啥样的,是一个数学问题), 但是咱们需要知道 md5 的特点
1.定长.无论你原始数据多长, 计算得到的 md5,都是固定长度
2.分散.给定两个原始数据, 哪怕绝大部分内容都一样,只要其中-个字节不同,得到的 md5 值都会差异很大
这里咱们把上面那一堆数据的最后一个字母改一下,再来对比
3.不可逆,给你一个原始数据,计算 md5,非常容易.现有计算机的算力极限,理论上是不可行的
2.2TCP
2.2.1 TCP的特点
TCP 这个协议最大的特点,就是 可靠传输 ~~(TCP 的初心!)
下面的图片看不懂没关系,先往下看,再回过头来看。
2.2.2TCP的相关特性
1.有链接
2.可靠传输
3.面向字节流
4.全双工
1,3,4都在代码中有体现,那什么是可靠传输(能不能抗住挖掘机一铲子)???
可靠传输,不是说,发送方把数据能够 100% 的传输给接收方~~ (要求太高了)
1)发送方发出去数据之后,能够知道接收方是否收到数据.
2)一旦发现对方没收到,就可以通过一系列的手段来"补救”
2.2.3TCP协议实现可靠传输和高效流量控制的核心机制
1.确认应答
发送方,把数据发给接收方之后,接收方收到数据就会给发送方返回一个 应答报文 (acknowledge, ack),发送方,如果收到这个应答报文了,就知道自己的数据是否发送成功了.
在实际的网络传输过程中数据可能会出现“后发先至”的情况
所以TCP在此要完成两个工作:
1.确保应答报文和发出去的数据, 能对上号,不要出现歧义.
2.确保在出现后发先至的现象时,能够让应用程序这边仍然按照正确的顺序来理解数据
一个 TCP 数据包里一共有 1000 个字节的荷数据,其中第一个字节的序号是 1,就在 TCP 报头的序号字段中写"1"由于一共是 1000 个字节,此时最后一个字节的序号自然就是1000 了.但是 1000 这样的数据并没有在 TCP 报头中记录.TCP 报头中记录的序号,是这一次传输的载荷数据中第一个字节的序号
剩下其他字节的序号,都需要依次的推出,在 应答报文中,就会在 确认序号 字段中填写 1001,因为收到的教据是 1-1000,所以 1001 之前的数据,都被 B 收到了或者也可以理解成, B 接下来向 A 素要 1001 开始的数据。
TCP 的初心,是为了实现可靠传输 =>达成可靠传输的最核心的机制, 就是 确认应答。
如何区分一个数据包是普通的还是ack应答数据呢???
在第二个ACK这里这一位为1, 表示当前数据包是一个应答报文.此时该数据包中的“确认序号字段”就能够生效.这一位为 0,表示当前数据包是一个普通报文.此时数据包中的"确认序号字段”是不生效.
最后总结:确认应答,是 TCP 最核心的机制, 支持了 TCP 的可靠传输!!
2.超时重传
确认应答,描述的是一个比较理想的情况,、如果网络传输过程中出现丢包了,怎么办??发送方,势必就无法收到 ACK 了。使用超时重传机制,针对确认应答,进行补充.
为什么会丢包???
网络中,“收费站” 可以理解成是一些 路由器/交换机如果数据包太多了,就会在这些路由器/交换机上出现“堵车”但是路由器针对“堵车"的处理,往往是比较粗暴的,不会把这些积压的数据包都保存好,而是会把其中的大部分数据包直接给丢弃掉,比时这个数据包就在网络上消失
丢包又分两种情况???
由于丢包是一个“随机" 的事件,因此在上述 tcp 传输过程中,丢包就存在两种情况
1.传输的数据丢了
2.返回的ACK丢了
无论出现上述哪种情况,发送方都会进行"重新传输"第一次是丢了,重传一下试试呗,很大概率就能传过去呢~.
重传操作,大幅度的提升了数据能够被传过去的概率~~重传就是一个很好的丢包下的补救措施了
那么发送方何时进行重传呢???
发送方,发出去数据之后,会等待一段时间,如果这个时间之内,ack 来了,此时就自然视为数据到达.如果达到这个时间之后,数据还没到, 就会出发重传机制~~
超过了等待的时间,再重传。
1.初始的等待时间,是可配置的,不同的系统上都不一定一样,也可以通过修改一些内核参数来引起这里的时间变化.
2.等待的时间,也会动态变化,每多经历一次超时,等待时间都念变长
A ->B 发了一条数据,第一次,A 等待 ACK 的时间,假设是 50ms此时如果达到 50ms, 还没有 ack,A 就重传,当 A 重传的数据,还是没有收到 ack,第二次等待的时间就会比第一次更长拉长也不是无限拉长,重传若干此时,时间拉长到一定程度,认为数据再怎么重传也没用了,就放弃 tcp 连接(准确的说,是会触发 tcp 的重置连接操作)
如果站在接收方的角度,收到两条重复的数据,是否会带来BUG???
比如发的是一条数据,收的时候收到两条数据了 (inputStream.read,读出来的是两条一样的数据),其实 TCP 已经非常贴心的帮我们把这个问题解决了.
TCP 会有一个“接收缓冲区"就是一个内存空间,会保存当前已经收到的数据,以及数据的序号,接收方如果发现当前发送方发来的数据,是已经在接收缓冲区中存在的(收到过的重复数据了),接收方就会直接把这个后来的数据给丢弃掉,确保应用程序进行read 的时候读到的是只有一条数据接受缓冲区,不仅仅是能进行去重,还能进行重新排序,确保发送的顺序,和应用程序读取的顺序是一致的
3.连接管理
在这里有两方面,建立连接+断开连接,建立连接也就是下面说的三次握手,断开连接也就是下面说的四次挥手
什么是握手呢???
握手也就是打招呼,打招呼的内容,没有实际意义,也比较简短,只是为了唤起对方的注意~~,tcp 这里的握手,也是类似,也就是给对方传输一个简短的,没有业务数掘的数据包通过这个数据包,来唤起对方的注意,从而触发后续的操作.
什么是三次握手???
TCP 的三次握手,TCP 在建立连接的过程中,需要通信双方一共"打三次招呼" 才能够完成连接建立的
如果SYN为1表示这个报文是一个同步报文段(就是一个特殊的TCP数据包没有载荷的(不携带业务数据的)) A 想和 B建立连接. A 就会主动发起握手操作,实际开发中,主动发起的一方,就是所谓的"客户端”被动接受的一方,就是"服务器”,此时,握手完成.此时,A 和 B 记录了对方的信息(也就是构成了“逻辑"上的连接),建立连接的过程,其实是,通信双方都要给对方发起 syn, 也都要给对方反馈 ack.一共是 4 次握手了,但是中间两次,恰好可以合并成一次.
三次握手是要解决什么问题???通过四次握手,是否可行?
TCP 初心,是为了实现"可靠传输”,进行确认应答和超时应答有个大前提,当前的网络环境是基本可用的,通畅的,如果当前网络已经存在重大故障了,此时,可靠传输,无从谈起,3次握手就是检查网络是否畅通。四次握手可以,但是没必要,两个数据合并成一个数据效率更高.
三次握手的作用???
三次握手核心作用一: 投石问路,确认当前网络是否是畅通的
三次握手核心作用二: 要让发送方和接收方都能确认自己的发送能力和接收能力均正常
三次握手核心作用三: 让通信双方,在握手过程中,针对一些重要的参数,进行协商(这里协商的信息有好几个,现在不做过多讨论,但至少知道tcp 通信过程中的序号从几开始,就是双方协商出来的(一般不是从1开始的))
断开连接——四次挥手
建立连接,一般都是客户端主动发起,断开连接, 客户端和服务器都可以主动发起.
最后的FIN就叫做结束报文段
和三次握手不同,此处的四次挥手,能否把中间的两次交耳合二为一?
不一定,不能合并的原因,ACK 和 第二个 FIN 的触发时机是不同的, ACK 是内核响应的.B 收到 FIN,就会立即返回 ACK,第二个 FIN 是应用程序的代码触发,B 这边调用了 close 方法才会触发 FIN ,像前面的三次握手, ACK 和 第二个 syn 都是内核触发的. 同一个时机. 可以合并,这里的四次挥手,ACK 是内核触发的,第二个 FIN 是应用程序执行 close 触发的,时机不相同,不能合并.
是否意味着,如果我这边代码 close 没写/没执行到,是不是第二个 FIN 就一直发不出去??
有可能的,果是正常的四次挥手,“好聚好散”, 正常的流程断开的连接,如果是不正常的挥手(没有挥完四次),异常的流程断开连接,(也是存在的)
在四次挥手中,TIME _WAIT有什么意义
TIME WAIT 状态主要存在的意义,就是为了防止最后一个 ACK 丢失,留下的后手如果最后一个 ACK 丟了,站在 B的角度,B就会触发超时重传
重新把刚才的 FIN 给传一遍.如果刚才 A 没有 TIME WAIT 状态, 就意味着 A 这个时候就已经真的释放连接了,此时重传的 FIN 也就没人能处理,没人能返回 ACK 了.B 永远也收不到 ACK 了.A 这边使用 TIME WAIT 状态进行等待, 等待的这个时间, 就是为了处理后续 B 重传的 FIN
此时有重传的 FIN 来了,就可以继续返回 ACK 了,B 这边的重传 FIN 才有意义.
4.滑动窗口
因为TCP要保证“可靠传输”,所以在所难免会牺牲一些效率,而滑动窗口就是来尽可能的提升效率的,让可靠传输对性能的影响,更少一些,TCP 只要引入了可靠性,传输效率是不可能超过 没有可靠性的 UDP 的,TCP 这里的"效率机制"都是为了让 影响更小,缩短和 UDP 的差距,缩短和UDP的差距。
每次收到一个应答报文,再发下一个数据,这个过程中,等待时间比较长的,怎样缩短应答确认的时间?
批量传输数据不等 ack 回来, 直接再发下一个数据,批量传输,也不是“"无限的"传输,批量传输也是存在一定的上限的,达到上限之后,再统一等待 ack
不等待的情况下,批量最多发多少数据,这个数据量,称为"窗口大小"
当前 A ->B 是批量的发了四份数据,此时 B 也要给 A 回应四组 ACK,此时 A 已经达到窗口大小, 再收到 ACK 之前,不能继续往下发了,需要等待有 ACK 回来了之后, 才能继续往下发。
这里是怎么继续发的? 是等待四个 ack 都回来了,在继续发四条?
回来一个 ack,就立即继续发一个。
TCP 初心是"可靠传输",上述滑动窗口中,确认应答是可以正常工作的,但是,如果出现丢包了咋办??
这里的重传,相比于前面的超时重传,又有变化~~~
情况1:ACK丢了
出现这种情况,不需要任何的重传。
确认序号,表示的含义是,当前序号之前的数据,已经确认收到了,下一个你应该从确认序号这里,继续发送。如果 1001 这个 ack 丟了.但是 2001 ack 到了.
12001之前的数据都已经确认传输成功了.涵盖了 1001 的情况!!!
情况2:数据包丢了
由于前面的 1001-2000 这个数据没了,此处返回的 ack 仍然是索要 1001,无论当前传输的数据具体是几,都在索要1001 这个数据,此时,主机 A 看到了 B这边连续的几个ack,都是在索要 1001.A 就知道了1001 这个数据就是丢了,就重传了1001,上述的重传过程中,并没有额外的冗余操作,哪个数据丢了,就重传哪个,没丟的数据则不需要重传整个过程都是比较快速的,(快速重传——滑动窗口下,超时重传的变种)
如果 ack 全都丟了呢??
平时丢包率达到 10%,就已经非常非常高了,直接丢包率 100% 了,此时相当于网线都断了,无从谈起"可靠传输"
如果通信双方,传输数据的量比较小,也不频繁,就仍然是普通的确认应答和普通的超时重传.
如果通信双方,传输数据量更大,也比较频繁, 就会进入到滑动窗口模式, 按照快速重传的方式处理
通过滑动窗口的方式传输数据,效率是会提升的,窗口越大,传输效率就越大.(一份时间, 等待 的 ack 更多了,总的等待时间更少了)
滑动窗口, 设置的越大,越好嘛??
如果传输的速度太快,就可能会使接收方,处理不过来了.此时,接收方也会出现丢包,发送方还得重传,TCP 前提是可靠性,可靠性的基础上,再提高传输效率
5.流量控制
站在接收方的角度,反向制约发送方的传输速率,发送方发送的速率,不应该超过接收方的处理能力.
数据到达 B的系统内核中,tcp socket 对象上带有接收缓冲区.A ->B 发的数据, 就会先到达 B的接收缓冲区.B 这边还有应用程序,就会调用 read 这样的方法,把数据从接收缓冲区中读出来,进一步的进行处理.(一旦数据被 read 了,就可以从接收缓冲区删除了)
这里也就是我们以前提到的生产者消费者模型,生产者:A,消费者:B的应用程序,交易场所:B的接收缓冲区(相当于阻塞队列)
消费速度,就是所谓的"处理能力"(取决于 B 的应用程序代码是咋写的)
如何量化衡量???
直接通过接收方,缓冲区的剩余空间大小,作为衡量处理能力的指标,剩余空间越大,意味着消费速度越快,处理能力就越强,剩余空间越小,消费速度越慢,处理能力就越弱.接收方每次收到数据之后,都会把接收缓冲区剩余空间大小,通过 ack 返回给发送方,发送方就会按照这个数值来调整下一轮的发送速度.
6.拥塞控制
流量控制,是考虑的接收方的处理能力,不仅仅是接收方,还有你整个通信的路径
这中间的转发过程中,任何一个节点,处理能力达到上限,都可能会对发送方产生影响,都可能会影响到可靠传输,由于中间节点,结构更复杂,更难以直接的进行量化,因此就可以使用"实验"的方式,来找到个合适的值,让 A 先按照比较低的速度先发送数据(小的窗口),如果数据传输过程非常顺利,没有丢包,
再尝试使用更大的窗口,更高的速度进行发送,(一点一点变化),随着窗口大小不停的增大,达到一定程度,可能中间节点就会出现问题了,此时这个节点就可能会出现丢包,发送方发现丢包了,就把窗口大小调整小,此时如果发现还是继续丢包,继续缩小,如果不丢包了,就继续尝试变大
再这个过程中,发送方不停的调整窗口大小,逐渐达成"动态平衡",这种做法,就相当于把中间节点,都视为"整体”,通过实验的方式,来找到中间节点的瓶颈在哪里~~~
流量控制
拥塞控制
都是在限制发送方的发送窗口的大小.
最终时机,发送的窗口大小,是取 流量控制 和 拥塞控制 中的窗口的较小值
7.延时应答
A 把数据传给 B, B就会立即返回 ack 给A[正常]
也有的时候, A传输给B, 此时B等一会再返回 ack 给A[延时应答]
延时应答本质上也是为了提升传输效率.发送方的窗口大小, 就是传输效率的关键.流量控制这里,就是根据接收缓冲区的剩余空间,来决定发送速率的,
如果能够有办法,让这个流量控制得到的窗口更大点,发送速率就更快点,(大点的前提,能够让接收方还是能处理过来的),延时返回 ack,给接收方更多的时间, 来读取接收缓冲区的数据,此时接收方读了这个数据之后, 缓冲区剩余空间, 变大了~~返回的窗口大小也就更大了,初始情况下,接收缓冲区剩余空间是 10kb,如果立即返回 ack,返回了 10kb 这么大的窗口,如果延时个 200ms 再返回,这 200ms 的过程中,接收方的应用程序的,又读了 5 kb,此时,返回的 ack, 就可以返回 15kb 的窗口了
8.捎带应答
在延时应答的基础上,进一步的提高效率,网络通信中,往往是这种"一问一答"这样通信模型
ack 也是内核立即返回的,response 则是应用程序代码来返回的,这两者时机是不同的,由于 tcp 引入了延时应答,上面的 ack,不一定是立即返回,可能要等一会在等一会的过程中,B 就正好把 response 给计算好了.计算好了之后就会把 response 返回,于此同时顺便就把刚才要返回的 ack 也带上了
这两个数据,就合并成了一个数据了本来是要传输两个 tcp 数据包 (封装分用两遍),现在通过上述操作,就可以把两个包合并成一个了,此时就可以得到更高效的效果。
9.面向字节流
创建⼀个TCP的socket, 同时在内核中创建⼀个 发送缓冲区 和⼀个 接收缓冲区。
• 调⽤write时, 数据会先写⼊发送缓冲区中;
• 如果发送的字节数太⻓, 会被拆分成多个TCP的数据包发出;
• 如果发送的字节数太短, 就会先在缓冲区⾥等待, 等到缓冲区⻓度差不多了, 或者其他合适的时机发送出去;
• 接收数据的时候, 数据也是从⽹卡驱动程序到达内核的接收缓冲区;
• 然后应⽤程序可以调⽤read从接收缓冲区拿数据;
• 另⼀⽅⾯, TCP的⼀个连接, 既有发送缓冲区, 也有接收缓冲区, 那么对于这⼀个连接, 既可以读数据,也可以写数据. 这个概念叫做 全双⼯
由于缓冲区的存在, TCP程序的读和写不需要⼀⼀匹配, 例如:
• 写100个字节数据时, 可以调⽤⼀次write写100个字节, 也可以调⽤100次write, 每次写⼀个字节;
• 读100个字节数据时, 也完全不需要考虑写的时候是怎么写的, 既可以⼀次read 100个字节, 也可以⼀次read⼀个字节, 重复100次;
10.粘包问题
粘包问题 (不是 tcp 独有的,而是面向字节流的机制都有类似的情况)
此处“包"应用层数据包,如果同时有多个应用层数据包被传输过去,此时就容易出现粘包问题
目前,接收缓冲区中,这三个应用层数据包的数据,就是以字节的形式紧紧挨在一起的,接收方的应用程序,读取数据的时候,可以一次读一个字节,也可以读两个字节, 也可以读 N 个字节....但是最终的目标是为了得到完整的应用层数据包,B 应用程序,就不知道,缓冲区里的数据。从哪里到哪里是一个完整的应用数据包了。
相比之下,像 UDP 这样的面向数据报的通信方式,就没有上述问题
UDP 的接收缓冲区中,相当于是一个一个的 DatagramPacket 对象,应用程序读的时候,就能明确知道哪里到哪里是一个完整的数据
那如何解决粘包问题?
核心思路: 通过定义好应用层协议,明确应用层数据包之间的边界.
1.引入分隔符
2.引入长度.
11.异常情况的处理
如果在使用 tcp 的过程中,出现意外,会如何处理?
1)进程崩溃:
进程没了,异常终止了,文件描述符表,也就释放了,相当于调用 socket.close0),此时就会触发 FIN, 对方收到之后,自然就会返回 FIN 和 ACK,这边再进行 ACK(正常的四次挥手断开连接的流程),TCP 的连接,可以独立于进程存在,(进程没了,TCP 连接不一定没)
2)主机关机(正常流程):
在进行关机的时候,就是会先触发强制终止进程操作,(相当于 1)此时就会触发 FIN, 对方收到之后, 自然就会返回 FIN 和 ACK.
不仅仅是进程没了,整个系统也可能关闭了,如果在系统关闭之前,对端返回的 ACK 和 FIN 到了,此时系统还是可以返回 ACK,进行正常的四次挥手的,如果系统已经关闭了,ACK 和 FIN 迟到了,无法进行后续ACK 的响应,站在对端的角度,对端以为是自己的 FIN 丢包了,重传 FIN,重传几次都没有响应,自然就
会放弃连接.(把持有的对端的信息就删了)
3)主机掉电(非正常):
这里说的是台式机哦,不是笔记本电脑
此时,是一瞬间的事情,来不及杀进程,也来不及发送 FIN,主机直接就停机了,站在对端的角度,对端不一定知道这个事情咋搞~~
1.如果对端是在发送数据(接收方掉电),发送的数据就会一直等待 ack,触发超时重传,触发 TCP 连接重置功能.发起"复位报文段”,如果 复位报文段 发过去之后,也没有效果,此时就会释放连接了。
2.如果对端是在接收数据 (发送方掉电),对端还在等待数据到达...,等了半天没消息,此时其实无法区分,是对端没法消息还是对方挂了
TCP 中提供了 心跳包 机制.
接收方也会周期性的给发送方发起一个特殊的,不携带业务数据的数据包, 并且期望对方返回一个应答,如果对方没有应答,并且重复了多次之后,仍然没有,就视为对方挂了.此时就可以单方面释放连接了.
4)网线断开:
网线断开,和刚才的主机掉电非常类似的,当前假设,是 A 正在给 B 发送数据, 一旦网线断开
A 就相当于就会触发超时重传 ->连接重置 ->单方面释放连接
B 就会触发心跳包 ->发现对端没响应 ->单方面释放连接.
2.3TCP 和 UDP 之间的对比~~
TCP 优势 可靠传输 /TCP 适用于绝大部分场景.
UDP 优势 更高效率' UDP 更适合于, 对于"可拿性不敏感”,"性能敏感”场景,比如说:局域网内部(同一个机房)的主机之间通信同一个机房内部,网络结构简单,带宽充足,交换机/路由器网络设备负载程度也不是很高出现丢包的概率就不大.往往也希望机器之间数据传输能更快.
如果要传输比较大的数据包,TCP 更优先(UDP 有 64KB 的限制),如果要进行"广播传输",优先考虑 UDP. UDP 天然支持广播,TCP 不支持 (应用程序额外写代码实现),有一种特殊的场景,需要把数据发给局域网的所有的机器这个情况就是广播(手机投屏)
经典面试题: 如何基于 UDP 实现可靠传输??
这个问题本质上是考察 TCP !!
1)确认应答
2)引入序号 确认序号
3)超时重传
4)滑动窗口
...........
三 .网络层
网络层要做的事情,主要是两方面,
1)地址管理:制定一系列的规则,通过地址,描述出网络上一个设备的位置,
2)路由选择:网络环境比较复杂的,从一个节点到另一个节点之间,存在很多条不同的路径,就需要通过这种方式,筛选/规划出更合适的路径进行数据传输
3.1 IP协议
3.1.1协议头格式
• 4位版本号(version): 指定IP协议的版本, 对于IPv4来说, 就是4.
• 4位头部⻓度(header length): IP头部的⻓度是多少个32bit, 也就是 length * 4 的字节数. 4bit表⽰最⼤的数字是15, 因此IP头部最⼤⻓度是60字节.
• 8位服务类型(Type Of Service): 3位优先权字段(已经弃⽤), 4位TOS字段, 和1位保留字段(必须置为0). 4位TOS分别表⽰: 最⼩延时, 最⼤吞吐量, 最⾼靠性, 最⼩成本. 这四者相互冲突, 只能选择⼀个.对于ssh/telnet这样的应⽤程序, 最⼩延时⽐较重要; 对于ftp这样的程序, 最⼤吞吐量⽐较重要.
• 16位总⻓度(total length): IP数据报整体占多少个字节.
• 16位标识(id): 唯⼀的标识主机发送的报⽂. 如果IP报⽂在数据链路层被分⽚了, 那么每⼀个⽚⾥⾯的这个id都是相同的.
• 3位标志字段: 第⼀位保留(保留的意思是现在不⽤, 但是还没想好说不定以后要⽤到). 第⼆位置为1表⽰禁⽌分⽚, 这时候如果报⽂⻓度超过MTU, IP模就会丢弃报⽂. 第三位表⽰"更多分⽚", 如果分⽚了的话, 最后⼀个分⽚置为1, 其他是0. 类似于⼀个结束标记.
• 13位分⽚偏移(framegament offset): 是分⽚相对于原始IP报⽂开始处的偏移. 其实就是在表⽰当前分⽚在原报⽂中处在哪个位置. 实际偏移的字节数是这个值 * 8 得到的. 因此, 除了最后⼀个报⽂之外, 其他报⽂的⻓度必须是8的整数倍(否则报⽂就不连续了).
• 8位⽣存时间(Time To Live, TTL): 数据报到达⽬的地的最⼤报⽂跳数. ⼀般是64. 每次经过⼀个路由, TTL -= 1, ⼀直减到0还没到达, 那么就丢弃了. 这个字段主要是⽤来防⽌出现路由循环
• 8位协议: 表⽰上层协议的类型
• 16位头部校验和: 使⽤CRC进⾏校验, 来鉴别头部是否损坏,不管IP数据的载荷
• 32位源地址和32位⽬标地址: 表⽰发送端和接收端.
3.1.2地址管理
IPv4地址由 32位二进制数 构成,理论上,32位二进制可组合的地址数量为约42.9亿个,在当时看起来还觉得很多,但互联网发展到现在,能上网的设备越来越多,还有最近几年很火的物联网,汽车,智能家居.......IP地址早就已经不够用了
如何解决IP地址不够用的问题???
我们有两种解决方案
方案一:动态分配IP,但是治标不治本,提高了IP地址的利用率,但并没有增加IP地址的数目(没什么卵用)
方案二:NAT机制(网络地址转换)
假设这么一个场景,你是一名大学生,你的周围有着和你一样成千上万的大学生,你们在网络上购物所填的地址都是这个大学所在的地址
所以把IP地址分成两大类:
1)内网IP(局域网):
在同一个局域网中,内网IP之间不能重复,在不同的局域网之间,内网IP之间可以重复
如果一个 |P 地址,是以 10.*或者 172.16.- 172.31.*或者 192.168.*(复合上述条件之一,IP 就是内网 IP)
2)外网IP(广域网IP)
外网IP始终都不允许重复,始终唯一
在现在的社会环境下,通常都是一个小区/学校/公司构成一个大的局域网,这样的局域网,就使用一个外网IP。
NAT是怎么工作的???
此时,运营商路由器也是一个NAT设备,对这里的源IP进行替换,也就相当于一个中转站。
如果当前局域网内,有多个主机, 都访问同一个网站服务器,此时服务器返回的响应经过当前的路由器之后,要交还给哪个主机呢(路由器还能记住谁是谁吗)???
可以看到,虽然IP一样,但是这两个请求来自不同的端口,返回响应的数据自然也会带着不同的目的端口
服务器返回的两条数据一个端口号是6666,一个是8888,路由器就知道6666的这个,就需要把IP换回第一主机的IP(192.168.0.100),8888这个就把IP换回第二个主机的IP(192.168.0.200)
当服务器返回数据之后,路由器如何决定这个数据要交给哪个设备?
是要结合端口号来进行区分!!!"端口号,可以用来区分同一个主机的不同进程,也可以区分不同主机的不同进程!
那么问题又来了,那恰好有两台电脑的端口号也一样,咋办???
这时候,路由器就主动地把相同的端口替换成不同的端口
NAT 机制最大的优势就是"纯软件的方案",不需要进行硬件升级,也正是因为这个机制,局域网内部的设备,能够主动访问外网的设备
外网的设备无法主动访问局域网内部的设备~
说到最后,其实IPV6才是最终解~~~~
3.1.3网段划分
把一个 IP 地址,会分成两个部分,网络号(标识了一个局域网) + 主机号(标识了局域网中的一个设备)
在第一个绿色的框内,也就是在一个局域网中,网络号为192.168.1,后面的.1 .10 .11 .12为主机号
第二个局域网中网络号 192.168.0主机号.1 .10 .11 .12,同一个局域网中的设备,网络号必须相同,主机号必须不同,两个相邻的局域网,网络号不能相同,通过一个路由器,连接一个局域网。
如果一个!P 地址,主机号全 0,当前这个 IP 就表示"网络号",192.168.100.0,代表一个局域网,一个具体的主机,是不能分配这个 IP 的
如果一个 IP 地址,主机号全 1, 表示当前这个 IP 就是一个"广播地址" ,192.168.100.255,也是具体的主机,是不能分配这个 IP 的
如果一个 IP 是 127 开头的, 此时这个 IP 就是"环回 ip",127.0.0.1 (最常用的)表示"设备自身” 自己发给自己~~
3.1.4路由选择
路由选择,就是描述了 IP 协议(IP 数据报) 转发过程,从 A->B,中间可能有很多条可行的路径,具体怎么走~,而进行IP 数据报转发的时候,每个路由器,都是无法知道网络的“全貌”的,只知道一些局部信息.(一个路由器能知道哪些设备和它自己是相连的),这就意味着 IP 数据在转发过程中, 是一个"探索式""启发式"过程.
路由器转发数据报的过程还比较复杂,咱们通过一个简单的例子来介绍
我在我们学校中要从我们宿舍去操场,但是我不认识路(假设没有导航地图),但是我会问路啊!
1.遇到了A
A说他不知道怎么走,但是听说操场在东区,建议我往东走
2.往东走的时候遇到了B
B也不知道怎么走,只知道在三食堂的附近
3.到了三食堂附近遇到了C
C说往北走两条街就是操场了
前面几个人,虽然不知道操场在哪,但是给我指出了一个逐渐接近的方向,最后距离比较近的时候,C就已经知道操场在哪里了,就可以完成最后的一段路线了~~
一个网络层的数据报,每次到达一个路由器,也会进行上述"问路"过程,每个路由器内部都有一个数据结构的”路由表“,更具数据包中的目的IP,查路由表,如果查到了 (问的人,恰好知道咋走,,就直接按照路由表给定的方向(从哪个网络接口进行转发),继续转发就行了,如果没査到 (问的人,不知道咋走),路由表里有一个"默认的表项”(下一跳地址),按照默认的表项转发即可~~
四.数据链路层
4.1认识以太网与mac地址
这里也有很多种协议,其中一个比较常见常用的,就是"以太网协议",通过网线/光纤,来通信,使用的协议,以太网协议,以太网,横跨数据链路层 +物理层
以太数据帧的格式为帧头+载荷(IP数据包)+帧尾
这里6个字节的目的地址不是IP地址,是网络中另一套地址体系,mac地址(物理地址),mac 地址由于是6个字节,能表示的范围,比IP 地址大了很多~~
IP 地址虽然早都不够用了,但是 mac 还是够用的.目前来说,每个设备都是有唯一的 mac 地址~~,mac 地址通常是 十六进制 表示的两个十六进制数字就是一个字节,字节和字节之间通常使用-或者:来分割
4.2对比理解IP地址和MAC地址
IP 地址和 Mac 地址各自的用途是什么?
IP 协议立足于全局,完成整个通信过程的路径规划工作
以太网 则是关注于局部, 相邻两个设备之间的通信过程~.
我要回老家(运城->双鸭山)
网络层关注的是路径是什么样的??
运城->太原->哈尔滨->双鸭山
运城->太原->石家庄->哈尔滨->双鸭山
运城->哈尔滨->双鸭山
.......
无论走哪条路,IP数据包里的源IP始终是运城,目的IP始终是双鸭山
网络层关注的是这么多路径我走哪条路
数据链路层关注的是路径是什么样的??
运城->太原:坐高铁
数据报:
源IP:运城 目的IP:双鸭山
源mac地址:运城 目的mac地址:太原
太原->哈尔滨:坐火车
数据报:
源IP:运城 目的IP:双鸭山
源mac地址:太原 目的mac地址:哈尔滨
源 IP 和 目的 IP 始终是整个通信过程中的最初的起点和终点(这里是不考虑 NAT 的情况),源 mac 和 目的 mac 会根据你当前转发的过程每次到达一个节点,往下一个节点转发的时候,源 mac 和 目的 mac 都会随之改变
4.3什么是MTU
MTU: 数据链路层的数据报能携带的最大载荷长度,不同数据链路层的协议的 MTU 是不一样的.MTU 可以考虑成一辆汽车的载重量~~
路是土路,汽车太重了,就容易压坏~~路是沥青的路~~ 载重量就更大路是高速公路级别的,载重量就更大~~
像交换机这样的设备,收到以太网数据帧的时候,就需要进行转发,(这个转发过程就需要能够根据 mac 地址, 判定出数据要走哪个网口,这里的网口是"物理意义"上插网线的口
具体如何转发?
交换机内部也有一个数据结构"转发表”和前面说的路由表, 有点像, 其实还是有一系列差别~~
转发表是一个简单的像 hash 这样的映射(当然这个表不一定是软件实现的,也可能是硬件实现的)
4.4DNS应用层协议
DNS是一套域名解析系统,使用IP地址来描述设备在网络上的位置像我们熟悉的www.baidu.com......,需要有一套 自动的系统把域名翻译成IP地址
如果你想访问某个域名,就先给这个 DNS 服务器发起请求,査询一下当前 域名 对应的 ip,然后再访问目标网站
全世界,无时不刻都有很多设备需要进行 DNS 的请求,这一组DNS服务器,能抗住这么高的请求量吗???
这种所谓的高并发,其实没有那么复杂,核心思路就是两条:
1)开源
搭建DNS系统的大佬们,就开始号召各个网络运营商,你们都可以自己搭建一组"DNS 镜像服务器",镜像服务器的数据,都从他们这边来同步此时用户就会优先访问离自己最近的镜像服务器.
2)节流
让请求量变少,让每个上网的设备,搞本地缓存我的电脑 1min 之内要访问 10 次 www.sogou.com只是让第一次请求 DNS 即可. 把请求得到的结果保存到本地,后面 9 次请求都使用第一次的结果即可~~(域名的变换,没有那么频繁)
好了,为了避免文章太长就先写到这里了,我会在后面的文章中详细介绍http和servlet