【JavaSE-网络部分08】网络原理-IP协议【网络层】

🎯 你正在阅读「网络原理续命手册」系列文章 🎯

🔥 弹简特 个人主页

❄️ 个人专栏直通车:

靠热爱去书写自己,靠勇敢去书写生活!


🌟 博主简介:


文章目录:


一、前言

上篇我们结束了传输层的学习,对TCP和UDP有了系统的认识。本篇正式进入网络层,介绍IP协议------它要解决的核心问题是"数据包如何从源主机到达目标主机"。围绕这个问题,IP协议承担了两大核心工作:地址管理路由选择。读完本篇,你将理解IP报文的格式组成、IP地址不够用的原因与NAT机制的解决方案、以及路由器是如何通过"问路"的方式将数据包一步步转发到目的地的。

二、网络层的介绍

我们网络层的核心协议IP协议

1、网络层的核心工作

  • 地址管理
  • 路由选择

那么在介绍地址管理和路由选择之前,我们先认识ip协议的报文格式👇

2、IP协议的报文格式

ip协议的报文格式如图:

2.1 报文中的各个属性

(1)4位版本

4位版本:描述IP协议的版本,有两个选项:IPv4和IPv6。

(2)4位首部长度

4位首部长度:和TCP一样,IP协议的报头中也包含选项字段,单位与TCP相同,即4字节。例如,若该字段值为15,则首部总长度为15×4=60字节,因此IP协议的最大报头长度为60字节。

(3)8位服务类型

8位服务类型:虽然总共8位,但只有4位比特是有效的,其中一位为1,另外三位为0,主要用于切换TCP的工作模式。这4位分别表示:最小延时、最大吞吐量、最高可靠性、最小成本。四个选项只能选择一个,因此IP协议可以切换工作状态,从而确保优先执行某一项。

(4)16位总长度

16位总长度:指整个IP数据报的长度。一个IP数据包 = 报头 + 载荷的长度。虽然单个IP数据包最大为64KB,但IP协议内置了拆包和组包功能,因此实际传输中不受64KB的限制。比如,当IP需要携带一个较大的数据报时,IP协议会自动将其拆分成多个IP数据报,共同传输一个TCP数据报。

那么IP是如何完成更大数据包的拆分和组包的呢?这与以下三个属性有关:

  • 16位标识
  • 13位片偏移
  • 3位标志
(5)16位标识(拆包组包相关)

16位标识:标识相同的IP数据报说明它们携带的载荷属于同一组数据,可以进行组包。

(6)13位片偏移(拆包组包相关)

13位片偏移:描述这些需要组包的数据包之间的顺序。

(7)3位标志(拆包组包相关)

3位标志:虽然占3位,但只有2位有效。其中一位表示是否触发拆包组包,另一位表示当前这个包是否为最后一个。

那么IP是如何拆包的呢?如图所示:

此时我们的IP协议一看,诶,这个数据有点长,我就进行拆包

接收方如何组包?

第一,接收方根据标识识别出哪些数据包需要组合在一起。

第二,根据片偏移将需要组合的包排成顺序。

第三,根据结束标志确定组包完成。

这样的组包拆包过程也能给我们一些启发:比如,若基于UDP实现传输超过64KB的数据,我们该如何设计?

答:可以参考IP协议的方案,在应用层协议中指定标识符、片偏移、标志位等,从而实现类似的效果。

(8)8位生存时间TTL

8位生存时间TTL:描述了一个IP数据包能够在网络上传输的最大时间,也就是说描述一个IP数据报在网络上能够存活的最大时间,这样做的目的主要是为了防止出现某个数据包无限转发。比如某个数据包,它的IP写错了,我们就尽量的给他去找,如果能找到就尽量的发,没找到我们就只能丢弃了,不能让他一直在那儿到处转,到处耗资源。一个IP数据包每次经过一个路由器就转发一次【注意,经过交换机是不算的,要经过路由器哦】,TTL的值就减1,等TTL的值减到0就说明找不到了,此时就要将其丢弃掉。

