网络原理------TCP/IP
文章目录
应用层
我们之前写了最基本的java socket,要知道,我们之前所写的所有代码都在应用层,都是为了完成某项业务,例如:翻译等等。我们讲解完基本的TCP/IP协议后,我们单独进行讲解。
传输层
- 负责数据能够从发送端传输接收端
再谈端口号
- 端口号,标识了一个主机上进行通信的不同的应用程序
- 在TCP/IP协议中,用源端口号,源IP,目的端口号,目的IP,协议号,这样一个五元组来标识一个通信
端口号范围划分
- 0 - 1023:知名端口号,留作计算机特殊用途
- 1024 - 65535:普通端口号,操作系统动态分配的端口号
认识知名的端口号
有些服务器是非常常用的,为了使用方便,人们约定一些常用的服务器,都是用以下这些固定的端口号:
- ssh服务器,使用22端口
- ftp服务器,使用21端口
- telnet服务器,使用23端口
- http服务器,使用80端口
- https服务器,使用443
UDP协议
UDP协议格式

- 16位UDP报文长度:表示整个UDP的大小,16位二进制数字范围是0 - 65535,所以UDP大小范围为0 - 65535个字节
- 16位UDP校验和:用于检查数据传输是否出错的情况
详解校验和:
- 校验和,其实本质上也是一个字符串,体积比原始的数据要更小,又是通过原始的数据生成的。原始数据相同,得到的校验和就一定相同。反之,校验和相同,原始数据大概率相同(理论上会存在不同的情况,实际的概率非常低,可以忽略不计)
如何基于校验和来完成数据校验?
- 发送方,把要发送的数据整理好(称为 data1),通过一定的算法,计算出校验和 checksum1
- 发送方把 data1 和 checksum1 一起通过网络发送出去。
- 接收方收到数据,收到的数据称为 data2(数据可能和 data1 就不一样了),收到数据 checksum1
- 接收方再根据 data2 重新计算校验和(使用相同的算法),得到 checksum2
- 对比 checksum1 和 checksum2 是否相同。如果不同,则认为 data2 和 data1 一定不相同
如果 checksum1 和 checksum2 相同,则认为 data1 和 data2 大概率是相同的(理论上存在不同的可能性,概率比较低,工程上忽略不计)
通过上面的方式,就能发现数据传输出错~~
-
计算校验和,有很多种算法。此处 UDP 中使用的是 CRC 算法(循环冗余算法)
-
把当前要计算校验和的数据,每个字节,都进行累加,把结果保存到这个 2 个字节的变量中,如果过程中如果累加溢出,也没关系。如果中间某个数据,出现传输错误,第二次计算的校验和就会和第一次不同~~
-
CRC 这个算法其实不是特别的靠谱,导致两个不同的数据,得到相同的 crc 校验和的概率比较大。前一个字节恰好少 1,后一个字节恰好多 1。这种情况概率确实不大,但是确实也还是有这方面的风险。
-
md5 / sha1 算法(就只介绍 md5)
-
这里有一系列的公式,来完成 md5 的计算(你们不需要考虑公式是怎样的,是一个数学问题),但我们需要知道 md5 的特点
-
定长,无论你原始数据多长,计算得到的 md5,都是固定长度。
校验和本身就不应该很长,要不然不方便网络传输
-
分散,给两个不同的数据,哪怕绝大部分内容都一样,只要其中一个字节不同,得到的 md5 也都会差异很大。md5 也非常适合作为 hash 算法
-
不可逆
给你一个原始数据,计算 md5,非常容易。给你 md5,还原出原始数据,计算量非常大,以至于于超出了现有计算机的算力上限,理论上是不可行的。md5 也可以应用在一些密码学场景中。
md5 在工作中非常常用,大家一定要熟悉这几个基本特点
基于UDP的应用层协议
-
NFS: 网络文件系统
-
TFTP: 简单文件传输协议
-
DHCP: 动态主机配置协议
-
BOOTP: 启动协议(用于无盘设备启动)
-
DNS: 域名解析协议
当然,也包括你自己写UDP程序时自定义的应用层协议
TCP协议
TCP协议格式

