目录
1.应用层
2.传输层(核心问题)
2.1 UDP协议
2.1.2 UDP的特点
2.1.3 基于UDP的应用层协议
2.2 TCP协议(重点内容)
2.2.1 TCP/IP 协议含义
2.2.2 TCP协议端格式:
2.2.3 TCP的特点
2.3 TCP原理
2.4 确认应答机制(安全机制)
2.5 超时重传机制(安全机制)
2.5.1 数据直接丢了,接收方没收到,自然不会发 ack
2.5.2 接收方收到数据,返回的 ack 丢了
2.6 TCP 是如何实现可靠性的?
2.7 连接管理机制(安全机制)(三次握手与四次挥手)(整个网络原理最核心的考点)
1️⃣TCP 建立连接:三次握手
❓2.7.1 为什么需要三次握手,两次不行吗?
❓❓2.7.2 四次握手呢?
2️⃣TCP 断开连接:四次挥手
❓❓❓2.7.3 为啥三次握手能100%合并,四次挥手就不能合并?
❓❓❓❓2.7.4 挥手为什么需要四次?
2.8 滑动窗口(效率机制)
2.8.1 批量发送的过程中,如果出现丢包咋办??
2.8.2 滑动窗口、快速重传是在批量传输大量数据的时候,会采取的措施
2.9 流量控制(安全机制)
2.9.1 如何实现流量控制
2.10 拥塞控制(安全机制)
2.10.1 拥塞窗口(拥塞控制实验出来的窗口)
2.11 延迟应答(效率机制)
2.11.1 如何进行延时应答
2.12 捎带应答(效率机制)
2.13 面向字节流
2.13.1 解决粘包问题
2.14 TCP异常情况
2.15 TCP小结
2.16 TCP 和 UDP 的差别
按照网络协议这几个层次展开:
应用层和代码直接相关的一层,决定了数据要传输什么,拿到数据之后如何使用;应用层虽然存在一些现有的协议(HTTP),但是也有很多情况,需要程序员自定制协议
约定应用层数据报,数据结构就是在自定义协议
❗❓如何约定?
1️⃣确定要传输哪些信息(根据所需求来)
2️⃣确定数据按照什么样的格式来组织(随意约定的)
网络上传输的,本质上都是 0101,视为二进制的字符串,需要把上述这些信息整合成一个字符串
实际开发中,有一种现成的格式,可以直接使用的:典型的格式1️⃣xml ——通过标签的形式来组织2️⃣json
2.1.1 UDP协议端格式:
1️⃣无连接 :知道对端的IP和端口号就直接进行传输,不需要建立连接;
2️⃣不可靠 :没有任何安全机制,发送端发送数据报以后,如果因为网络故障该段无法发到对方,UDP协议层也不会给应用层返回任何错误信息;
3️⃣面向数据报 :应用层交给UDP多长的报文,UDP原样发送,既不会拆分,也不会合并;
如果发送端一次发送 100 个字节,那么接收端也必须一次接收 100 个字节;而不能循环接收 10 次, 每次接收10 个字节。
4️⃣缓冲区 :UDP只有接收缓冲区,没有发送缓冲区
UDP 没有真正意义上的 发送缓冲区 。发送的数据会直接交给内核,由内核将数据传给网络层协议进行后续的传输动作;UDP 具有接收缓冲区,但是这个接收缓冲区不能保证收到的 UDP 报的顺序和发送 UDP 报的顺序一 致;如果缓冲区满了,再到达的UDP 数据就会被丢弃;
UDP的socket既能读,也能写,这个概念叫做 全双工
5️⃣大小受限 :UDP协议首部中有一个16位的最大长度。也就是说一个UDP能传输的数据最大长度是64K(包含UDP首部)。
NFS:网络文件系统
TFTP:简单文件传输协议
DHCP:动态主机配置协议
BOOTP:启动协议(用于无盘设备启动)
DNS:域名解析协议
当然,也包括你自己写UDP程序时自定义的应用层协议
✨从字面意义上讲,有人可能会认为 TCP/IP 是指 TCP 和 IP 两种协议。实际生活当中有时也确实就是指这两种协议。然而在很多情况下,它只是利用 IP 进行通信时所必须用到的协议群的统称。具体来说,IP 或 ICMP、TCP 或 UDP、TELNET 或 FTP、以及 HTTP 等都属于 TCP/IP 协议。他们与 TCP 或 IP 的关系紧密,是互联网必不可少的组成部分。TCP/IP 一词泛指这些协议,因此,有时也称 TCP/IP 为网际协议群。
互联网进行通信时,需要相应的网络协议,TCP/IP 原本就是为使用互联网而开发制定的协议族。因此,互联网的协议就是 TCP/IP,TCP/IP 就是互联网的协议
源 / 目的端口号:表示数据是从哪个进程来,到哪个进程去;32 位序号 /32 位确认号:后面详细讲;4 位 TCP 报头长度:表示该 TCP 头部有多少个 32 位 bit (有多少个 4 字节);所以 TCP 头部最大长度是 15 * 4 = 606 位标志位 :URG :紧急指针是否有效ACK :确认号是否有效PSH :提示接收端应用程序立刻从 TCP 缓冲区把数据读走RST :对方要求重新建立连接;我们把携带 RST 标识的称为 复位报文段SYN :请求建立连接;我们把携带 SYN 标识的称为 同步报文段FIN :通知对方,本端要关闭了,我们称携带 FIN 标识的为 结束报文段16 位窗口大小:后面再说16 位校验和:发送端填充, CRC 校验。接收端校验不通过,则认为数据有问题。此处的检验和不光 包含TCP 首部,也包含 TCP 数据部分。16 位紧急指针:标识哪部分数据是紧急数据;40 字节头部选项:暂时忽略;
1️⃣有连接
2️⃣可靠传输
3️⃣面向字节流
4️⃣全双工
TCP对数据传输提供的管控机制,主要体现在两个方面:安全和效率。 这些机制和多线程的设计原则类似:保证数据传输安全的前提下,尽可能的提高传输效率。
每一个 ACK 都带有对应的确认序列号,意思是告诉发送者,我已经收到了哪些数据;下一次你从哪里开始发。
丢包是一种典型的情况
如果中间任何一个节点出现问题,都有可能导致丢包
每个设备都是在承担很多的转发任务;每个设备转发能力都是有上限的;某一时刻某个设备,上边的流量达到峰值,就可能会引起部分数据被丢失
发送方对于丢包的判定是:一定时间内没有收到 ack
主机A发送数据给B之后,可能因为网络拥堵等原因,数据无法到达主机B; 如果主机A在一个特定时间间隔内没有收到B发来的确认应答,就会进行重发
主机A未收到B发来的确认应答,也可能是因为ACK丢失了;因此主机B会收到很多重复数据;这个时候TCP 会在接收缓冲区中根据收到的数据的序号,自动去重——保证了应用程序读到的数据仍然只有一份
那么,如果超时的时间如何确定?
最理想的情况下,找到一个最小的时间,保证 "确认应答一定能在这个时间内返回"。但是这个时间的长短,随着网络环境的不同,是有差异的。如果超时时间设的太长,会影响整体的重传效率;如果超时时间设的太短,有可能会频繁发送重复的包;
TCP 为了保证无论在任何环境下都能比较高性能的通信,因此会动态计算这个最大超时时间。
大家觉得答案是什么呢
答案是 确认应答+超时重传
✅总结:
1️⃣一切顺利,使用确定应答保证可靠性
2️⃣出现丢包,使用超时重传作为补充
这两个机制是 TCP 可靠性的基石
连接管理机制 包括 TCP 建立连接 与 TCP 断开连接
建立连接一定是由客户端主动发起的
握手(handshake):指的是通信双方进行一次网络交互;相当于客户端和服务器之间,通过三次交互建立了连接关系(双方各自记录对方的信息)
简单图解:
第一次握手:客户端将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给服务器端,客户端进入SYN_SENT状态,等待服务器端确认。
第二次握手:务器端收到数据包后由标志位SYN=1知道客户端请求建立连接,服务器端将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给客户端以确认连接请求,服务器端进入SYN_RCVD状态。
第三次握手:客户端收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给服务器端,服务器端检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,客户端和服务器端进入ESTABLISHED状态,完成三次握手,随后客户端与服务器端之间可以开始传输数据了。
三次握手这个过程本质上是投石问路,验证了客户端和服务器,各自的发送能力和接收能力是否正常
弄清这个问题,我们需要先弄明白三次握手的目的是什么,能不能只用两次握手来达到同样的目的。
- 第一次握手:客户端发送网络包,服务端收到了。
这样服务端就能得出结论:客户端的发送能力、服务端的接收能力是正常的。- 第二次握手:服务端发包,客户端收到了。
这样客户端就能得出结论:服务端的接收、发送能力,客户端的接收、发送能力是正常的。不过此时服务器并不能确认客户端的接收能力是否正常。- 第三次握手:客户端发包,服务端收到了。
这样服务端就能得出结论:客户端的接收、发送能力正常,服务器自己的发送、接收能力也正常。
因此,需要三次握手才能确认双方的接收与发送能力是否正常。
试想如果是用两次握手,则会出现下面这种情况:
如客户端发出连接请求,但因连接请求报文丢失而未收到确认,于是客户端再重传一次连接请求。后来收到了确认,建立了连接。数据传输完毕后,就释放了连接,客户端共发出了两个连接请求报文段,其中第一个丢失,第二个到达了服务端,但是第一个丢失的报文段只是在某些网络结点长时间滞留了,延误到连接释放以后的某个时间才到达服务端,此时服务端误认为客户端又发出一次新的连接请求,于是就向客户端发出确认报文段,同意建立连接,不采用三次握手,只要服务端发出确认,就建立新的连接了,此时客户端忽略服务端发来的确认,也不发送数据,则服务端一致等待客户端发送数据,浪费资源。
四次握手相当于把中间的 syn 和 ack 开分开分别发送,可以但没必要;分成两次发,效率不如合成一次
1️⃣2️⃣上述两个过程和 可靠性 有一点关系仅此而已;但是TCP是通过 确认应答 + 超时重传 实现可靠性的
断开连接是客户端和服务器都有可能先发起的
通信双方,各自给对方发送一个 FIN(结束报文),在各自给对方返回 ACK
四次挥手:即终止TCP连接,就是指断开一个TCP连接时,需要客户端和服务端总共发送4个包以确认连接的断开。在socket编程中,这一过程由客户端或服务端任一方执行close来触发。
由于TCP连接是全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接,收到一个FIN只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个TCP连接上仍然能够发送数据,直到这一方向也发送了FIN。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭。
简单图解:
ack 和 fin 有一定概率合并成一个的!!但是通常情况下,不能合并的
第一次挥手:客户端发送一个FIN=M,用来关闭客户端到服务器端的数据传送,客户端进入FIN_WAIT_1状态。意思是说"我客户端没有数据要发给你了",但是如果你服务器端还有数据没有发送完成,则不必急着关闭连接,可以继续发送数据
第二次挥手:服务器端收到FIN后,先发送ack=M+1,告诉客户端,你的请求我收到了,但是我还没准备好,请继续你等我的消息。这个时候客户端就进入FIN_WAIT_2 状态,继续等待服务器端的FIN报文。
第三次挥手:当服务器端确定数据已发送完成,则向客户端发送FIN=N报文,告诉客户端,好了,我这边数据发完了,准备好关闭连接了。服务器端进入LAST_ACK状态。
第四次挥手:客户端收到FIN=N报文后,就知道可以关闭连接了,但是他还是不相信网络,怕服务器端不知道要关闭,所以发送ack=N+1后进入TIME_WAIT状态,如果Server端没有收到ACK则可以重传。服务器端收到ACK后,就知道可以断开连接了。客户端等待了2MSL后依然没有收到回复,则证明服务器端已正常关闭,那好,我客户端也可以关闭连接了。
三次握手:ack 和 syn 是同一个时机触发的(都是内核来完成的)
四次挥手:ack 和 fin 则是不同时机触发的(ack 是内核完成的,会在收到 fin 的时候第一时间返回;fin 则是应用程序代码控制的,在调用 socket 的close 方法的时候才会触发 fin)
因为当服务端收到客户端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当服务端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉客户端,“你发的FIN报文我收到了”。只有等到我服务端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四次挥手
TCP 要保证的不仅仅是可靠性(提升可靠性往往意味着损失效率),还有效率
对每一个发送的数据段,都要给一个ACK确认应答。收到ACK后再发送下一个数据段。这样做有一个比较大的缺点,就是性能较差。尤其是数据往返的时间较长的时候。
此时 A 这边就花了大量时间在等待 ACK;想逃提高效率,就需要缩短等待时间——这时候就需要 批量发送数据
一次发多条数据,一次等多个 ack
✨这里就是批量发送4条数据,发完之后,统一等待 ack;每次收到一个 ack 就立即发下一条(不是收到4个 ack 再发下一组)
使用一份时间,等待多个 ack;总的等待时间缩短了,整体效率就提升了(是和没有批量发送进行对比的,而不是和 没有可靠性 对比的)
上述批量传输数据的过程,称为 滑动窗口
批量不是无限发送,是发送到一定程度就等待 ack ,不等待直接发送的数据量是有上线的,而且是回来一个 ack 就立即发下一条,相当于总的要批量等待的数据是一致的,把批量等待数据的数量,就称为“窗口大小”
滑动:收到一个 ack 就立即发下一条
可靠性第一,效率靠后
1️⃣ack 丢了——没有任何影响,因为后一个 ack 会对前一个 ack 进行包含,不影响数据的可靠传输
2️⃣数据丢了——重新传输数据
当 1001 这个数据重传过来之后,此时缺失的拼图就补全了;接下来就要从 7001 开始索要的
如果是 4001也缺失,那么收到的 1001-2000 之后,接下来返回的 ack 就是索要 4001;同理反复索要;发送方就会重传 4001
上述重传过程,没有任何冗余的操作;丢了的数据 才会重传,不丢的数据不必重传,整体速度是比较快的,这个重传过程也称为 快速重传
如果只传一条两条、少量的、低频的操作,就不会按照滑动窗口这么搞了,仍然是前面朴素的确认应答和超时重传了
滑动窗口,批量发送;窗口越大,相当于批量的数据越多,整体的速度就越快
✨接收端处理数据的速度是有限的。如果发送端发的太快,导致接收端的缓冲区被打满,这个时候如果发送端继续发送,就会造成丢包,继而引起丢包重传等等一系列连锁反应。这样就会得不偿失,需要通过流量控制来实现
流量控制(也是保证可靠性的机制):本质上就是让接收方来限制一下发送方的速度
接收端将自己可以接收的缓冲区大小放入 TCP 首部中的 "窗口大小" 字段,通过ACK端通知发 送端;
窗口大小字段越大,说明网络的吞吐量越高;接收端一旦发现自己的缓冲区快满了,就会将窗口大小设置成一个更小的值通知给发送端;发送端接受到这个窗口之后,就会减慢自己的发送速度;如果接收端缓冲区满了,就会将窗口置为0;这时发送方不再发送数据,但是需要定期发送一 个窗口探测数据段,使接收端把窗口大小告诉发送端。
发送方的窗口大小 = 流量控制 + 拥塞控制
✨滑动窗口的大小取决于流量控制(衡量了接收方的处理能力)和拥塞控制(衡量了传输路径的处理能力)
传输路径上任何一个设备,处理能力如果遇到瓶颈,都会对整体的传输速率产生明显的影响
拥塞控制:衡量中间节点传输的能力
中间路径上有多少个节点?每个节点当前的情况?甚至每次传输走的路径都不同
✅通过实验的方法,找到一个合适的发送速率:
开始的时候按照一个小的速率发送,如果不丢包就可以提高一下速率(扩大窗口大小);如果出现丢包,则立即把速率再调小;重复上述过程
此外,网络的堵塞情况,也不是一成不变的,即时刻变化的;此时拥塞控制这样的策略就也能很好的适应网络环境
实际发送方的窗口大小 = min (拥塞窗口,流量窗口)
当TCP开始启动的时候,慢启动阈值等于窗口最大值;在每次超时重发的时候,慢启动阈值会变成原来的一半,同时拥塞窗口置回1;少量的丢包,我们仅仅是触发超时重传;大量的丢包,我们就认为网络拥塞;
当TCP通信开始后,网络吞吐量会逐渐上升;随着网络发生拥堵,吞吐量会立刻下降;拥塞控制,归根结底是TCP协议想尽可能快的把数据传输给对方,但是又要避免给网络造成太大压力的折中方案。
TCP拥塞控制这样的过程,就好像 热恋的感觉
TCP 可靠性的核心是确认应答;ACK 要发,但是不是立即发,而是等一会再发
这样做的目的是提高传输速率;
TCP中决定传输速率的关键元素就是窗口大小;窗口大小由流量控制和拥塞控制共同约束
流量控制通过接收方的接收缓冲区的剩余空间大小
延时应答:通过延时让接收方应用程序,趁机多消费点数据,此时反馈的窗口大小就会更大一些,此时发送方的发送速率也就能快一些(同时满足让接收方能够处理过来)——提高传输速率
并不是所有的包都延时:
1️⃣数量限制:每隔N个包就应答一次;
2️⃣时间限制:超过最大延迟时间就应答一次;
基于延时效应,客户端服务器之间的通信模型,通常情况是“一问一答”的模式
客户端服务器通信模式:1️⃣一问一答:绝大多数服务器都是这样的2️⃣多问一答:上传文件3️⃣一问多答:下载大文件4️⃣多问多答:游戏串流
❓❓这里边存在一个 粘包问题
✅这就是一个 粘包问题
所谓的“一句话”就相当于一个“应用层数据报”
当 A 给 B 连续发了多个应用层数据报之后,这些数据就都积累到 B 的接收缓冲区,紧紧挨在一起,此时 B 的应用程序再读数据的时候,就难以区分从哪到哪是一个完整的应用层数据报,这样有可能就很容易读出半个包/一个半...
1️⃣定义分隔符(像如 xml 或者 json,本质上都是通过分隔符的方式来实现)
2️⃣约定长度:约定数据的前4个字节,表示整个数据报的长度(HTTP协议,既会使用分隔符,也会使用约定长度)
都是自定义应用层协议的注意事项
1️⃣进程关闭/进程崩溃:
进程没了,socket 是文件,随之关闭;虽然进程没了,但是连接还在,仍然可以继续四次挥手
2️⃣主机关机(正常流程关机):
先杀死所有的用户进程,也会触发四次挥手。如果挥完,则更好;如果没挥手完,比如对方发的 fin 过来了,这时候还没来得及 ack 就关机了,此时对端就会重传 fin,重传几次之后,发现都没有 ack,尝试重新连接,如果还不行就直接释放连接
3️⃣主机掉电(拔电源):
瞬间关机,来不及任何操作
1)对端是发送方
对端就会收不到 ack ----> 超时重传 ----> 重置连接 ----> 释放连接
2)对端是接收方
对端是没法立即知道,这边是还没来得及发的数据,还是直接没了?
在 TCP 内置了 心跳包——保活机制
a)周期性:虽然对端是接收方,对端就会定期给发一个心跳包(ping),就返回一个(pong)
b)如果心跳没了,挂了:如果每个 ping 都有及时的 pong ,这个时候说明当前对端的状态良好;如果 ping 过去之后,没用 pong,说明心跳没了,怕是这边挂了
4️⃣网络断开:同上
1️⃣TCP的特点有连接 可靠传输 面向字节流 全双工
2️⃣UDP的特点无连接 不可靠 面向数据报 缓冲区
最大的差别是:应用场景不同
1️⃣TCP 可靠传输,效率没那么高(绝大多数场景下,都可以是使用 TCP)
2️⃣UDP 不可靠传输,效率高(对于效率比较高、可靠性要求不高的情况下)