(9)8位协议

8位协议:用来描述当前IP数据报的载荷部分保存的是TCP、UDP还是其他协议的数据。补充说明:传输层是通过端口号来区分应用层协议

(10)16为首部首部检验和

16位首部检验和:只检验IP报头,不校验载荷。载荷部分是TCP或UDP数据,而TCP和UDP自身已做校验,因此IP层无需再校验。

(11)32位源IP地址,32位目的IP地址【最重要的属性】

为什么说 32位源IP地址,32位目的IP地址 是最重要的属性因为我们的IP协议有两个核心的工作:地址管理和路由选择,那么32位源IP地址和32位目的IP地址就是与我们的地址管理密切相关的。


我们介绍完我们的ip协议报文中各个属性之后,我们就开始介绍网络层的两个核心工作:地址管理和路由选择👇


3、地址管理

IP地址本质上是32位的整数 ,但为了方便阅读,我们在表示IP地址时通常采用点分十进制的方式,例如:192.168.100.213,其中每个部分的取值范围都是0~255。这种表示方法只是方便人类理解,对于计算机而言,底层仍然是一个32位的整数。


那么这里就有一个问题:16位的取值范围是065535,即64K;而32位的取值范围是042亿9000万。42亿9000万看起来是一个很大的数字,但如果我们给每一个能上网的设备都分配一个唯一且不重复的IP地址,是否够用呢?

随着互联网的发展,能够上网的设备越来越多,比如手机、冰箱、洗衣机、空调、门禁等,因此IP地址已经全部分配完毕。所以,想为每个设备都分配一个唯一的IP地址,并不现实。


那么IP地址不够用该怎么办呢?主要有以下几种解决方案👇

  1. 动态分配IP地址:当某个设备需要上网时,我们就给它分配一个IP;当它不需要上网时,我们就暂时回收该IP。不过,该方案只能在一定程度上缓解IP地址不足的问题,并不能从根本上解决。

  2. NAT网络地址转换:这个方案相比第一个效果更为显著。它的核心思路是使用一个IP地址代表多个设备,从而提高IP地址的利用率。

  3. IPv6:它通过扩展IP地址的长度,大幅增加了IP地址的数量。

真正能够解决IP地址不够用问题的是方案2和方案3,不过目前在实际应用中,我们主要采用的是方案1和方案2,方案3则作为预备方案。因此,接下来我们先来讨论一下NAT机制。


3.1 NAT机制

NAT叫做网络地址转换,那什么是网络地址转换呢?

首先呢NAT把所有的IP地址分成两大类:

  1. 公网IP【或者说是外网IP】:公网IP必须是唯一的。
  2. 私网IP【或者说内网IP】:私网IP在同一个局域网中不能重复,但是在不同局域网中允许重复。

私网IP只有以下三类:

  • 10.X.X.X
  • 172.16~172.31.X.X
  • 192.168.X.X

除了上述三类私网IP之外,其余的都是公网IP。

按照这样的一个划分,我们此时一个公网IP就不是代表一个设备了,而是代表一个局域网中的一系列设备。

那么哪些情况下会涉及到NAT呢?,假设主机A给主机B发送数据(注意是A给B发数据),那么有以下情况:

1)A和B在同一个局域网中,不需要涉及NAT。

2)A和B都在公网中,也不需要涉及NAT。

3)A和B在不同的局域网中,NAT机制禁止这样的通信。

4)A在公网中,B在局域网中,NAT机制也禁止这样的通信。

5)A在局域网中,B在公网中,此时的通信就会涉及到NAT。(假设我把项目部署到云服务器上,那么其他人的客户端也就能访问到了。)


那么具体我们来谈一下A是在局域网中,B是在公网中,此时的通信就会涉及到nat这样的情况:

我的电脑会连接学校的路由器,学校的路由器会连接运营商的路由器,运营商的路由器之间也会相互连接,最终连接到我们学校教务系统的服务器。

