文章目录
- [1. 应用层](#1. 应用层)
-
- [1.1 自定义协议的约束](#1.1 自定义协议的约束)
- [1.2 编辑数据的格式](#1.2 编辑数据的格式)
- [1.3 xml例](#1.3 xml例)
- [1.4 json例](#1.4 json例)
- [2. 传输层](#2. 传输层)
-
- [2.1 UDP协议](#2.1 UDP协议)
-
- 校验和
- [如何使用 UDP 来实现可靠传输](#如何使用 UDP 来实现可靠传输)
- [2.2 TCP协议](#2.2 TCP协议)
-
- 保留位(Reserved)
- 标志位(Flags)
- 检验和(Checksum)
- [序列号(Sequence Number)](#序列号(Sequence Number))
- [确认号(Acknowledgment Number)](#确认号(Acknowledgment Number))
- 确认应答【确保可靠性】
- 超时重传【处理丢包】
- 网络连接
- 滑动窗口
- 流量控制
- 拥塞控制
- 延时应答
- 捎带应答
- 面向字节流
- 异常情况
- [2.3 KCP协议](#2.3 KCP协议)
- [3. 网络层](#3. 网络层)
-
- [3.1 IP协议](#3.1 IP协议)
-
- [3.1 IP协议报头](#3.1 IP协议报头)
-
- 4位版本
- 4位首部长度
- [8位服务类型(type of service,TOS)](#8位服务类型(type of service,TOS))
- 16位总长度(字节数)
- 16位标识
- 3位标志
- 13位片偏移
- 8位生存时间(TTL)
- 8位协议
- 16位首部校验和
- 32位源IP地址和32位目的IP地址
- [3.2 IP协议如何管理地址](#3.2 IP协议如何管理地址)
- [3.3 路由选择](#3.3 路由选择)
- [4. 数据链路层](#4. 数据链路层)
1. 应用层
应用层是程序员打交道最多的一层协议,应用层里有很多现成的协议,但是有些并不适用于一些特定场景,需要自定义协议。
应⽤层,都是为了完成某项业务。
1.1 自定义协议的约束
服务器和客户端要交互哪些信息
信息发送的具体格式
1.2 编辑数据的格式
xml,利用xml可读性和扩展性都明显提高,但是整个信息冗余太多,会导致标签占据的空间比数据还多,在进行网络传输的时候,会占用网络带宽。
json,当下主流常用的数据组织格式,通过键值对结构形成json对象。
protobuffer:相比其他格式更节省带宽,效率最高。虽然 protobuffer 运行效率更高,但是使用并没有比 json 更广泛,只是哪些对于性能要求非常高的场景,才会使用 protobuffer。(因为不利于程序员阅读,数据是按照二进制方法来组织的)
1.3 xml例
xml
请求:
<request>
<userld>1000</userld>
<position>[经纬度]</position>
</request>
响应:
<response>
<shops>
<shop>
<id>1001</id>
<name>杨国福麻辣烫</name>
<image>图片地址</image>
<rank>4.8</rank>
<description>这是好吃的麻辣烫</description>
</shop>
</shops>
</response>
1.4 json例
java
请求
{
userld: 1000,
position: [经纬度]
}
响应
[
{
id: 1001,
name:"杨国福麻辣烫
},
{
id: 1002,
name:"魏家凉皮"
}
]
2. 传输层
负责数据能够从发送端传输接收端。
2.1 UDP协议
特点:无连接,不可靠传输,面向数据报,全双工。
UDP数据报格式:报头(重点)+载荷(应用层数据包)

UDP报头一共有四个字段,每个字段有2个字节。
由于协议用2个字节表示端口号,端口号取值就是0-65535,所以最大值就是64kb,因此一个数据报最大长度就是64kb ,一旦数据报长度超过64kb,就会发生数据截断。
端口号前文我们有过介绍,就是数据传输后,传到具体到哪个应用程序(每个程序都有自己的端口号)。
校验和
验证数据在传输过程中是否正确,检查当前传输的数据是否存在bit翻转(即数据在网络传输过程中是否收到了电/磁/电磁波等元素的影响,使得数据的高/低电平发生翻转,造成数据有误),如果错了,就把数据报丢弃。
CRC算法(循环冗余校验)
UDP在发送数据之前就会先计算一下CRC值,把算好的CRC值放到UDP数据报中,发送给接收方,接收方在拿到数据后,也会计算CRC值,如果传过来的值和接收方计算的值相等,就证明数据是正确的。
java
short checksum = 0;
for(遍历取出数据报中的每个字节的数据){
checksum += 当前字节的数据;
}
如果有两个/多个bit位发生翻转,有可能恰好校验和和之前一样。这样的情况概率比较低,可以忽略不计。
如果希望这里有更高的检查精度,就需要使用其他的更严格的校验和算法。
md5算法
背后有一系列的数学公式来进行计算,就不像刚才的那个 CRC 那么简单,类似于密码学的内容。
特点:
长度一定:无论原始数据多长,最终算出的md5值都是固定的长度
分散:计算md5值的时候,如果数据发生一点点变化,结果就会有很大差异
不可逆:一个字符串可以计算出md5的值,但是想根据计算出的md5的值得到字符串几乎是不可能的
因md5优异的特点,还将其用于字符串的 hash 算法,密码的加密保存。
如何使用 UDP 来实现可靠传输
TCP是可靠的传输协议,TCP是怎么实现可靠传输,UDP将其复制过来即可。
2.2 TCP协议
特点:有连接,可靠传输,面向字节流,全双工
4位首部长度,就是报头长度。在TCP中报头前20字节是固定长度的,最大长度是60字节。
源端口(Source Port):2字节
目的端口(Destination Port):2字节
序列号(Sequence Number):4字节
确认号(Acknowledgment Number):4字节
数据偏移(Data Offset):4位(占1字节,表示报头长度)
保留(Reserved):6位(占1字节)
标志(Flags):6位(占1字节)
窗口(Window):2字节
校验和(Checksum):2字节
紧急指针(Urgent Pointer):2字节(仅在紧急指针标志位为1时使用)
保留位(Reserved)
UDP 这个协议,长度受到 2 个字节的限制,想要进行扩展,发现扩展不了。一旦你改变了这里的报头长度,就会使机器发送的 UDP 数据报和其他机器不兼容,无法通信为协议的未来发展提供了灵活性。如果未来网络通信的需求发生了变化,需要引入新的特性,TCP协议的保留位可以被重新定义,而不会影响到现有协议的正常运行。
标志位(Flags)
标志位位于TCP报文头部,用于控制连接的建立、维护、终止以及数据传输的特殊处理。
SYN(Synchronize,同步):用于建立TCP连接(三次握手)。
ACK(Acknowledgment,确认):确认接收到的数据有效(所有正常通信)。
FIN(Finish,结束):正常关闭连接(四次挥手)。
RST(Reset,重置):强制终止连接,通常表示异常情况(端口未开放、异常中断)。
PSH(Push,推送):要求接收方立即将数据传递给应用层,而非缓存(实时数据传输)。
URG(Urgent,紧急):标记报文中的紧急数据,需优先处理(中断命令、紧急操作)。
检验和(Checksum)
类似于 UDP 的校验和,把报头和数据载荷放到一起计算校验和。
序列号(Sequence Number)
序号用于标识TCP报文中发送的数据的第一个字节,它是一个32位的数值,用于跟踪发送的数据流。
数据顺序性:序号确保接收方能够按照发送方发送的顺序正确地重组数据。即使数据包到达的顺序与发送顺序不同,接收方也可以根据序号重新排序。
数据完整性:接收方可以根据序号检测丢失的数据包,并请求发送方重传丢失的数据。
假设A向B发送数据,A发送的第一个数据段包含10个字节的数据,那么A在该数据段的TCP报头中将序号设置为1。如果下一个数据段包含5个字节的数据,那么序号将设置为11(1+10)。
确认号(Acknowledgment Number)
确认序号用于告知发送方,接收方已经成功接收到了哪些数据。它是一个32位的数值,表示接收方期望收到的下一个字节的序号。
反馈机制:接收方通过发送确认序号,向发送方反馈数据接收的状态,告知发送方哪些数据已经正确接收,哪些数据可能丢失或需要重传。
流量控制:发送方可以根据确认序号调整发送速率,确保网络不会因为过载而丢包。
假设A向B发送数据,B正确接收了序号为1到10的数据。那么B在回复给A的TCP报文中,将确认序号设置为11,表示B期望接收下一个序号为11的数据,
序列号:标识发送数据的第一个字节,用于确保数据的顺序性和完整性。
确认序号:告知发送方接收方期望接收的下一个字节的序号,用于反馈数据接收状态和实现流量控制。
确认应答【确保可靠性】
补充:可靠传输
网络通信过程是复杂的,无法确保发送方发出去的数据100%能够到达接收方。TCP的可靠性只能"退而求其次",只要发送方能够知道对方是否收到,就认为是"可靠传输"。
用来确保可靠性,最核心的机制就是 确认应答
应答报文(ack):发送方发完数据手,接收方收到数据就会返回一个应答报文,当发送方看到应答报文之后,就确定上个数据是传输成功了。
正确案例
就像我请女神去吃饭,女神欣然给我回应,这样我就知道女神接收到了我要请他吃饭的消息。
发送方发出数据,接收方给一个回应,就知道了接收方收到了发送方的数据。
后发先至
由于数据传输的过程中会经过很多设备,路由器交换机。如果数据1先发送但是数据堵在了半路,数据2虽后发送,但是路线通畅,可能就会导致后发先至 。
可能就会导致接收方先接收到数据2并且返回 ack(应答报文) 给发送方,此时发送方就以为是数据1的应答报文,这就乱套了。
为了解决上述问题,引入了序号和确认序号,对于数据进行编号,应答报文里就告诉发送方说,这次应答的是哪个数据。
发送方先发送序号为1的数据,后来发送了序号为2的数据,但是由于网络路径的原因可能序号为2的数据先被接收,此时序号的作用就是在接收方内会根据序号大小对数据进行排序,按照从小到大的顺序返回给发送方。
超时重传【处理丢包】
如果一切顺利,通过应答报文就可以告诉发送方,当前数据是不是成功收到。但是,网络上可能存在 "丢包" 情况。如果数据包丢了,没有到达对方,对方自然也没有 ack 报文了。这个情况下,就需要 超时重传 了。
当认为丢包之后,就会把刚才的数据包再传输一次(重传)
等待的过程有一个时间的阈值(上线),就是(超时)
TCP 可靠性就是在对抗丢包,期望在丢包客观存在的背景下,也能够尽可能的把包给传过去。
什么是丢包?
网络中的路由器/交换机,不仅仅是给这一次通信提供服务,还要能支持千千万万的主机之间的通信。整个网络中,就可能存在,某个路由器/交换机,某个时刻,突然负载量很高 ,短时间内可能有大量的数据包要经过这个设备转发。但是,一台设备能够处理的数据量是有限的! 很可能瞬间的高负载超出了这个设备能转发的数据量的极限!此时多出来的部分,就没了,也就是被设备丢包了。如果在传输过程中,碰巧某个数据包遇上了上述的情况,此时就会出现丢包。
丢包情况客观存在,啥时候丢包,难以预测。
如果一台设备在同一时间涌入大量数据,超过设备本身承载的极限,就会丢弃一些数据,就会发生丢包。
丢包的情况在网络通信中还是很常见的。但是发送方接收不到ack报文(接收方返回的确认应答的报文),有两种情况:1.数据在发送的时候发生丢包,接收方并没有收到数据(发送请求过程丢包)。这种情况接收方本身就没收到数据,此时重传理所应当,没有任何问题。
2.接收方接收到了数据,但是返回给发送方的ack报文丢了(返回响应过程丢包)。数据已经被 B 收到了,再传输一次,同一份数据,B 就会收到两次。如果发的请求是扣款请求呢??
针对第二种情况,在没有收到ack报文,但是数据已经被接收方收到。那么TCP会怎么处理?
TCP socket 在内核中有内存缓冲区 ,发送方发来的数据是要先放在接收方的缓冲区中的,然后应用程序调用 read 或者 scanner.next 来读取数据。
当发送方没接收到 ack报文 的时候,就会超时重传,发送的数据如果在接收方的内存缓存区中,此时新的数据会用自己的序号和缓冲区数据的序号做比较,如果有序号一样的,就丢弃新数据。
但是如果数据被应用程序读取走了,此时新的数据在缓冲区就无法和原来的数据比较序号了,我们应该怎么办?
注意,应用程序在读取数据的时候,是按照序号的从小到大顺序连续读取的。此时socket api会记录上一次读取的数据的序号是多少。
比如上一次读取的数据的序号是3000,新收到的数据包的序号是1001,由于应用程序在缓冲区读取数据都是从小到大的,所以1001已经读取过了,这里又重新传来序号为1001的数据,所以就重复了,直接丢弃。
网络连接
三次握手
TCP中"三次握手"使用来建立客户端与服务器的连接,此处谈到的连接"虚拟的、抽象的"连接,目的是让通信双方都能保存对方的相关信息。
在连接过程中,客户端一定是主动的一方
客户端先发送syn(synchronize,同步报文段),syn不会携带应用层数据,相当于告诉服务器,我是谁。
务器接收到这个数据包之后会给客户端返回一个ack(应答报文),就是告诉客户端,我收到了。
紧接着,服务器也给客户端发送了一个syn数据包,里面包含了服务器的信息,此时就和我们连接的概念一样了,双方都保存了对方的信息。
客户端收到了服务器的syn后会给服务器返回一个ack,告诉服务器我收到了,这样就建立了连接。
syn(同步报文段):是一个特殊的TCP数据报,这样的数据包, 不携带载荷, 没有应用层数据,也就不代表任何应用程序的业务逻辑,syn 起到的作用就只是"打招呼",所以把这个动作形象的称为"握手"。
实际过程中,中间服务器给客户端的两次的交互合二为一,最终形成了"三次握手"。
三次握手建立连接的意义
1.可以先针对通信路径,进行投石问路,初步检查下通信线路是否畅通。
2.验证通信双方,发送能力和接收能力是否正常。
3.过程中也会协商一些参数(谨防出现"后发先至"的问题)。
四次挥手
断开连接需要进行四次挥手,和三次握手(建立连接)不同的是,三次挥手必须是客户端是主动的一方,主动发起连接请求,而四次挥手(断开连接)服务器和客户端主动都可以。
FIN:结束报文段,是TCP的6个标志位中的最后一个,用于断开连接,FIN是通过close来触发。
断开连接的状态转换
LISTEN 状态:就是指 ServerSocket 已经创建好,并且绑定了端口号,可以进行连接请求了,这个状态只有服务器才有。
ESTABLISHED 状态:此时就是已经建立连接完毕。
CLOSE_WAIT 状态:被动的断开连接,接收方知道对方不再发送数据,但是自己这边的可能还有数据要发送,所以不会立即关闭连接,等到逻辑执行完毕,调用socket.close()方法向对端发送FIN。
TIME_WAIT状态:主动断开连接的一方,本端先给对面发送了FIN之后,对端也给本段发送了FIN,于是本端就进入了TIME_WAIT,它存在的意义就是为了防止最后一个ACK"丢包"。
如果在TIME_WAIT断开连接,那么如果最后一个ACK丢包的话,那么服务器这边没有收到ack报文,于是会重新传FIN数据包,但是已经断开了连接,重传的FIN也就无法返回ACK了。
此时TIME_WAIT最多会等待2MSL(一分钟),如果这么长时间都没重传,就认为客户端返回给服务器最后的那个ack也已经被收到了。
滑动窗口
TCP的可靠传输固然很好,但是付出的代价也是很多的,每次传输一个数据后都要等待接收到ack报文,才继续传输下一个,导致大量的时间都浪费在等待返回ack上了。
滑动窗口可以保证可靠性的基础上提高效率。

滑动窗口的核心机制就是批量传输 。未加入滑动窗口前是发送一条数据,收到ack后,才能发送下一条数据。
批量传输,就是连续发好几条数据,不等ack,连续发了一定数据之后统一等ack,这样就减少了总的等待时间。
步骤:
批量发送四份数据(1-1000、...、3001-4000),发送这四份数据之后,等待ack,暂时先不传了,就把这能够传输的数据量成为"窗口大小"。
批量发了四个数据,就会对应四个 ack,此时四个 ack 也不一定是同时到达,而是有先有后。
啥时候能继续发下一个数据(5001 这个数据呢),批量发送了四个数据之后,等到返回了一个ack,就往后发一组,接着返回接着发一组,以此类推。此时意味着,窗口大小没变但是位置变了。
滑动窗口,保证可靠性是前提,但是如果出现丢包会怎么办呢?
1)ack丢了
这里我们假设1-1000的确认序号为1001的ack报文丢了,但是确认序号为2001的ack报文成功返回。
确认序号返回有两个意思(假设确认序号为2001):
(1).意味着前2000个数据都已经被成功接收了
(2).意味着可以开始传输2001开始往后的数据
所以这里ack报文并不影响

2)数据丢了
虽然ack报文丢了不会影响,但是数据丢了需要重传。
如果数据丢失(如1-1000未到达接收方),TCP会因未收到ACK而触发超时重传: 接收方因未收到1-1000,不会发送ACK
1001,而是持续回复上一个有效ACK(如ACK 1); 发送方等待超时后,重传丢失的数据段(1-1000);
只有1-1000成功到达后,接收方才会继续确认后续数据(如ACK 2001)。
核心机制:TCP依赖超时重传确保丢失数据被恢复,而累积ACK仅对按序到达的数据生效。
如果有数据丢包了,返回ack报文,此时数据重新传输过去,成功之后,其他数据才能接着进行传输。
流量控制
通过滑动窗口来提高传输效率。窗口大小越大,更多的数据用同一块时间等待,效率就会越高,但是窗口大小能无限大吗?
不能。如果发送的数据太快,接收方的内存缓冲区满了,接收方处理不了,此时也会发生丢包。
与其知道内存缓冲区满了,不如提前感知到,减慢发送速度,改变窗口大小。
TCP报头的选项包含了窗口扩展因子,实际上设置的窗口大小是 16位窗口大小*2^窗口扩展因子
流量控制的思想就是通过接收方内存缓冲区的大小来决定传输速率,因为接收方返回的 ack报文会带上接受缓冲区剩余的大小。先传入1-1000的数据,接收方成功收到后,接收方还剩3000字节的空间,用ack告知发送方还剩3000字节的空间,继续传输1001-2000、2001-3000、3001-4000。
当传输完4000字节数据后,发现接收缓冲区满了,并且知道接收缓冲区一直是接收数据,数据并没有被应用层读取,由于已经满了,发送方就不能再发送数据了,否则会发生丢包,那么什么时候才可以继续传输数据呢?
此时发送方会时不时的出发"窗口探测包","窗口探测包"不携带任何载荷,不会对业务产生影响,但是会出发
ack,此时一旦有数据在接收方被读取,缓冲区就会有空闲空间并且通过ack返回给发送方,此时发送方就可以继续发送数据了。
拥塞控制
流量控制是针对接收方的,而拥塞控制就是针对数据传输中路线的设备的。因为网络通信本身也是生产者-消费者模型,发送方是生产者,接收方是消费者,而中间的传输线路的设备(路由器,交换机)就是阻塞队列。如果生产者生产数据太快,此时阻塞队列就会满了,必须等消费者消费一波才可以继续发送。
在网络通信中如果传输数据太快,超过设备本身负载量,也会发生丢包。

此时把整个中间传输的线路看作一个整体,通过"实验"的方式找到一个合适的传输速率。
如果按照某个窗口大小发送数据之后,出现丢包,就视为中间路径存在拥堵,那么就要减少窗口大小,如果没出现丢包,就要增大传输速率(增大窗口大小)。
拥塞控制是怎么把窗口大小试出来的1.慢启动,刚开始传输数据的时候,速率是比较小的,采用的窗口也就比较小
2.如果上述传输数据的时候没有发生丢包,那么就增大窗口大小,就是增大传输速率(这里是按照指数增大)
3.指数增长,当指数增长传输达到阈值,指数增长就会变成线性增长
4.线性增长也是一直在增长,到达一定传输速率,还是会发生丢包,一旦出现丢包,就把滑动窗口重置成最小的值,回到第一条慢启动的过程,并且也会重新设置阈值
延时应答
接收方在收到数据之后不会第一时间向发送方返回ack(确认报文),而是稍等一会,等这一会,接收方可能会在内存缓冲区中读取一些数据,缓冲区空间就会增大,此时再返回ack,那么窗口大小可能会设置的更大一点。
延时应答具体怎么延时,也不是单纯的按照时间,而是按照"ack"丢了来处理。
滑动窗口传输数据的时候如果ack丢了,影响其实不大,因为接收方从缓冲区读取数据都是按照序号的从小到大顺序读取的,如果1-1000的数据的ack丢了,但2001的ack返回了,也就知道了1-1000的数据也被读取成功了。
正常每次传输一个数据(1-1000)都会返回一个ack,这里就可以每隔几个数据返回一个ack,这样就起到了延时应答的效果,并且不用频繁的返回ack,也节省了开销。
通过减少ACK报文数量来优化网络性能:接收方不会立即确认每个数据包,而是等待后续数据到达(如200ms或2个报文),通过更高序号的累积ACK(如ACK 2001)一并确认之前的数据(如1-1000)。若超时无新数据,则单独发送ACK。这样既节省带宽,又利用TCP的累积确认特性保证可靠性,避免频繁ACK的开销。
捎带应答
基于延时应答机制,可以将 ack(应答报文) 和 requst/response 一起发送/接收,从而提高效率。
正常我们传输一条数据后,接收方会立即返回一个ack,这是TCP机制控制的,由系统内核返回的。如果我们用延时应答,此时ack可能会和response或者requst一起发送,就达到了捎带的效果,
利用延时应答+捎带应答,四次挥手也可能会变成三次。
捎带应答是一种优化通信效率的机制,它利用延时应答的特性,将 ACK(确认应答)与后续的请求或响应数据合并发送,而不是单独发送 ACK 报文。这样可以减少网络中的小报文数量,降低通信开销,提高系统吞吐量,同时减少往返延迟。
面向字节流
"粘包问题",基于TCP协议的字节流传输过程中,接收端在读取数据时,可能会一次性读取多个数据包,或一个数据包被拆分成多个读取操作的情况。
如何解决
1.通过特殊符号作为分隔符,见到第一个分割符,就认为一个包结束了
2.指定出包的长度,比如在包开始的位置上,加上一块特殊空间表示整个数据的长度
粘包问题是TCP引起的,但是TCP本身不会解决,需要程序员写代码,xml、json、protobuffer都能够处理好粘包问题。
异常情况
- 有一方出现了进程崩溃
进程无论是正常结束,还是异常崩溃,都会触发到回收文件资源,关闭文件这样的效果 (系统自动完成的)
就会触发四次挥手
TCP连接的生命周期,可以比进程更长一些,虽然进程已经退出了,但是 TCP 连接还在,仍然可以继续进行四次挥手
虽然说是异常崩溃,实际上和正常的四次挥手结束,没啥区别,进程不再了,是通过系统中仍然持有的连接信息,完成后续的挥手过程的 - 有一方出现了关机 (按照正常流程关机)
当有个主机,触发关机操作,就会先强制终止所有的进程,(类似上述的强杀进程)终止进程自然就会触发 4次挥手。
- 有一方出现了断电(直接拔电源.也是关机,更突然性的关机)
当有个主机,触发关机操作,就会先强制终止所有的进程,(类似上述的强杀进程)终止进程自然就会触发 4次挥手。
- 网线断开
这个情况本质上就是 有一方出现了断电 中的 a) 和 b) 的结合了
2.3 KCP协议
KCP 协议,在国内游戏圈子里用的比较多。
TCP是高可靠性,低效率
UDP是无可靠性,高效率
KCP是低可靠性,较高效率
3. 网络层
网络层最重要的协议就是IP协议,其主要完成的工作为:
地址管理,使用一套地址体系(IP地址),来描述互联网上每个设备所处的位置。
路由管理,管理数据包如何从网络中的某个地址,传输到另一个地址。
每个互联网设备都有IP地址,包括电脑、手机、服务器、路由器等。
3.1 IP协议

3.1 IP协议报头
4位版本
指的是当前IP协议版本,一般是IPv4、IPv6。
4位首部长度
IP 报头长度,像TCP一样可变长的。
8位服务类型(type of service,TOS)
实际上只有4位有效,并且这4位彼此冲突,如果其中1位为1,剩下3位就都是0,这四位表示当前IP协议所处的模式。
(1)最小延时:表示数据传输中尽量减少延迟
(2)最大吞吐量:表示传输中尽量增加传输速率
(3)最高可靠性:虽然不像TCP那样可靠,但是也保证了一定的可靠
(4)最小成本:硬件的开销
16位总长度(字节数)
描述了一个IP数据报的长度(报头+载荷)。
这里IP协议最大也是只能传输64kb的数据吗?如果构造一个非常大的 TCP 数据,IP 是否就传输不了了呢?
虽然 IP 自身有长度限制,但是 IP 也提供了 拆包/组包 这样的功能。此时,载荷就可以搞一个超过 64KB 也没关系。在 IP 这一层会自动拆成多个 IP 数据报,每个 IP 数据报来携带载荷的一部分,拆包过程都是 IP(系统内核) 自动完成。
组包:
16位标识
指的是哪些数据报的载荷应该往一起组装
3位标志
只有2位有效,其中1位标识当前数据是否已经拆包了,还有一位数据表示结束标记。
13位片偏移
表示这些包之间的先后顺序。
8位生存时间(TTL)
生存时间不是s/ms,它指的是次数 。当数据经过一个路由器转发 TTL-1,本质上是为了防止数据一直传输下去。TTL一般来说都是32/64位的整数。(实际的网络环境中,32位的整数,已经足够在国内访问美国的网站了)
8位协议
表示在传输层使用的哪个协议(TCP/UDP)
16位首部校验和
只是针对IP报头的校验,载荷已经在传输层(TCP/UDP)检验了
32位源IP地址和32位目的IP地址
表示发件人和收件人地址
3.2 IP协议如何管理地址
IP地址就是一个32位整数,为了方便,用"点分十进制"表示,通过3个 . 分成了四个部分,每个部分一个字节,每个部分的取值是0-255。
c
IPv4 地址 . . . . . . . . . . . . : 192.168.1.181
IP地址存在就是为了区分网络上不同的设备
32位的整数表示数据范围大概是42亿9千万,但是现如今的互联网时代,这些IP地址依旧无法满足,该如何解决?
动态分配IP地址
利用时间差,有人上网,也有人休息,不是全世界人民同时一起上网。
NAT机制(网络地址映射)
我们先把IP地址分为两大类
公网的IP必须是唯一的。
私网上/局域网上的设备,使用私网IP,只要保证局域网内部的IP不重复即可,不同的局域网之间的IP允许重复。
由于上述设定,就有一个重要的限制:
1.公网设备访问公网设备,没有任何问题,直接访问即可
2.局域网设备访问局域网设备(同一个局域网中),也没有任何问题
3.局域网设备访问局域网设备(不同局域网中),不允许访问
4.局域网设备访问公网设备,就需要对局域网设备的 IP进行地址转换
5.公网设备访问局域网设备,不允许主动访问
当时把 UDP 服务器放到了云服务器上,云服务器带有公网 IP
上述替换,本质上就是让一个公网 IP 能够对应到多个设备,从而起到节省 IP 地址的效果。
一个局域网内多个主机同时通过一个路由器访问同一个服务器:
路由器会将相关信息存放起来
当主机1发送数据,此时路由器接收,将主机1的IP地址修改为5.6.7.8,此时路由器中会有一个表来保存主机1的信息(主要包括主机1的源IP,主机1的源端口号,目的IP)。当主机2发送数据,此时路由器接收,将主机2的IP地址修改为5.6.7.8,此时路由器中会有一个表来保存主机2的信息(主要包括主机2的源IP,主机2的源端口号,目的IP)。
当服务器处理请求结束后返回给路由器,此时路由器会根据会根据返回的数据和表中的数据进行对比,因为两个设备都是访问同一个服务器,所以不能根据上面访问服务器的IP地址进行IP转换并且返回给每个主机,这时就要根据表中存储的端口号进行返回。
当同一局域网内的不同设备访问同一个服务器,此时路由器在收到数据后,会有一个表,表内存储了替换之前的IP、以及两个设备各自的端口号、以及目的IP和目的端口。当服务器返回响应给路由器的时候,路由器就不能根据访问的IP地址来转换IP地址了,因为两个设备访问的都是同一个服务器。此时就路由器就会拿出刚才的存储的表,通过源端口号进行分别,并且将IP地址转换回来。
客户端分配的端口号是系统随机分配的,可能会存在两个端口号相同的情况,那么我们该如何返回数据
虽然我们认为不同的设备可能系统分配的端口号不会一样,但是是在大多数情况,如果是极少数情况我们会做如下应对。
如果两个设备端口号一样,并且访问的服务器也一样。那么我们将IP地址在路由器转换后,会先将端口号也替换掉,然后当服务器返回响应的时候,路由器会拿出存储好的表,根据替换好的端口号来将替换好的IP协议进行转换并且返回。
在 NAT 机制之下,当前的网络是足够使用的了。本质也是提高 IP 的利用率,没有增加 IP 地址,而是让 IP 地址被复用了也不能从根本上解决问题,是一个权宜之计,要比动态分配好很多。
当前互联网就是 动态分配 + NAT 相结合产生的方案。
IPv6
IPv6 从根本上解决问题。
将IP地址从32位增加成了128位
IPv4 使用 4 个字节表示 IP 地址,2^32(42亿9千万)
IPv6 使用 16 个字节表示 IP 地址, 2^128
IPv6 迟迟没有完全落地,是因为所有的硬件设备都得换,而且对体验提升不大。
中国已经覆盖到70%,虽然使用率一般,但是防止了美国在IP地址上的垄断操作。
IPv4的网段划分
网段划分的目的是为了进行组网。
当前的网段划分的操作:
把一个 IP 地址,分成两个部分。网络号 +主机号
路由器的一个功能DHCP,自动会把局域网的所有设备的IP都分配好。
家用宽带来说,一般默认就是前三个字节是网络号
如果一个局域网中,网络号和主机号都相同,这个时候是无法上网的如果局域网中的设备,网络号和路由器的网络号不相同,也是无法上网的(这个主机无法通过路由器访问外网,也不能访问局域网中的其他设备)
两个相邻的局域网,网络号不能相同(一个路由器连接的两个网络,就是"相邻"的)
之前的网段划分的操作(教科书):

3.3 路由选择
由于网络结构错综复杂,每个路由器都掌握了局部信息,无法掌握全局的信息。
不同于我们现在使用的地图软件,只需要我们将起点和终点输入就可以得到很多条路线,这是在上帝视角看来,站在全局的角度,而我们可以根据路线找到最优解,对于地图而言,出发之前,路线就已经规划好了。
而对于路由器规划的网络传输线路,则是摸索式,自己一点一点走,路线不是固定的,出发之前并不是按照某一条线路固定的走。这种传输就像是我们没有导航一样的,从家里出发去到某一个地点,期间可能会找很多人问路,直到走到终点。
如何判定数据包应该发到哪里?
这就依赖路由器存储了一个路由表结构,能找到路线就直接发送,否则按照默认发送。
4. 数据链路层
数据链路层的核心协议就是以太网

数据链路层引入另外一种地址体系:mac地址/物理地址。
和IP地址不同的是,IP地址注重的是全局的转发,从起点到终点,而mac地址则是相邻设备之间的转发。 举个例子:
假如我们从长沙出发前往北京,具体路线是长沙-武汉-石家庄-北京 长沙->武汉 源IP:长沙;目的IP:北京;源mac:长沙;目的mac:武汉
武汉->石家庄 源IP:长沙;目的IP:北京 源mac:武汉;目的mac:石家庄 石家庄->北京 源IP:长沙;目的IP:北京;
源mac:石家庄;目的mac:北京 类似我们传输数据,每次经过一个交换机/路由器的时候,IP地址是不变的,但是mac地址会改变。
因为mac地址的表示范围比IPv4大很多,所以mac地址是静态分配的,机器出厂的时候就分配一个,一直都只是这一个不会像IP地址一样可能会发生改变。
鉴于mac地址一个机器就有一个mac地址,有些程序可能会使用mac地址作为身份标识(比如外挂)。
1.目的地址:需要发送到的MAC地址2.源地址:发送的源地址
3.类型 分为几种类型
类型 0800 + 数据报(16-1500字节) 可能是syn这样的特殊报文也可能是正常的业务数据
类型0806 ARP 请求/应答 + P
类型8035 RARP请求/应答 + PAD
ARP就是能够让路由器在内部建立一个结构 可以通过IP地址找到mac地址
RARP和ARP一样,但是是通过mac地址找到IP地址
关于DNS(域名解析系统)我们如果使用IP地址描述网络设备的位置,可读性不是很好。
这里我们用一串可读性更好的单词描述设备的位置,这就是域名。
www.baidu.com就是域名
上古时期都是引入一个hosts文件,这里有很多文本,有很多IP和域名,通过hash表存储,每次访问域名就会进行查询,返回对应的IP地址。
随着互联网的高速发展,不停的维护hosts文件也会耗时耗力,于是大佬们就搭建了一组服务器,来提供域名解析的服务,某个主机想要访问某个域名,此时就先查询一下域名解析服务器,将IP地址返回给主机。
如果全世界的大量的用户访问DNS服务器,服务器不就崩了?
解决办法就是每个国家的运营商通过搭建"镜像服务器",我们上网的时候,直接访问咱们中国的"镜像服务器"即可。