hi,我们又见面了,今天为大家带来TCP协议,一共为大家介绍TCP协议的十个核心特性
1.确认应答
2.超时重传
3连接管理
4滑动窗口
5流量控制
6拥塞控制
7延迟应答
8捎带应答
9面向字节流(粘包问题)
10异常情况(心跳包)
11基于TCP应用层协议
TCP协议位于传输层,我们知道TCP有一个特点是可靠性,也就是我们知道传输过去的数据是否传到了对端程序,也会尽可能传过去,不论是否传过去,都会有结果
是咋样保证可靠性的呢?
确认应答+超时重传
首先来认识一下确认应答
1.确认应答
来举个例子
我问女神问题,女神回答,女神的回答就是应答报文,这个回答的顺序是对的,但是网络传输往往存在后发先至的可能,啥意思呢,就是我女神先发的好啊好啊,后发的不行抱歉,但是我收到的时候先收到的是不行抱歉,好啊好啊.那么这样就造成信息接收错误了,就像下图画的那样
家人们,这不就尴尬了吗,为了避免后发先至的情况,我们需要针对消息进行编号
给发送的消息分配一个序号,同时给应答报文分配确认序号,真实的TCP也是用序号和确认序号来保证顺序的
就像这样就不会出错了
TCP给每个字节都进行了编号,也就是序列号
当前报文的报头序列为1,报文长度为1000,那么应答报文的确认序号就是1001,应答报文也叫做ACK报文,由英文单词acknowledge而来
发送方的序号和确认序号无关,确认序号是由发送方发过来的所有数据的最后一个字节的下一个字节的序号决定的
确认序号1001的含义:
1.<1001的数据接收方已经收到
2.接下来向发送方索要1001开始往后的数据
接收方就可以通过确认序号知道哪些数据收到了
对于TCP来说,当出现后发先至的状况,是咋样解决的呢?
对于TCP来说,TCP有接收缓冲区,是一块处在内核的内存空间,每个Socket文件都有自己的缓冲区,TCP根据序号针对收到的消息进行排列,(可以用优先级队列实现),应用程序收到的时候和发送顺序是一样的
2.超时重传
但是网络环境是不稳定的,如果没有任何意外,确认应答就保证了传输数据的可靠性,如果传输过程中出现丢包了呢
丢包也是网络上非常典型的情况,为啥会丢包呢?
数据在转发的过程中,每个设备,都是在承担很多的转发任务的,但是每个设备,转发能力是有上限的,某一时刻,某个设备,上面的流量达到了峰值,就可能引起部分数据丢包,这样的情况会出现概率性丢包,如果包丢了,接收方就收不到了,就不会返回ACK,发送方就迟迟收不到应答报文了
发送方等待一段时间以后,还是没有收到应答报文,发送方就认为数据丢包了,就会重新发一遍
网络丢包,是概率性事件,上个包丢了,重传还是有很大机会传过去的
发送方对于丢包的判定
是一定时间内没有收到ACK
1.数据直接丢了,接收方没收到,自然就不会发ACK
2.接收方收到数据了,返回一个ACK,但是ACK丢了
发送方区分不了这两个情况,都只能重传
由主机B返回的确认应答,因网络拥堵等原因在返回传送的途中丢失,没有到达主机A,主机A会等待一段时间,若在特定的时间间隔内始终未能收到这个确认应答,主机 A会对数据进行重新转发,那么主机B就会收到重复的数据,但是不必担心,TCP在接收缓冲区根据接收数据的信号,自动进行去重,从而保证应用程序收到的数据只有一份
那么既然进行了重传,重传的数据也有可能又丢,一旦出现连续丢包,那么说明网络存在严重问题
TCP针对多个包丢失,处理方法是:继续超时重传
每丢包一次,超时等待时间就会变得更长,(重传频率降低),TCP认为重传成功的可能性很小
连续多次重传,都无法得到ACK应答报文,TCP会尝试重置连接,也就是重连,如果重置连接也失败,TCP就会关闭连接,放弃网络通信
总结一下,TCP其实非常负责任,能重传就重传,重传不行重连,重连不行才放弃,它尽可能完成传输
在网络环境下如果一切顺利,那么使用确认应答保证可靠性
若出现丢包,使用超时重传进行补救
确认应答和超时重传是保证可靠性的基石
3.连接管理
TCP建立连接:三次握手
TCP断开连接:四次握手
下面我们来具体介绍一下
三次握手
握手(handshake)指通信双方进行一次网络交互,也就是客户端和服务器之间,通过三次交互,建立了连接关系
场景:小魏见了一博哥
小魏:一博哥,最近过的好吗?
一博哥:还不错,你呢?
小魏:我也还好,嘿嘿🤞
这段话中小魏问候一博哥,一博哥给出回应,就是一问一答,同时一博哥也问候了小魏,小魏也返回一个ack ,交互结束,连接建立完成
图中的syn叫做同步报文段,意思就是一方要向另一方,申请建立连接
ack还是应答报文的意思
注意:
上述连接在内核中完成,应用程序无法干涉,等连接完成了,服务器accept把建立好的连接拿出来到应用程序中,和应用程序建立连接
也就是说建立连接分两个层面
1.内核:自动连接
2.应用程序:服务器的accept完成
那么针对上述的交互过程,一定有人有疑问明明进行了四次交互,为啥说是三次,那么数据在转发过程中,涉及包的封装分用,而且有两次交互都是由服务器进行,为了提高效率,方便起见,就合并成一次交互(也和延时应答有关系,后面讲)
再来说syn报文,是什么样的呢?
观察TCP报头结构
绿色圆圈圈出来的是六个特殊的比特位,默认都是0,如果变为1,就表示特殊含义
从左至右,第二位是ACK,如果这一位为1,表示当前TCP数据报是一个应答报文
第五位,SYN,这一位要是1的话,表示当前TCP数据报是一个同步报文
如果第二位和第五位都是1,这个报文是SYN+ACK
那么我们为啥要进行三次握手呢?
这个过程,起到了投石问路的作用,验证了客户端和服务器各自的发送和接收能力是否正常,保证后续能够正常通信!!!
举个例子:
我和王一博打电话,但是因为是在公共场合,需要双方戴耳机,那么我们就需要验证一下双方的麦克风和耳机是不是正常的
一博哥给我发"歪歪歪,能听到吗",我听到了就说明我知道一博哥的麦克风是好的,我的耳机是好的
我给一博哥回一个"能听到的哥💚",一博哥听到了的话说明他知道他的耳机是好的,我的麦克风是好的.并且他发了"歪歪歪",我给出了回应,说明一博哥知道我的耳机是好的,他的麦克风也是好的,那么此时对于一博哥来说,知道了我们双方的耳机和麦克风都正常,但是对于我来说,我只知道一博哥的麦克风是好的,我的耳机是好的,我不知道一博哥的耳机和我的麦克风是否正常,所以一博哥给我回应:okok哈哈哈,那么我收到这个消息,我就知道我发的回应他收到了,说明我的麦克风正常,他的耳机正常,因此,我也知道我俩的耳机和麦克风都正常,此时两人都知道双方的麦克风和耳机正常,因此就可以正常交流了~~
上述的例子有没有让大家对于判断客户端和服务器各自的发送和接收能力有个更好的理解呢?
四次挥手
建立连接一定是客户端主动发起,断开连接,客户端和服务器都有可能先发起
客户端发送一个FIN(结束报文),(在TCP报文格式图中FIN为1就是结束报文),然后内核回应ACK,然后服务器发现客户端断开连接,自己也进行close操作,就是因为这个操作,触发了第二次FIN,这个close的执行时间看代码是咋写的
三次握手可以完全合并,四次挥手可以合并吗?
三次握手可以完全合并,是因为SYN和ACK是同一个时机触发的(都是由内核完成)
四次挥手,ACK由内核完成,会在收到FIN第一时间返回,FIN是由应用程序代码控制的,会在调用到socket 的close方法才会触发FIN.二者执行时机不同,如果立即调用close,趁着ACK还没发,就可以合并ACK和FIN,如果过了很久才close,那么FIN就是单独发送
因此,ACK和FIN一般不能合并的!!!但是有了捎带应答机制,也能实现,后面讲
那么是否存在第二次FIN客户端收不到的情况,第二个ACK服务器收不到?
不存在!
在咱们写的代码中没有显式的调用close,客户端的FIN咋发的,进程结束,客户端也就结束了,也就会自动进行close,触发了fin,此时客户端结束了,但是TCP连接还在,这是由内核维护的,虽然进程结束了,但是内核还是会继续维护TCP连接,直到四次挥手完成,对于服务器来说道理也是一样的
这张图描述了TCP的三次握手和四次挥手,可以看看
我们上述说的内容都与传输的可靠性有关,我们保证了可靠性,但是也需要保证效率,那么TCP保证传输效率的方式有哪些呢,我们来一起看看
4滑动窗口
看上图的传输过程,一问一答这样传输效率太慢了,主机A花很长时间在等待ACK,我们采用批量发送,一次发多条数据,一次等待多个ACK
这张图就是批量发送四条数据,发完之后等待ack,每次收到一条ACK,就跟着发下一条,比如这个图中,发了1-1000的数据,返回一个ACK之后,紧接着从4001开始发,注意不是收到四个ACK再发下一组,是第一组的一条消息收到ACK,下一组的第一条就开始往下发送了,依次类推
这样的方式就是一份时间,等待多个ACK,总的时间缩短了,整体效率提升
这个批量传输数据的过程就叫做滑动窗口
批量等待数据的数量,叫做出窗口大小收到一个ACK就发下一条,我们来看看图就更加明白了
窗口大小为5,那么12345一起发送,等待ACK,当1的ACK返回,立马发从6开始再发一条,窗口就往后滑一次,依次类推,因为传输速度很快,所以就像一个滑动的窗口,所以叫滑动窗口
当批量发送的时候,出现丢包咋办?
丢包分为两种情况
1.ACK丢了
2.数据丢了
一:数据包抵达,ACK丢了
这种丢包就已经很严重了,但是没关系没有影响,有确认序号,表示该序号前的的数据都已经收到了,由这个图看到下一个是2001发送成功,说明1-1001我已经收到了,1001-2000的我也收到了,也就是2001之前的数据我都收到了,因此1001ACK丢了也无所吊谓😎
那么如果是最火一个丢了呢,没关系,超时重传呗,
二:数据丢了
1001-2000的数据丢了,所以接收方一直在索要1001,不会因为接收到的是2000-3000,就返回3001
接下来的几次收到的ACK都在重复索要1001,确认序号是1001,那么主机A连续收到很多个1001,就知道数据可能丢了,于是重传1001-2000的数据,当重传以后,B收到,确认序号是7001,不是2001,为什么呢,因为2000-7000已经传输成功了
上述的重传过程没有多余的操作,只有丢失了数据才会重传,不丢不重传,速度很快,这个过程叫做快速重传!
滑动窗口和快速重传是在批量传输的数据量很大的时候,会采取的措施.
如果传输数据量很少,就不用这样,用确认应答和超时重传就好
主机B接收到的数据是先放到接收缓冲区上的,然后应用程序通过socket的InputStream创建的实例来读,读完的数据直接从缓冲区删除
当缓冲区满了会阻塞等待吗?
不会,满了就直接丢了,TCP也会控制尽量避免出现丢包的情况,类似阻塞等待的效果,也就是我们下一个要讨论的流量控制
5流量控制
在批量传输的过程中,是数据越多越好吗?
不是!发的太快了,接收方的接收缓冲区瞬间满了,继续发送,就会丢包.
那么我们就要进行流量控制
,就是让接收方限制发送方的发送速度,也就是让发送方阻塞等待一下
咋实现的捏~
看这个红色的框框,当ACK=1的时候,这个窗口大小就起作用了,这里的值就是建议发送方发送的窗口大小
窗口大小咋约定呢?
接收方一般将接收缓冲区的剩余空间作为窗口大小
如果接收端缓冲区满了,就会将窗口置为0;这时发送方不再发送数据,但是需要定期发送一
个窗口探测数据段,使接收端把窗口大小告诉发送端
还是来张图
3000是接收缓冲区的剩余空间,那就批量发三条,然后剩余空间变为0,发送方等待一会,定时发送窗口探测,报文,测一测还是0吗,不是0的话,说明应用程序从socket读取缓冲区内容,就腾出空间了.那么就可以继续发送了~
实际上发送方窗口大小=流量控制+拥塞控制
6.拥塞控制
滑动窗口大小取决于流量控制+拥塞控制
流量控制体现接收方的处理能力,拥塞控制体现了传输路径的处理能力
数据在传输的过程中,任何一个设备.处理如果遇到瓶颈,就会影响整个传输效率
拥塞控制要衡量中间结点的传输能力,中间路径的结点个数以及情况都要兼顾到
通过实验,找到一个合适的发送速率
开始的时候,以一个很小的速率发送,如果不丢包,加快速率,一旦出现丢包,立马降速,这是一个动态的过程
实际发送窗口大小=拥塞窗口和流量控制窗口的最小值
看一张图更好的理解拥塞控制
描述过程:
传输轮次就是发送的次数
刚开始以一个很小的速率发送,然后没有出现丢包,就指数级增长,让窗口大小短时间内达到阈值,接近网络传输路径瓶颈,指数增长到一定值变为线性增长,避免丢包,继续线性增长,增长到出现丢包,那么当前窗口大小到达当前路径上线上限,立即把窗口大小调为最开始的值,重复这个操作,我们可以发现拥塞窗口的阈值在逐步减小
这个过程是一个动态平衡的,重点理解这个策略
7.延时应答
确认应答中的ACK不是立即返回,而是稍等一会再返回
TCP决定传输速率的关键是窗口大小
其中的流量控制中,接收方起限制作用,接收方的接收缓冲区的剩余空间大小和延时应答有关系
如果立即返回ACK,那么ACK带有的窗口大小设为M,如果稍等一会,再返回,ACK返回带有的窗口为N,大小关系是M<N,为啥呢?因为,发送方等的这一会儿,应用程序从缓冲区消费掉了一批数据
延时应答的效果就是,通过延时,让接收方多消费点数据,让发送方的窗口大小大一点,那么发送方的发送速率会更快,缓冲区也腾出空间,接收方也能处理过来
有一个小问题.所有的包都能延时吗?
不能,不是所有的包都能延时,看情况
8.捎带应答
捎带应答是基于延时应答的一种应答方式
一般来说,客户端服务器之间有这样几种应答方式
一问一答:大部分客户端服务器
多问一答:上传大文件
一问多答:下载大文件
多问多答:游戏串流
有了延时应答这样的机制,让本来可以三次握手的四次交互就合并为3次
ACK由内核进行回应,其实当客户端发出请求,ack本来会立即返回,而回应是由应用程序回应的,时机本来是不同的,但是因为延时应答,ack就可能稍等一会再发,那么就很有可能和回应一起发回去,就合并成了一个数据包报,合成一个发,要比分开发效率高
那么四次挥手也有可能是有可能三次挥完的!因为捎带应答机制,fin由应用程序发送,ack由内核发送,ack也是等了一会,带着FIN一起返回给客户端,因此四次挥手也有可能三次就挥完
9.面向字节流
TCP传输是以字节流为单位进行传输的.
不像UDP,是一个一个字符读的,一般不会产生粘包问题
TCP就会产生粘包问题,以字节流传输,分不清哪里到哪里是一个数据
举个例子!
站在我的视角:我的接收缓冲区是这样的:
从哪到哪是一个完整的话?
我们不知道
这就是粘包问题
一句话就是一个应用层数据报,当A给B连续发了多个应用层数据报之后,这些数据就累计到B的接收缓冲区里,紧紧的在一起,应用程序在读的时候,难以区分从哪到哪是一个完整的应用层数据报
数据到了接收缓存区已经被分用过了,把tcp报头都给拆了,就剩下应用层数据报了,那么不能根据确认序号来确定哪个到哪个是一句话
所以解决方案就是
1.定义分隔符
比如说完一句话.以;结尾
2.约定长度:约定数据的前4个字节,表示整个数据报的长度
10.异常情况
1.进程关闭或者进程奔溃
进程没了,socket是一个文件,也就没了,但是连接还在,依然可以完成四次挥手
2.主机关机(正常关机)
关机会先销毁所有进程,也会触发四次挥手,挥完就没事
如果没挥完,对方发来FIN,内核没来得及回应ACK,就关机了,对方就会重传fin,传几次没有ACK,尝试重连,没反应,对方就会放弃连接
3.主机掉电
瞬间机器就关了,来不及进行四次挥手
如果该机器是发送方,收不到ACK,就会超时重传,然后重连,最后释放连接
如果该机器是接收方,无法立即知道发送方是磨蹭会再发还是发送方没有了,TCP内置了心跳包,保活机制,
心跳包具有周期性,心跳一旦没了,就挂了
该机器是接收方,定期给我们发一个心跳包(ping),发送方回一个pong,如果每个ping都有及时的pong,说明网络通信良好,如果ping过去之后,没有pong,说明心跳没了,那么发送方可能就挂了
11基于TCP应用层协议
HTTP
HTTPS
SSH
Telnet
FTP
SMTP
当然,也包括你自己写TCP程序时自定义的应用层协议.
以上就是今天所有的内容了,内容比较抽象,需要大家慢慢理解,完结散花🌸🌸🌸🌸🌸🌸🌸🌸