这个过程其实比较复杂,为了简化描述,我们将中间的过程省略掉:

我的电脑连接网关路由器(该路由器连接公网),公网再连接我们学校教务系统的服务器。

我的电脑没有公网IP,但我们的网关路由器拥有公网IP。


比如:我的电脑发出的数据如下图:

这个数据到达网关路由器之后会替换其中的源IP。


此时这个数据包就有一个公网IP了,如下:

于是我们这个数据就可以通过公网到达学校教务系统的服务器

最后我们服务器这边看到的数据就是来自于这个公网5.6.7.8这样的IP地址。

完整的图如下:


与此同时,我的主机和小明的主机因为在同一个局域网内,所以它们到达公网时的源IP都是同一个。这样一来,一个公网IP就可以代表整个局域网中的所有设备,而局域网内可以容纳几千甚至几万个设备,这样IP地址就够用了。

举个例子,如果你家在某个村子里,跟外地人说这个村名,对方大概率没听过;但你说自己在贵州省,对方立刻就清楚你的地域范围了。贵州省有这么多人,只要是贵州籍的,大家都能被统称为贵州人,代表这个地域身份。

如上图所示,此时细心的朋友也会发现,我的主机和小明的主机都访问了学校教务系统的服务器。那么有个关键问题:站在学校教务系统服务器的角度,看到我和小明的数据来源都是同一个公网IP,那服务器怎么知道我响应回去数据包的时候,当前这个数据包要返回给谁呢?

其实核心在于,对于服务器来说,只需要把响应返回给网关路由器就可以了,由网关路由器负责区分当前这个数据是给谁的。那么网关路由器是怎么区分的呢?

答:他涉及到一个数据结构,我们的路由器在进行NAT替换时,会记录一个类似于哈希表的映射关系。

比如:我的电脑和小明的电脑的IP地址和端口号如图所示:


此时我们的数据就会到达网关路由器,假设这个网关路由器它的外网IP是5.6.7.8


我发的数据经过网络传输,到达网关路由器之后会将源IP给替换,替换成我们的外网IP。


小明发的数据也会经过这样的一个替换:


然后在路由器替换的时候由路由器记录映射关系:


那么有这样的一张表之后,我们教务系统服务器返回的数据到达这个网关路由器的之后,就会去查到我们这个表,首先我们教务系统服务器比如要响应的数据如下所示:


这个响应数据到达我们的网关之后,此时网关一查,他一看我们的端口号,他就知道这个数据是返回给我的。

于是经过这样的一查表之后就知道这个公网IP5.6.7.8替换成我的主机的IP了👇:


同理,对于小明的数据

查到之后,我们的网关路由器就会将目的IP替换成192.168.100.216,从而就知道这个数据是返回给小明的。

完整图:


上述中我们使用端口号就是为了区分不同主机上的不同应用程序。


这里面有一个重要的问题,万一我的客户端的端口和小明客户端的端口是一样的呢。注意这种情况虽然概率很小,但是理论上是存在的,端口号一样是因为我的电脑和小明的电脑是两台不同主机,那两台不同主机上,是可能会出现相同的端口号的,如果是一台主机,那绝对不可能。

那么此时怎么办呢?其实我们的nat机制呀,他看到你的端口不一样,之后他就不会保留你那个端口号,他就自己替换成别的端口号就行了。

也就是说路由器进行替换时,除了替换IP地址之外,也可能会替换端口,确保替换后的端口号是不重复的,如果重复了,就重新分配一个随机的空闲的端口。


那么谈完nat机制之后呢,我们来看这个IPV6如何解决IP地址不够用的问题?👇


3.2 IPV6

我们的IPv6是用来扩充IP地址范围的。IPv4在IP协议中使用的是32位,即4个字节来表示IP地址;而IPv6则使用16个字节来表示IP地址。

IPv6的地址空间就非常大了,达到2的128次方,即使给地球上的每一粒沙子都分配一个IP地址,也是完全够用的。