- 源/目的端口号:表示数据是从哪个进程来,到哪个进程去;
- 32位序号/32位确认号:后面详细讲;
- 4位TCP报头长度:表示该TCP头部有多少个32位bit(有多少个4字节);所以TCP头部最大长度是15 * 4 = 60
- 6位标志位:
- URG:紧急指针是否有效
- ACK:确认号是否有效
- PSH:提示接收端应用程序立刻从TCP缓冲区把数据读走
- RST:对方要求重新建立连接;我们把携带RST标识的称为复位报文段
- SYN:请求建立连接;我们把携带SYN标识的称为同步报文段
- FIN:通知对方,本端要关闭了,我们称携带FIN标识的为结束报文段
- 16位窗口大小:后面再说
- 16位校验和:发送端填充,CRC校验.接收端校验不通过,则认为数据有问题.此处的检验和不光包含TCP首部,也包含TCP数据部分.
- 16位紧急指针:标识哪部分数据是紧急数据
- 40字节头部选项:暂时忽略
确认应答
- 问题:信息出现丢失;出现后发先至的情况
- 解决:确认应答报文和发出的数据,可以对应,不要出现歧义;确保后发先至的情况,应用也可以按正常顺序读取数据

注意:
-
序号按照字节编号 → 描述数据先后顺序
-
TCP 报头的序号,是这一次传输的载荷数据中第一个字节序号,剩下序号需要依次推出
-
通过特殊的 ack 数据包,里面携带的 "确认序号" 告诉发送方,哪些数据已经被确认收到了。此时发送方,就心中有数了,就知道了自己刚发的数据是到了还是没到 => 可靠传输

