👍 👎 💯 👏 🔔 🎁 ❓ 💣 ❤️ ☕️ 🌀 🙇 💋 🙏 💦 💩 ❗️ 💢
————————————————
文章目录
知识点
简介
- 传输控制协议TCP,是为了在不可靠的互联网络上提供可靠的端到端的字节流而专门设计的一个传输协议
- 互联网络与单个网络有很大的不同,因为互联网络的不同部分可能有截然不同的拓扑结构,带宽,延迟,数据包大小和其他参数。TCP的设计目标是能够动态地适应互联网络这些特性,而且具备面对各种故障时的健壮性
TCP协议的格式
- 源/目的端口号:表示数据是从哪个进程来,到哪个进程去
- 32位序号/32位确认号
- 4个比特位TCP的首部长度,4个比特相当于0-15,所以最大长度是15*4(固定的乘4)=60
- 6位标志位
- 16位窗口大小
- 16位校验和:发送端填充,CRC校验,接收端校验不通过,则认为数据有问题,此处的校验和不光包含TCP首部,也包含TCP数据部分
- 16位紧急指针:标识哪部分数据是紧急数据
- 40字节头部选项
确认应答(ACK)机制
- 这是可靠传输的最核心机制
- 本质来说就是你发的每一个请求,对方都会给你一个应答(ACK)
- 在TCP中就如同下图
- 因为TCP是按照字节传输的,所以TCP将每个字节的数据都进行了编号,即为序列号
- 每个ACK都带有对应的确认序列号,意思是告诉发送者,我已经收到了那些数据,下一次你从哪里开始发
- 此时给请求和应答都带上编号,既能保证数据传输没有歧义,也不会浪费太多的空间和快带
- 标志位ACK为1时,应答机制才生效,确认号才有用
超时重传机制
- 确认应答虽说比较理想,但是数据传输的过程中,因为是在网络传输,所以会有丢包的可能
- 所以无论是哪方丢了,都会触发重新传输报文
- 也就是说当发送了一条数据之后,TCP内部会自动启动一个定时器,到达一定时间还没有收到ACK(应答),定时器就会触发重传消息的动作
- 但两种丢包的方式实际上还有差距
- 那么如果超时的时间如何确定
- TCP为了保证无论在任何环境下都能比较高性能的通信,因此会动态计算这个最大超时时间
连接管理机制(三次握手)
为什么要建立连接
1.验证通信双方发送能力和接受能力是否正常
2.通信双方要协商一些重要的参数
-
在正常情况下,TCP要经过三次握手建立连接
-
举一个打电话的例子
-
TCP真实的连接过程
-
第一次握手:
-
客户端将TCP报文标志位SYN设置为1,随机产生一个序号值seq = j,保存在TCP首部的序列号字段里,指明客户端打算连接的服务器端口,并将该数据包发送给服务端,发送完毕后,客户端进入SYN-SENT状态,等待服务器端确认
-
第二次握手
-
服务器端收到数据包后由标志位SYN=1知道客户端请求建立连接,服务器端将TCP报文标志位SYN和ACK都设置为1,ack = j+1,随机产生一个序号值seq = k,并将该数据包发送给客户端以确认连接请求,服务器端进入SYN_RCVD状态
-
第三次握手
-
客户端收到确认后,检查ack是否为j+1,ACK是否为1,如果正确则建立连接成功,客户端和服务器进入ESTABLISHED状态,完成三次握手,随后客户端与服务器端之间可以开始传输数据了
-
注意:我们上面写的ack和ACK,不是同一个概念
小写的ack代表头部的确认号Acknowledge number,缩写ack,是对上一个包的序号进行确认的号,ack = seq+1
大写的ACK,则是我们上面说的TCP首部的标志位,用于标志的TCP包是否对上一个包进行了确认操作,如果确认了,则把ACK标志位设置为1 -
建立连接的过程,相当于通信双方各自给对方发送SYN 再各自给对方发送ACK应答,只不过中间ACK和SYN合二为一了,于是就形成了三次握手
如果只有两次握手可以吗
- 答案是不可以
- 就拿我刚刚举的那个打电话的例子,如果取消一次握手,双方中有一方就不知道对方的通信是否有异常,所以至少是三次握手
如果是四次握手可以吗
- 可以,但是没有必要
- 如果是将第二部的SYN和ACK拆开,是可以的,但是这样做的效率比较低,传输的一个包的效率肯定比传输两个包的效率高
- 如果是刚刚那个打电话的例子,你打电话反复问听得到吗,没必要
连接中的重要状态
-
CLOSED
处于关闭状态 -
LISTEN
服务器调用new ServerSocket 就会绑定端口号,并且进入LISTEN状态
客户端调用new Socket(ip,port)就会尝试和服务器建立连接并触发三次握手
服务器准备就绪,随时可以有客户端建立连接 -
STN_SENT/STN_RCVD
建立连接的中间过程,如果连接正常,这两个状态都是一瞬间消失的 -
ESTABLISHED
连接建立完毕,随时可以传输消息
断开连接管理机制(四次挥手)
- 挥手请求可以是客户端发起的,也可以是服务器端发起的
- 第一次挥手:客户端发起挥手请求,向服务器端发送标志位是FIN报文段,设置序列号seq,此时,客户端进入FIN_WAIT状态,这表示客户端没有数据要发送给服务器端了
- 第二次挥手:服务器端收到了客户端发送的FIN报文段,向客户端返回一个ACK的报文段,ack设为seq+1,客户端进入FIN_WAIT_2状态,服务器端告诉客户端,我去人并同意你的关闭请求
- 第三次挥手:服务器端向客户端发送标志位FIN的报文段,请求关闭连接,同时服务器端进入LAST_ACK状态
- 第四次挥手:客户端收到服务器端发送的FIN报文段,向服务器端发送标志位是ACK的报文段,然后客户端进入TIME_WAIT状态。服务器端收到客户端的ACK报文段后,就关闭连接。此时,客户端等待2MSL的时候后依然没有收到回复,证明服务器端已正常关闭,然后客户端也可以关闭连接了
四次挥手可以改成三次吗
- 其实四次挥手断开连接本质也是双方发起断开连接请求,再各自给对方回应,只不过中间的FIN和ACK是不能合并在一起的
断开连接的重要状态
-
FIN_WAIT1/FIN_WAIT2
实现TCP连接和断开都需要一定的流程,为了方便记录不同的流程,所以就引进来很多不同的状态,而这两个就是为了记录状态(根据状态识别现在到哪个步骤了)
其实FIN_WAIT1的存在是很短暂的,当客户端发送FIN进入该状态,一旦收到ACK就会进入FIN_WAIT2的状态。如果服务器一直不发送FIN,那么客户端就会一直处于FIN_WAIT1的状态,而服务器就会处于CLOSE_WAIT状态 -
CLOSE_WAIT
此时4次挥手挥了一半(当然有可能挥了一半,剩下的一半就不挥了)也就是服务器没有调用socket.close()方法,从而导致没有正确的关闭连接(比如服务器代码中出现一些异常,导致没有执行close方法)如果服务器上出现大量CLOSE_WAIT状态,说明服务器代码中由BUG,这就是文件资源泄露问题 -
LAST_ACK
只要服务器收到了FIN发送ACK,然后又成功执行了close方法向客户端发送了FIN就会进入LAST_ACK状态 -
TIME_WAIT
这是一个比较重要的状态
谁先主动断开连接,谁就会进入TIME_WAIT状态,其实此时对于主动断开的一方已经完成了4次挥手,但是它仍然不能立刻释放连接,而且要这个状态保持一段时间再彻底释放连接
那么等待时间为什么是2MSL呢?
就要了解为什么TIME_WAIT状态为什么要等待一段时间,其实就是为了确保双方都能真正的进入CLOSED状态,如果一旦最后一个ACK丢失,服务器无法区分是自己FIN丢失了还是对方ACK丢失了,所以服务器都会重新传输FIN 数据包,如果客户端不保持一段时间,服务器重传的FIN就无法有应答ACK了,防止最后一个ACK丢包,无法重传
所以一旦存在大量的TIME_WAIT状态的,这种情况不是BUG,过段时间就好了
所以服务器需要重传FIN的话,就会有两个部分,一部分是发送FIN的时间,二是接受ACK的时间,所以需要2MSL的时间
滑动窗口
- 刚才我们讨论了确认应答策略,对每一个发送的数据段,都要给一个ACK应答,收到ACK后再发送下一个数据段。这样做有一个比较大的缺点,就是性能较差,尤其是数据往返的时间较长的时候
- 滑动窗口的本质就是批量传输数据,总的传输时间:N分数据传输的时间叠加成了一份的时间+N份的应答时间叠加成了一份的时间
窗口是什么
滑动是什么
- 如果出现了丢包,如何进行重传?这里分为两种情况
1.数据报已经抵达,ACK丢了
这种情况下,部分ACK丢了不要紧,因为可以通过后续的ACK进行确认;比如上图的主机A,没有收到ACK1001,但是收到了ACK2001,此时主机A就可以判断是1001的ACK丢失了,无所谓,数据已经传过去了(因为如果数据没有传过去的话,返回的ACK始终是1001,不会出现2001这个ACK)
2.发送的数据包直接丢了
当某一段报文段丢失之后,发送端会一直收到1001这样的ACK,就像是在提醒发送端“我想要的是1001”一样
如果发送端主机连续三次收到了同样一个1001这样的应答,就会将对应的数据1001-2001重新发送(所谓的“快速重传”),这时候接收端收到了1001之后,再次返回的ACK就是7001了(因为2001-7000接收端之前就收到了,放在了接受缓冲区中)
被放到了接收端操作系统内核的接收缓冲区中,这种机制被称为"高速重发控制",而且大部分触发重传的时候,窗口已经是满得了,所以需要等待ACK到了之后才可以继续往后滑动
流量控制
- 接收端处理数据的速度是有限的。如果发送端发的太快,导致接收端的缓冲区满了,这个时候如果发送端继续发送,就会造成丢包,继而引起丢包重传等一系列连锁反应
- 因此TCP支持根据接收端的处理能力,来决定发送端的发送速度,这个机制就是流量控制
- 总的来说,流量控制的本质就是 接收方通过TCP报头中的窗口大小,将自己的处理能力和缓冲区剩余空间大小发送给对方,来制约发送方发送数据的速率
拥塞控制
- 虽然有了TCP的滑动窗口提升速率,高效可靠的发送大量的数据,但是如果在刚开始阶段就发送了大量的数据,仍然可能引发问题因为网络上有很多的计算机,可能当前的网络状态已经比较拥堵,在不清楚当前网络状态下贸然发送大量的数据,是很有可能引起雪上加霜的
- TCP引入 慢启动 机制,先发少量的数据,探探路,摸清当前的网络拥堵状态,再决定按照多大的速度传输数据
总的来说 因为网络的拥堵情况是瞬息万变的,所以窗口的实际大小是动态变化的
延迟应答
- 如果接受数据的主机立刻返回ACK应答,这时候返回的窗口可能比较小,比如我们举个简单的例子,有人前一天晚上问你,你明天中午能吃几块钱麻辣烫,因为你现在不饿,你就说了个就几块钱吧,但是等到明天中午的时候,你就饿了,所以你会吃几十块的,如果你第二天早上回答,会更多,这就是一个延迟应答
- TCP中也有延迟应答
- 所以延迟应答的目的就是为了提高效率,在流量控制的基础上,尽量返回一个合理的而且又比较大的窗口
- 窗口越大,网络的吞吐量就越大,传输的效率就越高,我们的目标是在保证网络不拥堵的前提下,尽量的提高传输的效率,那么所有包都得延迟应答嘛?
捎带应答
-
捎带应答是在延迟应答的基础上,为了进一步提高程序的运行效率而引入的机制
-
我们一开始将内核收到的数据就会立刻返回ACK,但是我们现在引入了 延迟应答之后,我们返回ACK的时候,就会往后拖大约200ms,此时这个时间足够让应用程序完成响应的计算(例如close方法),之后程序在返回Rsep的时候就会发现刚才返回的ACK还没有发呢,就会在这个Resp基础上,顺便带上一个ACK的信息
-
也就是说捎带应答就是因为延迟应答的机制存在,将连续发送两个包的时间在延迟范围内合并成了一个包来发送,去节省带宽,提高效率
-
所以说我们在断开连接(四次挥手)的时候呢,有可能合并成三次也正是因为有延迟应答和捎带应答的缘故
-
但是要是注意,连接过程(三次挥手)中的SYN和ACK的合并并不是因为这个,那是因为接收的SYN和ACK都是在内核中实现的,没有涉及到不同的发送时机
面向 字节流 的粘包问题
- 此处的标题是面向字节流的粘包问题,所以这个问题不是针对TCP,而是只要涉及字节流都会有可能发生这个问题呢
- 所以粘包 是站在应用层的数据包,在处理数据的时候,容易读取半个应用层数据报的情况
- 那么如何避免粘包问题?归根到底,明确两个包之间的边界
- 对于UDP协议来说,是否也存在"粘包问题"呢?
先赞后看,养成习惯!!!^ _ ^♥♥♥
每天都更新知识点哦!!!
码字不易,大家的支持就是我坚持下去的动力。点赞后不要忘记关注我哦!