那么既然IPv6可以表示如此庞大的地址范围,为什么目前我们主要还是采用方案2的NAT机制呢?

因为IPv6和IPv4是不兼容的。你的电脑如果想要支持IPv6,就必须更换新的设备,这样就会产生不小的资金成本;而如果采用NAT机制,则不需要更换设备,只需要在现有设备上更新软件即可。况且,升级设备使用IPv6并不会提高我们的网速。所以,升级软件和升级设备的成本是完全不同的。


3.3 IP地址的其他划分规则

对于我们Java学习者来说,这里边我们就只需要做一个了解即可,而具体的细节是需要网络管理员去详细研究的。

3.3.1 网段划分

IP地址本质是32位二进制数,为了方便使用,通常分成4个8位的十进制段(如192.168.1.1)。核心划分方式是ABCDE类划分(最基础的分类法),Java开发中接触最多的是A、B、C类私网地址。

每个IP地址都被划分为两部分:前半部分称为网络号,后半部分称为主机号。

  • 网络号:在同一个局域网中,所有设备的IP地址的网络号必须相同;而在相邻的局域网中,网络号必须不同。
  • 主机号:在同一个局域网中,所有设备的IP地址的主机号必须不同。

一个路由器连接了两个局域网,因为路由器上有两个接口,一个是LAN口(局域网接口),一个是WAN口(广域网接口)。我的电脑和室友的电脑连接到主路由器的LAN口,然后主路由器的WAN口又接到外部的网络中。

比如,如下图所示:我们选用的网段划分是C类,那么前3段时网络号,最后1段时主机号

由图可知:

IP地址的网络号取决于子网掩码 。在没有特别说明的情况下,常见的家庭/小型网络通常使用 255.255.255.0 (即 /24)作为子网掩码,此时IP地址的前三段(如 192.168.0.xxx)是网络号,最后一段是主机号。

按照这个标准来看:

  • 网络号 192.168.0.xxx 的设备包括:

    • 我的电脑(192.168.0.200)
    • 小明的电脑(192.168.0.199)
    • 主路由的LAN口(192.168.0.1)
    • 小王的电脑(192.168.0.195)
  • 网络号 192.168.1.xxx 的设备包括:

    • 我的服务器(192.168.1.10)
    • 我的电视(192.168.1.11)
    • 主路由的WAN口(192.168.1.1)

① 同一个局域网中,所有设备的网络号必须相同。

  • 主路由LAN口、我的电脑、小明的电脑、小王的电脑都在 192.168.0.xxx 这个局域网中,网络号一致,符合规则。

  • 服务器和电视都在 192.168.1.xxx 这个局域网中,网络号一致,符合规则。

② 相邻的局域网中,网络号必须不同。

  • 主路由的LAN口连接的是 192.168.0.xxx 这个局域网。
  • 主路由的WAN口连接的是 192.168.1.xxx 这个局域网。
  • 两个相邻局域网的网络号不同(一个是 192.168.0,一个是 192.168.1),符合规则。

③ 同一个局域网中,所有设备的主机号必须不同。

  • 192.168.0.xxx 网络中:

    • 0.200(我的电脑)
    • 0.199(小明的电脑)
    • 0.1(主路由LAN口)
    • 0.195(小王的电脑)
    • 主机号分别为 200、199、1、195,各不相同,符合规则。
  • 192.168.1.xxx 网络中:

    • 1.10(我的服务器)
    • 1.11(我的电视)
    • 1.1(主路由WAN口)
    • 主机号分别为 10、11、1,各不相同,符合规则。

同时可知,路由器的功能就是把数据包从一个局域网转发到另一个局域网。

所谓的相邻局网就是一个路由器连接的这两个局网就是相邻的局域网。


1). 分类规则(核心)
IP类别 首位二进制特征 十进制范围(第一段) 网络位/主机位 典型用途
A类 0开头 1~126 8位/24位 大型网络(如早期互联网主干)
B类 10开头 128~191 16位/16位 中型网络(如企业内网)
C类 110开头 192~223 24位/8位 小型网络(如家庭/办公室内网)
D类 1110开头 224~239 - 组播(多台设备同时接收数据)
E类 1111开头 240~255 - 科研/保留使用
2). 私网地址