数据重传
- 问题:若网络传输中,出现丢包的情况,发送方无法接收到ACK
- 原因:网络堵塞
- 丢包:有可能丢的是数据,也有可能是ACK
- 解决:重新传输数据(设置重传时间)
- 初始的等待时间,是可配置的,不同的系统上都不一定一样,也可以通过修改一些内核参数来引起这里的时间变化
- 等待的时间,也会动态变化。每多经历一次超时,等待时间都会变长。
情景:
- A -> B 发了一条数据,第一次,A 等待 ACK 的时间,假设是 50ms此时如果达到 50ms,还没有 ack,A 就重传。当 A 重传的数据,还是没有收到 ack,第二次等待的时间就会比第一次更长拉长也不是无限拉长,重传若干次时,时间拉长到一定程度,认为数据再怎么重传也没用了,就放弃 tcp 连接(准确的说是会触发 tcp 的重置连接操作)
对重复数据的传输:
- 其实 TCP 已经非常贴心的帮我们把这个问题解决了TCP 会有一个"接收缓冲区"就是一个内存空间,会保存当前已经收到的数据,以及数据的序号。接收方如果发现,当前发送方发的数据,是已经在接收缓冲区中存在的(收到过的重复数据了,接收方就会直接把这个后来的数据给丢弃掉,确保应用程序进行读取的时候,读到的是只有一条数据,不会重复
连接管理
1.握手:
正常情况下,TCP经过三次握手建立连接,四次挥手断开连接
- 握手🤝:会由发送端主动发送一个没有业务数据的数据包,接收端也会返回一个没有业务的数据包和ACK,然后发送端再发送一个ACK,然后双方就构成了"逻辑"上的连接(这个没有业务数据的数据包也被称为"同步报文段"------SYN)


- 三次握手的核心作用:
- 确认当前网络是否流畅
- 让双方都能确认自己的发送和接收能力正常
- 在握手的过程中,针对一些重要的参数,进行协商
注意⚠️:
- 有的时候,网络如果不太好,客户端和服务器之间可能会连接断开,再重新建立连接。重连的时候,就可能在新的连接好了之后,旧连接的数据姗姗来迟。这种迟到的数据,应该要丢弃掉的!

- 挥手:
- 建立连接:客户端主动发起
- 断开连接:客户端和服务器都可以主动发起
具体流程:
- TCP 四次挥手是关闭连接的过程,流程如下:
- 第一次挥手(FIN) :客户端发送带有
FIN
标志的报文,告知服务器"我要关闭数据发送了",进入FIN - WAIT - 1
状态 - 第二次挥手(ACK) :服务器收到
FIN
后,回发ACK
确认报文,告诉客户端"我知道你要关了",进入CLOSE - WAIT
状态;客户端收到ACK
后,进入FIN - WAIT - 2
状态 - 第三次挥手(FIN) :服务器数据发送完毕,发送带有
FIN
标志的报文,告知客户端"我也准备好关闭了",进入LAST - ACK
状态 - 第四次挥手(ACK) :客户端收到
FIN
后,回发ACK
确认报文,进入TIME - WAIT
状态;服务器收到ACK
后,进入CLOSED
状态;客户端等待一段时间(2 倍 MSL)后,也进入CLOSED
状态,连接彻底关闭

问题1:
- 为什么第二次和第三次挥手合并呢?
- ACK 是内核响应的. B 收到 FIN, 就会立即返回 ACK
- 第二个 FIN 是应用程序的代码触发. B 这边调用了 close 方法才会触发 FIN
clientSocket.close();从服务器收到 FIN (同时返回 ACK), 再到执行到 close 发起 FIN, 这中间要经历多少时间,经历多少代码,是不确定的!(看你代码是咋写的)FIN 就会在 socket 对象 close 的时候,被发起.可能是手动调用 close 方法,也可能是进程结束. - 像前面的三次握手,ACK 和第二个 syn 都是内核触发的,同一个时机,可以合并
问题2:
- 是否意味着,如果我这边代码 close 没写 / 没执行到,是不是第二个 FIN 就一直发不出去?(有可能的)
- 如果是正常的四次挥手,"好聚好散",正常的流程断开的连接。
如果是不正常的挥手(没有挥完四次),异常的流程断开连接,(也是存在的)
问题1:
- TIME_WAIT的作用是什么?
- 如果最后一个 ACK 丢了,站在 B 的角度,B 就会触发超时重传,重新把刚才的 FIN 传一遍。如果 B 才没有进入 TIME_WAIT 状态,就意味着 A 这个时候就已经真的释放连接了。此时重传的 FIN 也就没人处理,没人能返回 ACK 了,B 永远也收不到 ACK 了。
- A 这边使用 TIME_WAIT 状态进行等待,等待的这个时间,就是为了处理后续 B 重传的 FIN。此时有重传的 FIN 来了,就可以继续返回 ACK 了,B 这边的重传 FIN 才有意义。
问题2:
- TIME_WAIT 等待时间是多长?
- 假设网络上两个节点通信消耗的最大时间为 MSL
此时 TIME_WAIT 的时间就是 2 × MSL
解释:
- 可配置的参数(数值也是拍脑门出来了)
已经是上限了,绝大部分的数据包也不会达到这个时间的,都会比这个时间短很多
滑动窗口
- 前三个机制,都是在保证 tcp 的可靠性。TCP 的可靠传输,是会影响传输的效率的。(多出了一些等待 ack 的时间,单位时间内能传输的数据就少了)
- TCP想通过"滑动窗口",提高效率
- 滑动窗口,就让可靠传输对性能的影响,更少一些。TCP 只要引入了可靠性,传输效率是不可能超过 没有可靠性的 UDP 的。TCP 这里的 "效率机制" 都是为了让 影响更小,缩短和 UDP 的差距
- 缩短确认应答的等待时间

- 批量传输数据
不等 ack 回来, 直接再发下一个数据
批量传, 也不是"无限的"传输.
批量传输也是存在一定的上限的. 达到上限之后, 再统一等 ack
不等的情况下, 批量最多发多少数据, 这个数据量, 称为"窗口大小"

- 当前 A -> B 是批量的发了四份数据
此时 B 也要给 A 回应四个 ACK
此时 A 已经达到窗口大小,再收到 ACK 之前,不能继续往下发了
需要等待有 ACK 回来了之后,才能继续往下发 - 这里是怎么继续发的?
是等四个 ack 都回来了,再继续发四条?
还是回来一个 ack 就继续发一个呢?√
回来一个 ack,就立即继续发一个
窗口越大,等待的 ack 越多,此时传输效率也就越高
丢包问题
- 数据包到达,ACK丢了
-
这种情况, 不需要任何重传, 没事的
-
确认序号, 表示的含义是, 当前序号之前的数据, 已经确认收到了.
下一个你应该从确认序号这里, 继续发送.
-
如果 1001 这个 ack 丢了, 但是 2001 ack 到了.
2001 之前的数据都已经确认传输成功了, 涵盖了 1001 的情况!!!
- 数据包丢了

- 当某一段报文段丢失之后,发送端会一直收到 1001 这样的 ACK,就像是在提醒发送端"我想要的是 1001"一样;
- 如果发送端主机连续三次收到了同样一个"1001"这样的应答,就会将对应的数据 1001 - 2000 重新发送;
- 这个时候接收端收到了 1001 之后,再次返回的 ACK 就是 7001 了(因为 2001 - 7000 接收端其实之前就已经收到了,被放到了接收端操作系统内核的接收缓冲区中;
这种机制被称为"高速重发控制"(也叫"快重传")
具体优化:
-
如果通信双方,传输数据的量比较小,也不频繁,就仍然是普通的确认应答和普通的超时重传。
如果通信双方,传输数据量更大,也比较频繁,就会进入到滑动窗口模式,按照快速重传的方式处理。
-
通过滑动窗口的方式传输数据,效率是会提升的。
窗口越大,传输效率就越大。(一份时间,等待的 ack 更多了,总的等待时间更少了)
-
滑动窗口,设置的越大,越好嘛?
如果传输的速度太快,就可能会使接收方,处理不过来了。此时,接收方也会出现丢包,发送方还得重传......
TCP 前提是可靠性,可靠性的基础上,再提高传输效率
流量控制
接收端处理数据的速度是有限的。如果发送端发的太快,导致接收端的缓冲区被打满,这个时候如果发送端继续发送,就会造成丢包,继而引起丢包重传等等一系列连锁反应。
因此 TCP 支持根据接收端的处理能力,来决定发送端的发送速度,这个机制就叫做流量控制(Flow Control);
- 接收端将自己可以接收的缓冲区大小放入 TCP 首部中的"窗口大小"字段,通过 ACK 端通知发送端;
- 窗口大小字段越大,说明网络的吞吐量越高;
- 接收端一旦发现自己的缓冲区快满了,就会将窗口大小设置成一个更小的值通知给发送端;
- 发送端接受到这个窗口之后,就会减慢自己的发送速度;
如果接收端缓冲区满了,就会将窗口置为 0;这时发送方不再发送数据,但是需要定期发送一个窗口探测数据段,使接收端把窗口大小告诉发送端

- 接收缓冲区剩余空间大小
(这里是否是最大只能表示 64k 呢?其实不是的)
TCP 报头中,选项部分里有一项是叫做
"窗口扩展因子"
通过扩展因子,就可以让窗口大小
表示一个更大的值

拥塞控制
-
流量控制,是考虑的接收方的处理能力。不仅仅是接收方,还有你整个通信的路径。拥塞控制,就是考虑 / 衡量 通信过程中中间节点的情况
-
关键问题:
接收方的处理能力,很方便进行量化。
由于中间节点,结构更复杂,更难以直接的进行量化。
因此就可以使用 "实验" 的方式,来找到个合适的值。
-
让 A 先按照比较低的速度先发送数据 (小的窗口)
如果数据传输过程非常顺利,没有丢包。
再尝试使用更大的窗口,更高的速度进行发送。(一点一点变化)
随着窗口大小不停的增大,达到一定程度,可能中间节点就会出现问题了。此时这个节点就可能会出现丢包。
发送方发现丢包了,就把窗口大小调小。此时如果发现还是继续丢包,继续缩小。如果不丢包了,就继续尝试 或变大
-
再这个过程中,发送方不停的调整窗口大小,逐渐达成 "动态平衡"
这种做法,就相当于把中间节点,都视为 "整体",通过实验的方式,来找到中间节点的瓶颈在哪里~~
具体图像:
改进后的图像:
- 改进措施:不是让速度直接归零,直接从慢启动开始。而是有一定的初速度,忽略指数增长的过程

延迟应答
-
A 把数据传给 B,B 就会立即返回 ack 给 A [正常]
也有的时候,A 传给 B,此时 B 会一等会再返回 ack 给 A [延迟应答]
本质上也是为了提升传输效率。
-
发送方的窗口大小,就是传输效率的关键。
-
流量控制这里,就是根据接收缓冲区中的剩余空间,来决定发送速率的。
如果能够有办法,让这个流量控制得到的窗口更大点,发送速率就更快点
(大的前提,能够让接收方还是能处理到过来的)
延时返回 ack,给接收方更多的时间,来读取接收缓冲区的数据
此时接收方读了这个数据之后,缓冲区剩余空间,变大了~~
返回的窗口大小也就更大了。
-
初始情况下,接收缓冲区剩余空间是 10kb,如果立即返回 ack,返回了 10kb 这么大的窗口。
如果延时 200ms 再返回,这 200ms 的过程中,接收方的应用程序的,又读了 2kb,
此时,返回的 ack,就可以返回 12kb 的窗口了。
捎带应答
在延迟应答的基础上, 我们发现, 很多情况下, 客⼾端服务器在应⽤层也是 "⼀发⼀收" 的. 意味着客⼾端给服务器说了 "How are you", 服务器也会给客⼾端回⼀个 "Fine, thank you";
那么这个时候ACK就可以搭顺⻛⻋, 和服务器回应的 "Fine, thank you" ⼀起回给客⼾端
面向字节流
- 这里有一个重要的问题------粘包问题(这不是TCP独有的,而是所有面向字节流的机制都有类似的情况)
- 此处的"包"是指应用层数据包。如果同时有多个应用层数据包被传输过去,此时就容易出现粘包问题

-
目前,接收缓冲区中,这三个应用层数据包的数据,就是以字节的形式紧凑在一起的。接收方的应用程序,读取数据的时候,可以一次读一个字节,也可以读两个字节,也可以读 N 个字节......
-
但是最终的目标是为了得到完整的应用层数据包。B 应用程序,就不知道,缓冲区里的数据,从哪里到哪里是一个完整的应用数据包了
如何解决粘包问题呢?
- 引入分隔符
- 引入长度

- 应用程序在读取数据的时候,就可以持续读取数据,直到遇到分隔符为止

- 应用程序在读取数据的时候,可以先读取两个字节,得到数据包的长度,再根据这个长度,继续读取对应字节个数
但是一般的自定义应用层协议格式,xml,json和protobuffer,本身明确了包的界限
异常情况
如果在使用TCP的过程中,出现意外,会如何处理?
- 进程崩溃
- 进程没了,异常终止了。文件描述符表,也就释放了。相当于调用 socket.close()。此时就会触发 FIN,对方收到之后,自然就会返回 FIN 和 ACK,这边再进行 ACK(正常的四次挥手断开连接的流程)
- TCP 的连接,可以独立于进程存在(进程没了,TCP 连接不一定没)
- 主机关机(正常)
- 在进行关机的时候,就是会先触发强制终止进程操作 (相当于 1)
- 此时就会触发 FIN,对方收到之后,自然就会返回 FIN 和 ACK。不仅仅是进程没了,整个系统也可能关闭了. 如果在系统关闭之前,对端返回的 ACK 和 FIN 到了,此时系统还是可以返回 ACK,进行正常的四次挥手的. 如果系统已经关闭了,ACK 和 FIN 迟到了,无法进行后续 ACK 的响应. 站在对端的角度,对端以为是自己的 FIN 丢了,重传 FIN. 重传几次都没有响应,自然就会放弃连接. (把持有的对端的信息就删了)
- 主机掉电(非正常)
- 此时,是一瞬间的事情,来不及杀进程,也来不及发送 FIN,主机直接就停机了.
站在对端的角度,对端不一定知道这个事情咋搞~~
-
如果对端是在发送数据(接收方掉电),发送的数据就会一直等待 ack,触发超时重传,触发 TCP 连接重置功能,发起"复位报文段"
如果复位报文段发过去之后,也没有效果,此时就会释放连接了.
-
如果对端是在接收数据(发送方掉电),对端还在等待数据到达......等了半天没消息,此时其实无法区分,是对端没消息,还是对方挂了
TCP 中提供了心跳包机制(形象的比喻)
接收方也会周期性的给发送方发起一个特殊的,不携带业务数据的数据包,并且期望对方返回一个应答.
如果对方没有应答,并且重复了多次之后,仍然没有,就视为对方挂了,此时就可以单方面释放连接了.
4.网线断开
- 当前假设,是 A 正在给 B 发送数据,一旦网线断开,
A 就相当于就会触发超时重传 -> 连接重置 -> 单方面释放连接.
B 就会触发心跳包 -> 发现对端没响应 -> 单方面释放连接.
控制位字段
标志位 | 作用 | 配合字段 / 规则 | 典型场景 |
---|---|---|---|
URG(Urgent,紧急) | 标记报文段中存在需优先处理的 "紧急数据" | 紧急指针(指示紧急数据结束位置) | 远程登录时,用户按下 Ctrl + C 发送中断信号,数据被标记为紧急数据,URG 置 1,让接收方优先处理 |
ACK(Acknowledgment,确认) | 确认已收到数据,是 TCP 可靠传输的核心标志之一 | ACK = 1 时,确认号字段有效(表示 "期望接收的下一个字节序号");ACK = 0 时,确认号无意义 | 正常数据传输中,接收方收到数据后,发送 ACK = 1 的报文,告知发送方 "已收到 xx 之前的数据,期待下一个是 xx" |
PSH(Push,推送) | 要求接收方立即将数据递交给应用层,不等待接收缓冲区填满 | 无 | 实时聊天、远程命令执行等低延迟需求场景,如用户在终端输入命令后,系统设置 PSH = 1,让接收方 TCP 层直接把数据推给应用层(如 Shell 程序) |
RST(Reset,重置) | 强制重置 TCP 连接,处理 "异常连接" | 无 | 一方收到非法 TCP 报文(如目标端口未被监听),回复 RST = 1 的报文强制对方关闭连接;连接超时后,通过 RST 快速断开 "僵死连接" |
SYN(Synchronize,同步) | 用于建立 TCP 连接的 "三次握手" 阶段,标记 "同步序号"(协商双方的初始序列号) | 无 | 第一次握手时,客户端发送 SYN = 1 的报文,请求建立连接并同步初始序列号;第二次握手,服务端回复 SYN = 1,ACK = 1,同步自己的初始序列号并确认客户端的序列号 |
FIN(Finish,结束) | 用于断开 TCP 连接的 "四次挥手" 阶段,标记 "本端已无数据可发送,请求关闭连接" | 无 | 一方数据发送完毕,发送 FIN = 1 的报文,告知对方准备关闭连接;对方收到后回复确认,最终完成连接关闭 |
TCP和UDP对比
-
TCP 优势 可靠传输,适用于绝大部分场景。
-
UDP 优势 更高效率,更适合于"可靠性不敏感","性能敏感" 场景
-
如果要传输比较大的数据包,TCP 更优先 (UDP 有 64KB 的限制)
-
如果要进行 "广播传输", 优先考虑 UDP. UDP 天然支持广播,TCP 不支持 (应用程序额外写代码实现)
网络层
IP协议
基本概念:
- 主机:配有IP地址,但是不进行路由器控制的设备
- 路由器:配有IP地址,又能进行路由设备控制
- 节点:主机和路由器的统称
协议头格式

- 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位分片偏移(fragmentation offset):是分片相对于原始IP报文开始处的偏移。其实就是在表示当前分片在原报文中处在哪个位置。实际偏移的字节数是这个值 * 8 得到的。因此,除了最后一个报文之外,其他报文的长度必须是8的整数倍(否则报文就不连续了)。
- 8位生存时间(Time To Live, TTL):数据报到达目的地的最大报文跳数。一般是64。每次经过一个路由,TTL -= 1,一直减到0还没到达,那么就丢弃了。这个字段主要是用来防止出现路由循环。
- 8位协议:表示上层协议的类型。
- 16位头部校验和:使用CRC进行校验,来鉴别头部是否损坏。
- 32位源地址和32位目的地址:表示发送端和接收端。
- 选项字段(不定长,最多40字节):略
地址管理
- IP 地址,是一个 32 位的整数. 2^32 => 42 亿 9 千万.
地址,理论上来说,是不应该重复的! - 互联网发展到今天,能上网的设备,非常非常多的。早就超过了 42 亿 9 千万这个数字~
最近几年很重要的概念,物联网。汽车,空调,冰箱,洗衣机... 也都能联网了.
如何解决这个 IP 地址不够用的问题呢?
-
方案一:动态分配 IP
这个方案,治标不治本,提高了 IP 地址的利用率,并没有增加 IP 地址的数目.(虽然这是一个 过渡 方案,这个方案目前仍然是广泛存在的)
-
方案二: NAT 机制 (网络地址转换)
本质上 让一个 IP 地址,代表一批设备.
IP地址分类:
-
内网 IP (局域网 IP)
如果一个 IP 地址,是以 10.* 或者 172.16.* - 172.31.* 或者 192.168.* ( 符合上述条件之一,就是内网 IP )
在同一个局域网内部,内网 IP 之间,不能重复.
在不同的局域网中,内网 IP 之间,可以重复
-
外网 IP (广域网 IP)
剩下的 IP 就都是外网 IP
外网 IP 则始终不允许重复,务必唯一.
NAT技术背景
之前我们讨论了,IPv4协议中,IP地址数量不充足的问题
NAT技术当前解决IP地址不够用的主要手段,是路由器的一个重要功能
-
NAT能够将私有IP对外通信时转为全局IP,也就是就是一种将私有IP和全局IP相互转化的技术方法
-
很多学校,家庭,公司内部采用每个终端设置私有IP,而在路由器或必要的服务器上设置全局IP
-
全局IP要求唯一,但是私有IP不需要;在不同的局域网中出现相同的私有IP是完全不影响的
NAT IP转换过程

- NAT路由器将源地址从10.0.0.10替换成全局的IP 202.244.174.37;
- NAT路由器收到外部的数据时,又会把目标IP从202.244.174.37替换回10.0.0.10;
- 在NAT路由器内部,有一张自动生成的,用于地址转换的表;
- 当10.0.0.10第一次向163.221.120.9发送数据时就会生成表中的映射关系;
NAPT
-
那么问题来了,如果局域网内,有多个主机都访问同一个外网服务器,那么对于服务器返回的数据中,目的IP都是相同的。那么NAT路由器如何判定将这个数据包转发给哪个局域网的主机?
-
这时候NAPT来解决这个问题了. 使用IP+port来建立这个关联关系

- 这种关联关系也是由NAT路由器自动维护的. 例如在TCP的情况下, 建立连接时, 就会生成这个表项; 在断开连接后, 就会删除这个表项
- 假如port都相同,NAT设备就会修改端口号,确保数据以一一对应传回对应主机
NAT 技术的优缺点
由于NAT依赖这个转换表,所以有诸多限制:
- 无法从NAT外部向内部服务器建立连接;
- 装换表的生成和销毁都需要额外开销;
- 通信过程中一旦NAT设备异常,即使存在热备,所有的TCP连接也都会断开;
但是 NAT 有一个最大的优点:不需要更新硬件设备,只更新软件,就可以解决 IP 地址不够用的问题!
网段划分
IP 地址分为两个部分,网络号和主机号
- 网络号:保证相互连接的两个网段具有不同的标识;
- 主机号:同一网段内,主机之间具有相同的网络号,但是必须有不同的主机号;

- 不同的子网其实就是把网络号相同的主机放到一起
- 如果在子网中新增一台主机,则这台主机的网络号和这个子网的网络号一致,但是主机号必须不能和子网中的其他主机重复
- 相邻的局域网,由路由器连接
通过合理设置主机号和网络号,就可以保证在相互连接的网络中,每台主机的IP地址都不相同
子网掩码
- 一个 IP 地址,哪个部分是网络号,哪个部分是主机号,不一定的
- 子网掩码,就是用来确定网络号的

- 子网掩码是一个32位的整数,例如:255.255.255.0,转化为二进制就是1111 1111 1111 1111 1111 11111 0000 0000。这里被标为1的部分就是网络号,这里的1也不一定是二十四个,根据实际的网络环境,灵活配置的。一般来说,家用路由器的子网掩码都是255.255.255.0
- 目前介绍这种 子网掩码 的划分,是当下环境的现状

- 上面这种,是上古时期网段划分的方式
- 这种方式基本上见不到了
网络号具体计算
- 子网掩码也是一个32位的正整数。通常用一串"0"来结尾
- 将IP地址和子网掩码进行"按位与"操作,得到的结果就是网络号
- 网络号和主机号的划分与这个IP地址是A类、B类还是C类无关
路由选择
-
上面讲的是IP地址的管理,路由选择也是IP协议的核心功能之一
-
路由选择,就是描述了 IP 协议(IP 数据报)转发过程
-
而进行 IP 数据报转发的时候,每个路由器,都是无法知道,网络的"全貌"的,只知道一些局部信息。(一个路由器能知道哪些设备和它自己是相连的)
这就意味着 IP 数据在转发过程中,是一个"探索式""启发式"过程。
这个过程,很难给出"最优解"只能是"较优解"。
-
一个网络层的数据报,每次到达一个路由器,也会进行上述"问路"过程。
每个路由器内部都有一个数据结构"路由表",根据数据报中的目的 IP,查路由表。
-
如果查到了(问的人,恰好知道咋走),就直接按照路由表给定的方向(从哪个网络接口进行转发),继续转发就行了。
-
如果没查到(问的人,不知道咋走),路由表里有一个"默认的表项"(下一跳地址),按照默认的表项转发即可~~
数据链路层
以太网帧格式
-
这里也有很多协议,常见的就是"以太网协议"
-
通过网线/光纤来通信,使用的协议是以太网协议。以太网横跨数据链路层 + 物理层
-
源地址和目的地址是指网卡的硬件地址(也叫MAC地址),长度是48位,是在网卡出厂时固化的
-
帧协议类型字段有三种值,分别对应IP、ARP、RARP
-
帧末尾是CRC校验码
认识MAC地址
- MAC地址用来识别数据链路层中相连的节点;
- 长度为48位,及6个字节,一般用16进制数字加上冒号的形式来表示(例如: 08:00:27:03:fb:19)
- 在网卡出厂时就确定了,不能修改. mac地址通常是唯一的(虚拟机中的mac地址不是真实的mac地址,可能会冲突;也有些网卡支持用户配置mac地址).
对比理解MAC地址和IP地址
- IP地址描述的是路途总体的起点和终点;(关注全程)
- MAC地址描述的是路途上的每一个区间的起点和终点;(关注相邻节点)
认识MTU
MTU相当于发快递时对包裹尺寸的限制。这个限制是不同的数据链路对应的物理层,产生的限制。
- 以太网帧中的数据长度规定最小46字节,最大1500字节,ARP数据包的长度不够46字节,要在后面补填充位;
- 最大值1500称为以太网的最大传输单元(MTU),不同的网络类型有不同的MTU;
- 如果一个数据包从以太网路由到拨号链路上,数据包长度大于拨号链路的MTU了,则需要对数据包进行分片(fragmentation);
- 不同的数据链路层标准的MTU是不同的;
MTU对IP协议的影响
由于数据链路层MTU的限制,对于较大的IP数据包要进行分包。
- 将较大的IP包分成多个小包,并给每个小包打上标签;
- 每个小包IP协议头的16位标识(id)都是相同的;
- 每个小包的IP协议头的3位标志字段中,第2位置为0,表示允许分片,第3位来表示结束标记(当前是否是最后一个小包,是的话置为1,否则置为0);
- 到达对端时再将这些小包,会按顺序重组,拼装到一起返回给传输层;
- 一旦这些小包中任意一个小包丢失,接收端的重组就会失败。但是IP层不会负责重新传输数据;
重要应用层协议DNS
- 域名解析系统
- 作用:把域名翻译成IP(二者为一组键值对)
来源
- 最早是使用hoste文件,来维护域名与IP的映射关系
- 后来搭建了DNS系统(一组服务器),把映射关系保存到服务器中
- 为了避免高并发的情况,增加许多镜像DNS服务器(三大运营商进行管理)
特点
- 开源:有许多镜像服务器,用户优先访问自己最近的服务器
- 节流:只要请求一次,并保存到本地