一、应用层
二、传输层
2.1 UDP
UDP 的数据报文格式:由 UDP 报头、UDP 数据载荷(完整的应用层数据)组成。把应用层数据报分装成 UDP 数据报,本质上就是在应用层数据包的基础上,添加了 8 个字节的报头。
- 目的端口:即为 服务器端口。
- 源端口:即为 操作系统给客户端自动分配的端口。端口号会被打包到 UDP 数据报中。
- 报文长度是 2 个字节,0-64k 在现代互联网当中,就可能不够用了。数据大的话,就会进行拆包,然后分为多个发送,接收方再重新拼接起来,但是可能回出现丢包和乱序,所以就改成 TCP 来解决,因为 TCP 没有大小限制。
- 校验和就是用来验证网络传输的这个数据是否正确。因为网络传递的本质就是:光信号和电信号,但是外界的磁场之类的会影响到结果。可能就会导致 0 变成 1,就导致数据出错了,所以校验和就能帮助我们发现数据中的错误。还可以使用数据内容参与运算,如果是基于数据内容得到的校验和,那么数据出错的话,被识别出来的概率还是很高的。这里的 校验和 与 以太网 的校验和一样。
2.2 TCP
TCP,即 Transmission Control Protocol,传输控制协议。就是对数据的传输进行一个详细的控制。
TCP 协议段格式
- 源/目的端口号:表示数据是从哪个进程来,到哪个进程去。
- 32 位序号/32 位确认序号:后续补充
- 4 位首部长度:4 位就是 4 个 bit 位的意思(取值范围 0~15),来表示 TCP 报头的长度。
注意:有 4 位,只能表示 0~15,因此这里的单位是 4 字节。例如,4 位 bit 数:1111,此时的报头长度为:15 * 4 = 60 字节。即为,4 位 bit 数转化成 10 进制数,再乘以 4,得出的结果就是报头的长度。
不同于 UDP,TCP 的报头的变长的,不像 UDP 是固定的 8 字节。
原因:在于 TCP 协议段格式中 “选项” 一栏。因为这个选项,可有可无,可有一个或者多个,因此TCP的报头不是固定的长度。
-
保留位:现在不用,但是不能保证后面不会用到。为了未来升级,留有空间。
比如 UDP,把报文长度的 2 字节改成 4 字节,不好意思,改不了。除非把全世界的 UDP 都改了,但明显不现实。因此我们想升级一个协议是一件相当麻烦的事情。
当对于推翻之前的格式,不如在设计协议之初,留有一点可扩展的空间。 -
6 位标志位
:
- URG:紧急指针是否有效。
- ACK:确认号是否有效。
- PSH:提示接收端应用程序立刻从TCP缓冲区把数据读走。
- RST:对方要求重新建立连接;我们把携带RST标识的称为复位报文段。
- SYN:请求建立连接;我们把携带SYN标识的称为同步报文段。
- FIN:通知对方,本端要关闭了,我们称携带FIN标识的为结束报文段。
-
16 位窗口大小:后续补充
-
16 位校验和:发送端填充,CRC校验。接收端校验不通过,则认为数据有问题。此处的检验和不 光包含TCP首部,也包含TCP数据部分。
-
16位紧急指针:标识哪部分数据是紧急数据。
-
40字节头部选项:暂时忽略。
可靠传输
可靠传输是 TCP 的核心机制。引入 TCP 的关键原因,就是为了保证可靠传输。
实现可靠传输的核心:
- 确认应答
- 超时重传
确认应答机制(可靠性)
- 确认应答就是发出去之后,对方收到了。但是消息可能会乱序。
- 避免出现乱序,就可以根据编号机制来解决,根据编号就可以知道消息是第几条了。
- 就是根据编号来确定当前的 应答报文 就是针对哪个消息进行的确认应答。
就像下面这样:
A 给 B 发送了 1000 个字节,序号是 1 - 1000。B 给 A 返回的应答报文(ACK)就会带有一个确认序号,为 1001(这个 1001 代表的是 小于 1001 的数据已经被 B 收到了,接下来 A 应该从 1001 这个序号开始往后进行传递)
确认应答的作用:
超时重传机制(可靠性)
- 主机A发送数据给B之后,可能因为网络拥堵等原因,数据无法到达主机B;
- 如果主机A在一个特定时间间隔内没有收到B发来的确认应答,就会进行重发;
但是,主机A未收到B发来的确认应答,也可能是因为ACK丢失了;
如果是 ACK 丢了,虽然对方收到了消息,但是我收不到 ACK(ACK 丢了),发送方超出了等待时间,就会重新发送一次。那么就会导致接收方收到了重复的消息:
- TCP 内部会有一个去重操作,接收方收到的数据会先放在操作系统的 接收缓冲区 当中的。
- 收到新的数据,TCP 就会去 接收缓冲区(一段内存,每个 socket 都有,按照序号来进行去重) 当中检查有没有这个消息,如果有,就丢弃,没有的话,就放到缓冲区。用来保证程序调用 socket API 拿到的这个数据一定是不可重复的。
重传如果失败,还会继续尝试,也不会无休止的重传,连续几次失败的话,就认为是网络遇到了严重的情况,再怎么重传也可能不行,就只能放弃(自动断开与 TCP 的连接)。重传的时间间隔会逐渐变大。
如何确定超时的时间?
- 最理想的情况下,找到一个最小的时间,保证 "确认应答一定能在这个时间内返回"。
- 但是这个时间的长短,随着网络环境的不同,是有差异的。
- 如果超时时间设的太长,会影响整体的重传效率。
- 如果超时时间设的太短,有可能会频繁发送重复的包。
TCP为了保证无论在任何环境下都能比较高性能的通信,因此会动态计算这个最大超时时间。
- Linux中(BSD Unix和Windows也是如此),超时以500ms为一个单位进行控制,每次判定 超时重发的超时时间都是500ms的整数倍。
- 如果重发一次之后,仍然得不到应答,等待 2*500ms 后再进行重传。
- 如果仍然得不到应答,等待 4*500ms 进行重传。依次类推,以指数形式递增。
- 累计到一定的重传次数,TCP认为网络或者对端主机出现异常,强制关闭连接。
连接管理机制(可靠性)
连接管理也是 TCP 保证可靠性的一个机制,主要是两部分:
- 如何建立连接。
- 如何断开连接。
如何建立连接(三次握手)
客户端和服务器之间,通过三次交互(三次握手),完成了建立连接的过程。三次握手只是一个形象化的比喻:
- 其实一次握手,就是一次交互的过程。就是客户端 给 服务器 发了一个数据,这就相当于一次握手。
- 服务器再给客户端反馈一个数据,这就是另外一次握手。
- 客户端 根据服务器的反馈,而进行反馈,告诉服务器,它接受到了它的反馈。
一共经历3次,就完成这个三次握手。
简化流程图如下:
第一次握手的时候客户端给服务器发一个 SYN 同步报文段。意思就是客户端要和服务器建立连接。
当** SYN 为 1** 时,表示当前报文为一个 **同步报文段。**即为 主机 A 与 主机 B 之间要建立连接。
- 客户端 先给 服务器 发起一个 SYN 同步请求:
当服务器收到这个请求时,就会立即做出回应(ACK),表示我收到了你的SYN。
- 服务端发送 ACK 的同时,也要发送一个 SYN,表示服务器想和客户端建立连接:
(此处服务器的 ACK和SYN 可以合并到一起)
- 客户端收到服务器的 SYN 之后,也会立即返回一个 ACK:
这样的话,双方各自向对方发送 SYN,再各自向对方发送 ACK,双向奔赴之后就建立连接了。
如果 ACK 这一位为 1,就表示这个报文是一个确认报文段(确认应答报文也就是 ACK 为 1)。ACK 这一位为 1 的时候,起到一个应答效果,用来确认消息是已经送到了的。
注意:每次要传输的数据,都要结果一系列的封装和分用,才能完成传输。如果 服务器 的 SYN 和 ACK 分开发送的话,就会导致封装的次数变多,导致效率变低。
只要把六个标志位中的 ACK 和 SYN 变为 1。就可以封装在一起了。
封装在一起后的样子:即为 三次握手。
常见的状态:
- LISTEN:表示服务器启动成功,端口绑定成功,随时可以有客户端来建立连接。就像是手机开机,有信号,可以随时接电话。
- ESTABLISHED:表示当前客户端已经连接成功,随时可以进行通信。类似有人给我打电话,已经接通了,可以进行交流了。
如何断开连接(四次挥手)
三次握手,就让客户端和服务器之间建立好了连接。其实在建立好连接之后,操作系统内核当中,就需要使用一定的数据结构来保存连接的相关的信息,保存的信息其实最重要的就是前面说的五元组:源IP、源端口、目的IP、目的端口、TCP。有一天,连接断开了,那么五元组保存的连接信息就没意义了,对应的空间就可以释放了。
四次挥手,就是和对方断开连接:
就是双方各自向对方发送了 FIN(结束报文段)请求,并且各自给对方一个 ACK 确认报文。这样的话,就完成了四次挥手。四次挥手就是各自和对方说断开连接。
四次挥手中重要的状态:
- CLOSE_WAIT:
- 四次挥手挥了两次之后出现的状态,这个状态就是在等待代码中调用
socket.close()
方法,来进行后续的挥手过程。 - 正常情况下,一个服务器上面不应该存在大量的 CLOSE_WAIT 如果存在,说明大概率是代码 bug,close 没有被执行到。
- TIME_WAIT:
- 谁主动发起 FIN(先断开连接的一方),谁就进入 TIME_WAIT。
- 起到的效果:就是给最后一次 ACK 提供重传机会,表面上看起来 A 发送完 ACK 之后,就没有 A 的啥事了。
- 按理说 A 就应该销毁链接,释放资源了,但是并没有直接释放,而是会进入 TIME_WAIT 状态等待一段时间,一段时间后,再来释放资源。等这一会儿,就是怕最后一个 ACK 丢包了,如果丢包了,就意味着 B 过一会儿就会重传 FIN。
- 如果最后一个 ACK 丢了,B 就无法区别是 FIN 丢了,还是 ACK 丢了。于是 B 就假设 FIN 丢了,就重传 FIN(超时重传)。因为如果断开的太早,就没办法继续重传了。
- TIME_WAIT 持续时间:2*MSL。MSL:表示网络上任意两点之间,传输需要的最大时间。这个时间也是系统上可以配置的参数,一个典型的设置就是:60s(随便想的时间)。
滑动窗口(效率)
滑动窗口存在的意义是,保证可靠性的前提下,尽量提高传输效率。
以下是不使用滑动窗口的传输机制:
从图里就可以看到,由于确认应答机制存在,就导致了当前每次执行一次发送数据,都需要等待上一个 ACK 到达,大量的时间都浪费在等待 ACK 上面了,就导致效率变低了。
使用滑动窗口就是一次发送一堆数据:
就相当于是一次发了 4 组数据,在发生这 4 组数据的过程中,不进行等待,这四组都发完了再统一等。
即为,把等待多份 ACK 的时间压缩成等待一份 ACK 了。
- 窗口大小指的是无需等待确认应答而可以继续发送数据的最大值。上图的窗口大小就是4000 个字节(四个段)。
- 发送前四个段的时候,不需要等待任何ACK,直接发送。
- 收到第一个ACK后,滑动窗口向后移动,继续发送第五个段的数据;依次类推。
- 操作系统内核为了维护这个滑动窗口,需要开辟 发送缓冲区 来记录当前还有哪些数据没有 应答;只有确认应答过的数据,才能从缓冲区删掉。
- 窗口越大,则网络的吞吐率就越高。
当 ACK(2001)到达之后,就认为 1001 - 2000 的数据已经收到了,接着就可以发送下一组的数据 2001 - 3000,等待的 4 组ACK发生了变化:3001,4001,5001,6001。
滑动窗口的窗口越大,就可以认为传输速度越快,窗口大了,同一份时间内等待的 ACK 就更多。总的等待 ACK 的时间就少了。
丢包
那么如果出现了丢包,如何进行重传?这里分两种情况讨论。
情况一:数据包已经抵达,ACK 被弄丢了。
- 在发送 4001 的数据之前,发现收到了一个 2001 的 ACK,1001 并没有收到.
- 这里的 1001 已经不重要了。因为 2001 的意思就是:2001 之前的数据全都已经确认收到了。
- 由于 ACK 确认序号的含义,就保证了后一条 ACK 就能涵盖前一条。
- 确认序号的含义:表示当前序号之前的数据全都收到了。
- 就像发送方收到了 5001,就意味着 1-5000 的数据都确认收到了,3001、4001 被丢包也毫不影响。只要收到了 5001,就涵盖了 3001、4001 表达的信息。
情况二:数据包丢了。
由于 1001-2000 这个数据丢了,所以 B 就再反复索要 1001 这个数据。即使 A 给 B 已经发后面的数据了,这个时候 B 仍然再索要 1001 这个数据。
当 A 被 B 索要多次之后,A 就明白了这个数据丢了,就会触发重传。
当 B 收到这个数据的时候,返回的 ACK 直接就是 7001 了。
在重传 1001 - 2000 的数据之前,接收缓冲区如下:
重传之后就是把缺口补上了,后面那些数据已经传过了,就不用再重传了,然后 B 就向 A 索要从 7001 开始的内容就好了:
快速重传就像是看直播的时候有事忙,等忙完的时候把拉下的一部分再补起来就好了。
流量控制(可靠性)
流量控制是对滑动窗口的进一步补充,本质上就是在控制滑动窗口的大小,目的也是为了可靠性。
- 滑动窗口越大,传输速率越高,不光要考虑发送方,还得考虑接收方。
- 发送方发的太快,接收方处理不过来,就会把新收的包给丢了,发送方还得重传。
- 流量控制的关键是能够衡量接收方的速度,直接使用接收方接受缓冲区的剩余空间大小,来衡量当前的处理能力。
- 可以把接收缓冲区理解为一个阻塞队列。B 的应用程序,在调用 read 方法的时候,就是从接收缓冲区中来取数据。通过 ACK 报文来看缓冲区大小,如果接收方反馈的窗口大小是 0,那么发送方也会发一个信息,用来探测有没有空间。
就像下面的图片,类似于一个生产者消费者模型:
主机 A 发送的数据到达了主机 B 的接收缓冲区,此时主机 A 就是生产者。
主机 B 的应用程序,通过 socket api 来读取数据,被 socket api 读到的数据就从缓冲区中删掉了,应用程序就是消费者。
接收缓冲区就是交易场所(类似与一个队列)
所说的窗口大小,是指发送方(主机 A)批量发送多少数据。比如,主机 A 发送的数据很快,窗口很大,此时接收缓冲区的数据也会增长很快。如果主机 B 的应用程序读取数据读的不快,随着时间的推移,接收缓冲区逐渐就满了。
如果不做任何限制,主机 A 还是按照往常速度发,此时新来的数据就没有地方保存了,就被内核丢弃了。
这个过程就像是一个水池一样:
水位高了(剩余空间小了),进水就要慢点。
水位低了(剩余空间大了),进水就要快点。
探测接收缓冲区剩余大小的具体流程:
首先 A 给 B 发送 1 - 1000 数据的时候,B 返回数据的时候会返回接收缓冲区还剩 3000(就是 B 主机 1001 ACK 的右边的 3000) 的空间,那么下一次 A 就可以一次发送 3000 数据了。当 B 收到 3000 数据的时候,剩余缓冲区的大小就是 0 了。不过要注意的是: 当接受缓冲区大小为 0 的时候,A 也会继续发送窗口探测数据,当探测到接受缓冲区剩余大小又可以发送数据的时候,就可以继续发送数据了。
窗口大小(接收区的剩余空间)是如何返回给发送方的呢?
在前面提到的 TCP 协议段中,有 16 位窗口大小来表示当前剩余的空间,当收到 ACK 时,就能知道当前窗口剩余空间的大小了。
那么问题来了,16位数字最大表示65535,那么TCP窗口最大就是65535字节么?
实际上,TCP首部40字节选项中还包含了一个窗口扩大因子M,实际窗口大小是 窗口字段的值左移 M
位。
拥塞控制(可靠性)
拥塞控制也是滑动窗口的延申,也是限制滑动窗口的发送速率。控制衡量的是:
- 发送方到接收方 整个链路之间的拥堵情况,都会影响传输速率。
- 开始的时候,以一个比较小的窗口来发送数据,如果数据很流畅的到达了,那么就加大窗口大小。
- 加大到一定程度之后,出现了丢包(就意味着链路出现拥堵了),然后再减小窗口。
- 通过反复的 增大/减小 最终达到”动态平衡“。通过拥塞窗口来限制滑动窗口的大小。
- 最终滑动窗口的大小 由 拥塞窗口 和 流量控制窗口共同决定,由小的一方决定。一旦丢包,就立刻让窗口变小,降低速度,然后继续去试探的增大。
- 如果把窗口一下变得很小,就是期望这次传输,一定能成功。
下面的图就描述了拥塞控制中,窗口大小的变化规则:
- x 轴上的 传输轮次 就是第几次传输数据。
一开始指数增长是因为,初始窗口太小,而我们实际上可能触发丢包的值很大。因此,指数增长可以帮我们快速找到丢包的界限。
但是增长到一定的程度时,就会进入线性增长。因为即将达到丢包的界限,如果再来指数增长,就可能一次越界,直接触发丢包。如下图:
当线性增长达到丢包的界限,发生丢包之后,就让拥塞窗口立即变小,回归到初始窗口的大小。一次变得很小之后,也就是希望这次传输一定能成功。如下图:
延时应答(效率)
如果接收数据的主机立刻返回ACK应答,这时候返回的窗口可能比较小。
- 假设接收端缓冲区为1M。一次收到了500K的数据;如果立刻应答,返回的窗口就是500K。
- 但实际上可能处理端处理的速度很快,10ms之内就把500K数据从缓冲区消费掉了。
- 在这种情况下,接收端处理还远没有达到自己的极限,即使窗口再放大一些,也能处理过来。
- 如果接收端稍微等一会再应答,比如等待200ms再应答,那么这个时候返回的窗口大小就是 1M。
捎带应答(效率)
在延迟应答的基础上,我们发现,很多情况下,客户端服务器在应用层也是 "一发一收" 的。意味着客 户端给服务器说了 "How are you",服务器也会给客户端回一个 "Fine, thank you";
那么这个时候ACK就可以搭顺风车,和服务器回应的 "Fine,thank you" 一起回给客户端。
面向字节流(粘包问题)
面向字节流会引起:粘包问题。指的是在应用层数据报,在 TCP 缓冲区中,若干个应用层数据报混在一起了,分不出来谁是谁。如下图:
如何解决粘包问题呢?
通过设计一个合理的应用层协议来解决,核心就要明确应用层数据中,包和包之间的边界,以下有两种方式:
- 给应用层数据设定 “结束符” 或者 “分隔符”。
举例:以 分号 为结束符:
- 给应用层数据设定 “长度”。
举例:约定每个应用层数据报的前 4 个字节,存储数据报的长度:
TCP 异常情况
进程终止
触发关闭文件的操作,正常的关闭。如,任务管理器里面的结束任务:
不管进程是怎么终止的,本质上都会释放对应的 PCB,也会释放对应的文件描述符,一样会触发 四次挥手。
“进程终止” 不代表连接就终止,进程终止其实是相当于调用了 socket.close()
而已。
机器重启
机器掉电/网线断开
这个是突发的情况,机器是来不及进行任何动作的!
-
掉电的是接收方,此时另一边还在发送数据,此时显然发送方不会再有 ACK 了,于是就会超时重传。重传几次之后,就会尝试重置连接,复位报文段:
,再然后发送方就会放弃这个连接,把连接对应的资源就回收了。
-
掉电的是发送方,此时另一方在尝试接收数据,此时却接收不到任何数据。接收方如何知道,发送方是挂了?还是发送方暂时还没发呢?此时接收方采取的策略,就是 “心跳包”机制:
每隔一段时间,向对方发送一个 PING 包,期待对方返回一个 PONG 包,如果 PING 包发过去,过了很久还没有收到 PONG 包,并且重试几次都不行,此时就认为对方已经挂了。
2.3 TCP 与 UDP 的对比
- 对可靠性有一定的要求,就使用 TCP。
- 如果传输的单个数据报比较长(超过 64 k),使用 TCP。
- 对可靠性要求不高,对于效率要求更高,使用 UDP。
- 如果需要广播,优先考虑 UDP。
广播:一份数据同时发送给多个主机。 UDP 自身就支持广播,但是 TCP 自身不支持,就只能在应用层程序中,通过多个连接,轮询的方式给每个主机发送数据(伪广播)。
2.4 经典面试题:基于 UDP 如何实现可靠传输
那就根据 TCP 怎么实现的,UDP 的在传输层也照着实现一遍就好。本质就是在应用层基于 UDP 复刻 TCP 的机制。
-
实现确认应答机制,把每个数据接收到之后,都要反馈一个 ACK (这就不是内核返回的了,而是应用程序自己定义一个 ACK 包,发送回去)。
-
实现序号/确认序号,以及实现去重。
-
实现超时重传。
-
实现连接管理。
-
要想提高效率,实现滑动窗口。
-
为了限制滑动窗口,实现流量控制/拥塞控制。
-
实现延时应答、捎带应答、心跳机制....
三、网络层
3.1 IP 协议报头结构
IP 协议报头结构和作用:
-
**4 位版本:**IP 协议的版本号,当前只有两个取值:4 和 6,对于 IPV4 来说,就是 4。
-
**4 位首部长度:**IP 的报头和 TCP 类似,都是可变的,带有选项。
4 bit的取值范围为 0 - 15,这里的单位也是 4 字节。如果取值 1111 => 15,实际表示的首部长度为 15 * 4 = 60 字节。
-
8位服务类型TOS:3位优先权字段(已经弃用),4位TOS字段,和1位保留字段(必须置为0)。4位TOS分别表示:最小延时,最大吞吐量,最高可靠性,最小成本。 这四者相互冲突,只能选择一个。
所以 IP 协议就可以通过 TOS 来切换形态:
- 最小延时:从主机 A 到主机 B 之间,取一条时间最短的路径。
- 最大吞吐量:从主机 A 到主机 B 之间,取一条路径,它的传输带宽是最高的。
- 最高可靠性:从主机 A 到主机 B 之间,取一条最不容易丢包的路径。
- 最小成本:从主机 A 到主机 B 之间,取一条开销最小的路径。
此时,我们就可以根据实际需要,来选择一条合适的路径。
-
16 位总长度:16 位 => 最大长度 64k,因此,单个 IP 数据报最大长度是不能超过 64k。如果要构造一个更长的数据报(比如载荷部分已经超过 64k了,咋办?IP协议自身实现了分包和组包这样的操作)
-
这三个字段就是用来 分包和组包 的。
- 16 位标识(id):相当于 IP 数据报的身份标识。把一个数据拆成多个 IP 数据报的时候,这三个 IP 数据报的标识是一样的。
- 3 位标志:第一位保留(保留的意思是现在不用,但是还没想好说不定以后要用到)。 第二位置为1表示禁止分片,这时候如果报文长度超过MTU,IP模块就会丢弃报文。第三位表示"更多分片",如果分片了的话,最后一个分片置为1,其他是0。类似于一个结束标记。
- 13 位片偏移:是分片相对于原始IP报文开始处的偏移。其实就是 在表示当前分片在原报文中处在哪个位置。就相当于描述了当前这个包的顺序。
举例:假设我们有一个很长的 IP 数据报:
如果整个 IP 数据报太长了,IP 协议就会把这个大包拆成多个小包,保证每个包都不超过 64k。
假设分成两个小包,每个小包是一个 IP 数据报,即每个小包都有一个 IP 报头。除了 IP 报头外,将剩余数据分成两个部分:,小包大小不超过 64k。
注意:
- 对于 IP 数据包来说,根本不关心数据载荷里是什么,只是单纯的对数据进行切分。
- 如何区分多个 IP 包,是从同一个数据拆分出来的呢?
根据 16 位标识,被拆解成多个 IP 数据报的 16 位标识的值都是一样的。,通过 IP 协议里面的标识,看到标识相同的,就可以知道哪些包是来自同一份数据。
- 组包的时候,按照什么顺序进行组合,才能保证组合出来的数据是合格的?
通过 13 位片位移,来描述多个包的相对顺序。片位移的值越小,说明这个包所在的位置越靠前。
由于网络的复杂性,接收方接收的顺序可能跟发送方发送的顺序不一样,所以通过片位移,就能确认正确的组包顺序。
- 如何确定接收到的小包是不是最后一个小包?
通过 3 位标志位,只有 第 3 位能用,当这一位标志为 0 时,表示 后续还有小包。当这一位标志为 1 时,表示 当前是最后一个包。
:::
- 8 位生存时间(TTL):表示一个 IP 数据报,在网络上还能存在多久,这里的单位不是 s 或者 ms,而是转发次数。
IP 数据报被发送的时候,会有一个初始的TTL(如常见的取值:128或64),IP 数据报每次经过一个路由器,TTL就会 -1。如果 TTL 耗尽,减到 0 时,收到这个包的路由器就会把这个包给丢弃。
这样的机制,主要是因为有些包里面的 IP 地址,可能永远也到不了(如,翻墙,IP 地址不存在等),像这种包,不可能在网络上无休止的转发(这样会占用大量的硬件资源)。
正常情况下,IP 数据报都会在既定的 TTL 内到达目的 IP。
-
8 位协议:表示当前传输层使用的协议类型,TCP 或者 UDP 都有不同的取值。通过这个取值来区分当前数据交给传输层后,是交给哪一个协议来处理。
-
16 位校验和:和 UDP / TCP 校验和是一样的,都是用来校验数据是否正确的。
- 源 IP 地址:发送方主机地址。
- 目的 IP 地址:接收方主机地址。
3.2 IP 协议核心功能
- 地址管理:通过一系列的规则,把网络上的设备的地址给描述出来。
- 路由选择:根据当下的源地址和目的地址,规划出一条合适的路径。
地址管理
IP 地址是一个 点分十进制 构成的数据。
对于 IP 地址来说,里面还可以进一步进行划分。
可以把 IP 地址分成下面两个部分:
在 cmd 中输入 ipconfig 可以查看 IP 配置:
我们要求:同一个局域网里,不同主机之间的网络号是相同的,主机号不能相同。两个相邻的局域网(同一个路由器连接的)也是不同的。
那么,到底前多少个 bit 位是网络号,其实是不固定的。通过引入 “子网掩码” 这样的概念来表示多少个 bit 位是网络号。
子网掩码:也是一个 32 位,点分十进制来表示的整数。
-
子网掩码的左侧都是 1,右侧都是 0(不会 1 0 混着排列)
-
左边的这些 1 就表示哪些位是网络号,剩下的 0 就表示哪些位是 主机号。
像下面这样:
计算当前网络的网络号:
一般家用的场景中,一个局域网设备很少(不会超过 255 个),常见的子网掩码就是 255.255.255.0,如果一个局域网设备多了,子网掩码就会出现一些其他值。
拓展:一些特殊的 IP 地址
-
如果 IP 的主机号为 全0,该 IP 就表示网络号(局域网里的一个正常的设备,主机号是不能设为0的)
-
如果 IP 的主机号 全1(255),该 IP 就表示 “广播地址”,即:往这个广播地址上发送的信息,整个局域网中的设备都能收到。
-
如果 IP 地址是 127 开通的,该 IP 都表示 “环回 IP”,也就是主机自己的 IP。典型 IP 代表:127.0.0.1
-
10 开头、192.168 开头、172.16 至 172.31 开头,如果 IP 地址是这三种开头的,就表示该 IP 地址是一个局域网内部的 IP 地址(内网IP)
-
除了上述 4 种 IP 地址,剩下的 IP 地址成为 外网IP(直接在广域网上使用的IP)
要求:
外网 IP 地址,一定要唯一。每个外网 IP 都会对应到唯一的一个设备。
而内网 IP,只是在当前的局域网中是唯一的,即:不同局域网中,可以有相同的内网 IP 设备。
IP 地址不够用
不同的局域网里,有相同的 IP 设备,主要就是因为当前的 IPv4 协议,使用的地址是 32 位的整数。32 位整数表示的数据范围,就是 42亿9千万。如果给每个设备都分配一个唯一的 IP 地址,就意味着世界上的设备不能超过这么多。所以就有了这样的方式。解决 IP 地址不够用:
-
动态分配 IP 地址:每个设备连上网的时候,才有 IP,不联网的时候就没 IP(这个IP就给别人用),通过 动态IP 来解决 IP地址不够用的问题。
-
NAT 机制:就是让多个设备共用一个 IP(外网IP)把网络分为 内网 和 外网。内网可以共用一个 外网的IP。也就是这里的外网 IP,会给所有接入这个运营商设备的局域网都来使用这个 外网IP。外网区分内网的数据,就是通过端口号来区分的。网络的连接,是一个 五元组,即使是 IP 相同也没事。有 NAT 机制,就会隐含一个重要的结论:
- 对于一个 外网IP 来说,可以在互联网的任意位置都能访问到。
- 对于一个 内网IP 来说,只能在当前局域网内部访问,就是 局域网1 的设备,不能使用内网访问 局域网2 的设备。
- 内网IP 是可以重复的,只有在局域网内才是唯一的。
- 不同的局域网访问的话,就需要一个带有 外网IP 的机器,然后 局域网1 把数据放到 外网服务器,然后 局域网2 就可以访问到外网服务器,就会修改 IP数据报,把内网发出的 源IP,改为 外网IP。
- NAT 也是存在极限的,端口号个数是 65535,如果一个局域网内的连接数超过了 65535,这时候 NAT 就不一定好使了,因为端口号不够用了。NAT 相当于是续命了,并不是从根本解决。
- IPv6:从根本解决了 IP 地址不够用的问题。因为 IPV6 在报头中使用了更长的字段来表示 IP地址,使用 16 个字节来表示。16个字节,128位,之前是 4个字节,32位。IPv6 比 IPv4 能表示的多很多很多。IPv6 地址如下:
每个数字都是一个十六进制的数字(4 bit),每个冒号分割了 2 个字节。 IPv6 使用率不是很高的原因是:支持 IPV4 和 IPV6 需要两截然不同的机制。现有的大量网络设备(路由器…)很可能只支持 IPV4,不支持 IPV6。IP地址的划分,是通过 子网掩码 划分的,在子网掩码之前,是通过 ”分类” 的方式来划分的,把 IP 地址分成了 ABCDE。这五类,每一类分别都有几位是 网络号,几位是 主机号。