公网IP是互联网唯一的,而私网IP仅在局域网内有效,Java做内网应用开发时会频繁遇到:

  • A类私网:10.0.0.0 ~ 10.255.255.255
  • B类私网:172.16.0.0 ~ 172.31.255.255
  • C类私网:192.168.0.0 ~ 192.168.255.255(最常见,比如家庭路由器的192.168.1.1
3). 子网掩码(辅助划分)

用来区分IP的"网络部分"和"主机部分",Java中解析IP时偶尔会用到:

  • A类默认子网掩码:255.0.0.0
  • B类默认子网掩码:255.255.0.0
  • C类默认子网掩码:255.255.255.0

3.3.2 特殊的IP地址

这些地址有固定用途,Java开发中遇到时需要知道其含义(比如判断IP是否为内网、是否为广播地址):

1). 回环地址(localhost)
  • 范围:127.0.0.1 ~ 127.255.255.254(核心是127.0.0.1
  • 用途:指向本机,Java程序中测试网络功能时常用(比如new Socket("127.0.0.1", 8080)),数据不会出本机网卡。
2). 广播地址
  • 定义:某个网段中"主机位全为1"的IP,比如C类网段192.168.1.0的广播地址是192.168.1.255
  • 用途:发送到该地址的数据包,会被网段内所有主机接收,Java中一般不会主动使用(避免广播风暴)。
3). 网络地址
  • 定义:某个网段中"主机位全为0"的IP,比如192.168.1.0是C类网段的网络地址。
  • 用途:标识整个网段,不能分配给具体设备,Java中解析IP网段时会用到(比如判断某个IP是否属于192.168.1.0/24网段)。
4). 0.0.0.0
  • 用途1:Java服务端绑定端口时使用(比如serverSocket.bind(new InetSocketAddress("0.0.0.0", 8080))),表示监听本机所有网卡的该端口(外网、内网都能访问)。
  • 用途2:表示"未知的IP地址"(比如设备还没获取到IP时)。
5). 受限广播地址(255.255.255.255)
  • 用途:发送到该地址的数据包,仅在本机所在的局域网内广播,不会被路由器转发,Java中基本不会用到

我们说IP协议最核心的两个部门就是地址管理和路由选择,那么说完地址管理,我们简单来谈一下路由选择👇


4、路由选择

路由选择是IP协议中的第二要点,路由选择就是路径的规划,那路由选择是通过IP协议进行数据转发的过程。

这个转发的过程极其之复杂,作为Java的学习者,我们在这里不做过多讨论,只做简化的描述。

我们的路由选择类似于一个不断问路的过程:

网络环境是非常之复杂的环境,任何一个路由器是无法存储所有的网络信息的,但是每个路由器是可以知道其附近的网络情况,至少知道谁和谁相连。

当我们数据包到达某个路由器的时候,那就会匹配某个路由表,这个路由表呢就记录了这个路由器周围的设备的IP是啥,以及记录每个设备要通过哪些端口转发过去。

所以此时我们就根据路由表进行匹配,如果你的目的IP刚好匹配到了路由表中的条目,那么就直接按照当前对应的端口转发过去就行了。如果没有匹配到的话,那路由表中会有一个特殊的条目叫做"下一跳",它指向的设备,是上一级路由器所在的位置------虽然当前的路由器不认识你的目的IP,但它的"上一级"很有可能是认识的。

举个栗子

这里我们可以把这个过程再细化一下,让"问路"的类比更具体:假设你从家(你的终端设备,比如电脑、手机)要去朋友家(目的IP对应的设备),你不知道完整的路线,只认识家附近的小卖部(第一级路由器)。你走到小卖部问路,小卖部老板(路由器)手里有一张"附近路线表"(路由表),上面记着周边几个小区、路口的走向,但他不知道你朋友家具体在哪。

这时候,老板就会告诉你:"我不清楚具体位置,但你往前走,到前面的十字路口交警岗亭(下一跳路由器),他肯定比我知道的多"。你按照指示走到岗亭(数据包转发到下一跳路由器),岗亭交警(第二级路由器)同样查看自己的"路由表",如果刚好有你朋友家的路线(目的IP匹配),就直接指给你;如果没有,就再告诉你下一个该去问的地方(下一个下一跳路由器)。

这个"不断问路、不断转发"的过程,直到某个路由器的路由表中,刚好匹配到目的IP的条目,此时数据包就会被直接转发到目的设备,整个路由选择的简化过程就完成了。

这里有两个Java学习者需要重点理解(无需深入底层)的关键细节,避免误解:

第一,路由表不是固定不变的。就像现实中修路、封路会让小卖部老板更新路线表一样,网络中如果某个设备故障、新增设备,路由器会通过自身的路由协议(底层复杂逻辑,我们暂不深究)自动更新路由表,确保"问路"的准确性------不会把你指向一条走不通的路(避免数据包丢失)。

第二,"下一跳"是路由选择的核心兜底逻辑。正因为单个路由器无法存储所有网络的IP信息(就像一个人不可能记住全国所有地址),所以"下一跳"相当于"逐级上报、逐级询问"的机制,让数据包能够沿着正确的方向,逐步靠近目的设备,而不是一匹配不到就直接丢失。

看到这里,可能有Java学习者会问:这和我们写Java代码有什么关系?其实,我们日常开发中,很多场景都间接用到了路由选择的逻辑,只是我们不需要手动实现。比如,你用Java写一个接口调用,从本地项目(终端)调用远程服务器(目的IP)的接口,数据传输的过程中,就自动完成了我们上面说的"路由选择+数据包转发"------你只需要关心接口地址(目的IP+端口)是否正确,底层的"问路"过程,由操作系统和网络设备自动完成。

再举一个简单的关联案例:Java中的Socket编程,我们创建Socket连接时,需要传入服务器的IP地址和端口号,这其实就是在告诉"本地路由器":我的数据包要去这个IP对应的设备,麻烦帮我规划路线、转发过去。而路由器的路由表匹配、下一跳转发,就是支撑这个Socket连接能够成功建立的底层网络基础。

总结一下:路由选择的核心,就是"数据包通过不断匹配路由器的路由表、不断寻找下一跳,最终到达目的IP设备"的过程,类比成"不断问路、逐级找路",就能快速理解其核心逻辑。我们不需要掌握路由协议的底层实现(那是网络工程师的重点),但需要清楚这个过程的大致流程,才能更好地理解Java中网络编程、接口调用的底层支撑,避免出现"只知道怎么写代码,不知道数据怎么传过去"的盲区。


三、写在最后

本期总结:

本篇我们系统地梳理了网络层的核心知识:从IP报文的逐字段解析,到地址管理面临的挑战与NAT机制的应对方案,再到路由选择"逐级问路"的转发逻辑。网络层解决了"从哪里来、到哪里去"的寻址与路径规划问题,是整条网络通信链路中承上启下的关键一层。

OK,到这里呢,咱们网络层相关的知识点就全部介绍完毕啦!

最后这里咱再跟各位Java学习者老铁们强调一句:网络层的内容,咱们做到简单了解、理清核心逻辑就足够了,不用去深究底层复杂的路由协议(那是网络工程师的主场)。咱们的重点,还是应该放在传输层应用层上,这两层才是和咱们日常Java开发(接口调用、Socket编程等)关联最紧密、最实用的内容。

下一期呢,咱们就简略过一下数据链路层和物理层的核心知识点,不用死记硬背,做到有个大致认知、能够串联起整个网络体系就好。

最后恳请各位老铁,点赞+收藏+关注走一波!你们的支持就是我持续更新的最大动力,避免下期找不到啦~ 咱们下期再见,不见不散!💪