TCP协议

TCP协议通过确认应答和超时重传来确保数据的可靠传输,包括序列号和确认序号防止应答错乱。建立连接采用三次握手,确保双方的发送和接收能力正常。断开连接则通过四次挥手,保证连接的完全关闭。滑动窗口机制提高了传输效率,流量控制和拥塞控制则动态调整窗口大小,适应网络状况,确保稳定性和效率。
摘要由CSDN通过智能技术生成

1.TCP协议的可靠传输

我们知道TCP协议的特点有:有连接,可靠传输,面向字节流,全双工

其中连接,面向字节流和全双工在《网络编程》该文章中详细说明了,而要深入了解TCP协议,了解可靠传输也是重中之重(可靠传输也是TCP协议的一大特点)

那么TCP协议是如何做到可靠传输的呢?

1.1确认应答

可靠传输不是说100%的传输率就是可靠传输,因为即使协议做的再好,也挡不住别拔网线,TCP协议的可靠传输是指可以让发送方清楚该条数据是否真的送达了.

那么如果要确认是否送达,那么就可以让接收方回复一下就好了,这也是TCP中确认应答机制的原理.

举个例子:

如上这张图,A给B发"可以请你吃饭吗?" B回应"好啊好啊",此处B的回应就称之为应答报文,也叫做ack, 此时A收到了B的回应,A就知道这条消息发送成功了,如果没收到回应,就说明丢包了,没发送过去.

但是网络上可能会出现"后发先至"的情况.这种情况下,收到消息的顺序可能会错乱.

举个例子:

在正常情况下:

如果发生了"后发先至" :

原本B的期望回复顺序是:先回复"吃饭"的"好啊好啊",然后回复"处对象"的"gun",但是由于后发先至,导致先回复"吃饭"的是"gun",后回复"处对象"的是"好啊好啊".

此时就发生了应答错乱的现象,而这种情况又是客观存在,无法避免的,所以TCP中对发送的数据和应答都加上了序号,来确定应答的数据对应的是哪一条数据.

如下图(描述了TCP报文的结构),其中被红圈圈住的序号和确认序号就是TCP中确认应答的机制所在.

TCP的序号方式也很特别,它是按照字节来进行编号的,它给每一个字节都"加"上了序号,但是报文中只记录第一个字节的序号,而应答报文中则要传输的序号是 传输数据的最后一个字节的序号+1

举个例子:当主机A给主机B发送了一条1000字节的数据,那么此时该数据的序号就是1(假设从1开始),而应答就是1001,当下一次进行传输的时候就会从1001开始序号.

如下图:

小结:TCP的可靠传输能力,最主要是通过确认应答机制来保证的,通过应答报文,可以让发送方清楚的知道传输是否成功,又通过序号的方式,将多组数据进行区分.

1.2 超时重传

上面的确认应答中描述的是没有丢包的情况,那么如果在传输过程中丢包了又改如何处理呢?

此处,丢包大致可以分为两种情况:

  1. 发送过去的数据丢了

  1. 返回的ack丢了(但实际上传输的数据没有丢)

但是无论以上那种情况,在发送方看来就是没有收到ack,无法区分是那种情况,此时TCP就一视同仁的认为:就是丢包了

因此TCP又引入了重传机制,就是把丢包的数据在发送一份.

也就是超时重传:当接收方等待了一段时间没有收到响应,就会进行重传(发送方在发送了一个数据之后,就会等待ack,此时开始计时)

但是超时重传也有一个问题,因为第二种丢包的情况,数据以及发送过去了,此时重传就会造成同一份数据发送了两遍.

如下:

我们想象一下,如果一个支付数据被发送了两遍,那么此时我付了两份钱,但是只得到一份收益,这显然不是我们想要的,TCP肯定不会让这种情况发生.

所以,TCP对于这种重复的数据就有处理办法的,也就是去重.

TCP中存在一个"接收缓冲区"(接收方系统内核里面的一段内存),每个TCP的socket对象都会有一个"接收缓存区"

当主机B收到主机A的一条数据,会先将它放到接收缓存区中,然后,后序应用在进行getInputStream等操作,在接收缓存区中读取数据.

这个接收缓存区我们可以把它理解为一个 优先级阻塞队列 ,根据数据就序号,可以很容易的找出重复的数据,如果有重复的数据,那么新来的这份数据就会被直接丢弃,也就保证了,接收方读到的数据都是不重复的.(至于为什么要使用优先级而不用普通的阻塞队列,是因为在传输中可能会出现"后发先至"的情况,所以要根据序号的大小来排序)

当然重传的数据也有可能会再次丢包,此时TCP会进行N次传输,每次重传的时间间隔都不同,会越来越大,当丢的次数比较多的时候就不会在重传了.

小结:可靠传输是TCP中最核心的部分,TCP的可靠传输就是通过 确认应答 和 超时重传 来进行实现的.其中,确认应答描述的是传输顺利的情况;超时重传描述的是传输出现问题的情况.

2.连接管理

2.1 建立连接(TCP三次握手)

TCP中的连接,不是我们真正意义上的连接,而是通讯双方各自有一块空间存储对方的ip和端口.

如下图:

当A和B的这块空间被维护好了,此时A和B就建立了连接,同时存储这些信息空间的数据结构也被成为连接

那么这个连接是如何建立的呢?

此时就需要A和B之间进行信息的交互了.

举个例子:

假如有一天我向女神进行表白,此时我们的表白流程是这样的,如下图:

首先我向女神进行表白,女神同意, 又向我进行了表白,我也同意了.此时我和女神之间的连接就建立完成了.

而其中的每一次交互就相当于一次"握手",其中一共进行了4次交互,理论上应该要进行4次握手.

但是实际上,TCP建立连接虽然需要进行4次交互,但是实际上只需要进行三次握手,是为什么呢?

因为其中女神给我的两句话完全可以合并成一句话,如下图:

此时交互次数就由4次变成了3次了.

而TCP需要进行4次交互,但是只进行3次握手的原因也很简单.

因为每一次交互都是发送应该数据报,每一个数据报都需要进行各各层级的 封装和分层 ,这其中也是有不小的开销的,所以TCP之间建立连接只进行3次握手.

小结:TCP的3次握手,本质上是4次交互,通信各方要各自想对方发起一个"建立连接"的请求,同时也需要回复对方一个ack.

那么此时问题来了,既然减少握手有消耗,那如果只进行2次握手是否可行呢?

如下:

我们发现上面的对话很怪,但是也可以说得通.

我发出了"做我女朋友"的请求,同时还告诉她"如果她同意,那我也做他的男朋友"

在TCP中这样的交互是可行的,但是这样的前提是:3次握手除了各自保存对方的信息之外,没有其他的用途

这个前提显然是不存在的,因为TCP的3次握手还有其他功能:确认交互双方的发送能力和接收能力是否正常.

举个例子:

在现实生活中,我们和别人打电话时,总是要确认对方的设备情况是否正常.

此时A给B打电话:

第一次对话:

A先给B说了一句"能听到吗?",假如此时B听到了

此时B知道:A的麦克风正常,B的听筒正常

此时A知道:(空)

第二次对话:

此时B给A回复:"能听到,你呢?"

此时B知道:A的麦克风正常,B的听筒正常

此时A知道:A的听筒正常,A的麦克风正常,B的麦克风正常,B的听筒正常.

第三次对话:

此时A给B回复:"我也能听到"

此时A和B都知道:彼此双方的听筒和麦克风都是正常的.

那么如果是只有两次握手,不管里面的内容那么丰富,B始终都不知道A的听筒和B的麦克风是否正常,也就无法保证通信双方的发送能力和接收能力是正常的.

小结:3次握手在一定程度上保证了TCP传输的可靠性.

除此之外,TCP3次握手还有一个作用:在握手的过程中,双方来协商一些重要参数.

如下图:

其中syn代表的意思是同步报文段,ack代表的意思是回应报文

在TCP通信过程中,有些数据需要通信双方进行同步,此时恰好可以通过3次握手来完成数据的同步,而不需要额外的交互过程.

总结TCP三次握手的意义:

  1. 让通信双方各自保存好对方的信息.

  1. 验证通信双方的发送能力和接收能力是否正常.

  1. 在握手的过程中,双方协商一些重要的参数.

2.2 断开连接(TCP四次挥手)

四次挥手和三次握手的本事十分相似,都是通信双方发出请求,然后对方进行回应,而挥手代表的加锁断开连接的请求,如下图:

注意:断开连接既可以是客户端想服务器发起,也可以是服务器想客户端发起,此处使用客户端发起为例.

首先客户端先服务器发起FIN,也就是断开连接的请求

然后服务器给出了客户端FIN的回应

在然后,服务器向客户端发起FIN

最后,客户端想服务器给出回应

到此连接断开.

那么此处服务器的ACK和FIN是否可以打包到一起变成三次挥手呢?

答案是不可以!

在三次握手中,之所以可以将ACK和SYN打包到一起发送,是因为ACK和SYN的发送是处于同一时期的.会处于同一时期的原因是,TCP建立连接的过程是完全由操作系统内核来操控的,应用程序无法感知和操控,当服务器的内核收到SYN之后,就会立即发送ACK,也会发送SYN.

但是断开连接不一样,FIN的发起是由应用程序来进行控制的,当应用程序调用socket的close方法或者程序退出,就会触发FIN.

而ACK是由系统内核控制的,当服务器收到FIN时,服务器主机的系统内核就会立即发起ACK,而服务器的FIN什么时候发送是由服务器的程序来决定的(close或退出),如果ACK和FIN之间的发送间隔比较短,那么ACK和FIN是可以合并到一起发送过去的,但是如果FIN的发起间隔比较久,那么就无法合并成一个进行发送了,所以无法将四次挥手全部看成三次.

在四次挥手中,当接收方收到FIN的请求后会进入CLOSE_WAIT状态,此时接收方会等待应用程序执行close或者退出程序.

当发送方收到接收方发来的FIN数据报时,会发送ACK并进入TIME_WAIT状态,此时可以认为四次挥手已经完成,但是TIME_WAIT状态还要保持TCP连接不要释放一段时间.

等待的原因是:传过去的ACK数据报可能会丢包,此时接收方要重新传过去一份FIN,如果此时连接已经断开了,那么接收方就无法收到ACK,TIME_WAIT保持一段时间的原因就是为了能处理最后一个ACK出现丢包的情况,可以在收到重传的FIN之后发送ACK.

TIME_WAIT的等待时间为2MSL(通常情况下MSL为60s),当等待时间达到这个时间还没有收到重传的FIN,就会认为ACK已经被送达,连接就会真正被释放,此时即使ACK还是没有发送过去也没办法了.

3.滑动窗口

TCP的滑动窗口机制,本质上是为了让TCP的效率尽可能快一点,因为引入了可靠性,导致TCP在传输效率上面相比于UDP要慢了很多,而传输效率的降低主要体现在等待ack上面.

滑动窗口机制就是在一定程度上缩短了等待ack的时间.

对于确认应答机制来说,每发送一条数据,都要等待对方的ack,然后在进行下一条的传输.

如下图:

滑动窗口机制,就是让一组数据不等待的批量发送,然后使用一份时间等待着一组数据的ack,其中,将不需要等待就可以发送的数据最大的量,成为窗口大小.

如下图:

上图中的窗口大小就为4000.

当批量发送了一次数据之后,并不会等待这一批数据的ack全部到达之后在发送数据,而是收到1条ack之后,就会接着发送下一条数据.

那么在滑动窗口这样的机制下,如果发生丢包该怎么办呢?

此处可以将丢包分为2种情况:

  1. ack丢了

  1. 数据丢了

第一种:ack丢了

此时其实不用进行过多的处理,虽然ack没有接收到,但是数据是确实的发送过去了,那么当下一个ack传过来的时候就可以将丢失的ack信息给补全.

如下图:

虽然ack1001和ack2001丢包了,但是ack3001传输成功,这个3001可以包含前面的1001和2001(因为确认序号的含义就是告诉发送方:3001之前的数据已经接收到了),此时主机A就清楚1~1000和1001~2000以及2001~3000的数据已经传输过去了,不用等ack了,

第二种:数据丢了

如下图:

此时1001~2000的这条数据丢了,主机B没有收到,那么接下来主机B再收到其他(不是1001~2000)的数据时,它仍然会发送1001的ack,此时主机A发现,主机B多次发送1001的ack,就意识到1001~2000的这条数据可能丢包了,会再次进行发送,当主机B接收到了1001~2000这条数据,此时主机B已经接收到了1~4000这些数据,那么此时会发送4001的ack,当A收到了4001之后,就会发送4001~5000这条数据了,以此类推....

4.流量控制

流量控制是一种干预窗口大小的机制.

在滑动窗口机制下,理论上,窗口越大,一份时间里等待的ack就越多,传输效率越高.

但是如果窗口变得无限大,不等ack,此时可靠性就无法得到保障.

同时,窗口太大也会消耗大量的系统资源,因为同一时间的等待数据过多需要的缓冲区空间就越大.

如果一次发送的量太多,太快,接收方处理不过来,丢包了,效率就更慢了.

所以,我们需要对窗口的大小进行一个约束,此时就引入了流量控制的机制.

流量控制机制,会根据接收方的接收能力来判断当前的窗口大小为多少.

接收方的接收能力的判断,是根据接收方当前缓冲区的剩余空间来决定的

假设主机A是发送方,主机B是接收方.

每当A给B发送了一条数据,B就需要算一下当前缓冲区中的剩余空间,然后将这个值通过ack反馈给A,A再根据这个值来决定接下来的窗口大小时多少.

所以,窗口的大小也不是一成不变的,而是随着接收方的缓冲区剩余空间大小,一直进行动态变化.

在ack数据报中,有一个模块表示了ack返回的窗口大小数值.

如下图:

这个16位窗口大小,并不代表窗口大小最大只能是64KB,在选项中有一个窗口扩展因子.

假如窗口大小里面已经是64KB,扩展因子里面写了一个2,此时就可以表示64<<2,也就是256KB.

而当窗口大小为0时,发送方就要暂停发送,在暂停发送的过程中,发送方会给接收方定期发送窗口探测报文,这个报文不携带具体的业务逻辑,只是为了触发ack查询窗口大小,当窗口大小不为0了,就会继续发送数据.

5.拥塞控制

上面说到的流量控制是针对接收方的处理能力来判断当前窗口大小.

但是,接收方有能力接收这个频率的数据,不代表其中间节点的设备(交换机、路由器等)可以承受得住.

但是,这些设备接收方是无法感知的,它无法感知中间究竟有多少个中间节点,而且每次数据的传输路径可能都不相同,因此,中间节点的阈值是无法通过信息的传递来获取的.

所以,设计TCP的大佬们选择使用"实验"的方式来测试出一个合适的值.

如下图:

这张图的纵轴(拥塞窗口)代表当前尝试以多大的窗口大小进行发送;

横轴(传输轮次)代表是第几次传输.

首先,第0轮传输是以1个窗口开始的,当传输顺利,没有丢包时,就会进行扩大窗口的操作.

前几次会不断以指数增长的状态快速增长,当增长到阈值之后,会进行转变成线性增长.

而在其中的传输过程一旦发生丢包的情况,说明此时的发送速率已经接近当前网络的极限了.此时会将窗口大小一下缩短成一个很小的值,同时,指数增长的阈值也会随之下降,下降的大小为:发生丢包时的大小除以2,然后再重复刚才的操作.

所以,拥塞窗口也不是一个固定数值,而是一直动态变化的,在这个变化的过程中,拥塞窗口的大小会逐渐趋于动态平衡.

这样做,既可以将问题解决,又可以随着网络的动态变化而变化.

而实际的窗口大小,是取 拥塞窗口和流量控制窗口中的较小值.

6.延时应答

延时应答机制是基于滑动窗口的基础上,尽可能的增大窗口的大小(在允许的范围内)

其原理也很简单,就是接收方在返回ack的时候,稍微等待一会,然后在返回ack.

这样做的好处就是:接收方晚发送了一会,在这段时间里面,接收方就可以多处理一些数据,此时接收方的缓存区的剩余空间就更大了,就可能让下一次的窗口大小增加.

如下图:

比如在这张图里面的策略是,每隔一条数据,返回一次ack.

7.捎带应答

捎带应答是建立在延时应答的基础上的机制,也是提高TCP的效率的机制.

假设现在有一个场景,如下图:

因为ack是由系统内核进行发送处理的,所以当收到A发来的请求时,B就会立即发送一个ack过去,业务响应随后到达.

但是如果在上面的场景中出现了延时应答,那么此时B就不会发送ack,但是紧接着B又给A发送了业务上的响应,此时ack就可以随着这个响应一起被发送过去.

如下图:

和四次挥手比较相似,都是有一定的几率会合并,将本来不同时机的数据,在延时应答的影响下,成为了同一时机的数据,进行了合并的操作,而延时应答就是提高了这样的概率.

8.面向字节流--粘包问题

TCP是面向字节流的,但是面向字节流,会导致一个问题--粘包问题

什么是粘包问题呢?

我们知道,服务器收到的数据会存放到缓存区里,当服务器进行read的时候,就会从缓存区读取数据,但是,读取的单位是字节,我们不清楚每一个请求里面有多少字节,那么就可能会出现一次读半个数据报、一次读一个半数据报.....这样的情况.

如下图:

假设一次读4个字节,那么第一次读的就是aaaa,第二次是abbb.... 这就属于是一次读半个数据报

假设一次读6个字节,那么第一次读的就是aaaaab,第二次是bbbbcc... 这就属于是一次读一个半数据报

但是我们要处理请求,是需要一次读一个数据报,无论是多了还是少了都无法正确的传达用户的请求,这样的问题成为粘包问题.

解决方法也很简单:约定好应用层协议就好了.

也就是在应用层协议中规定好 应用层数据报 和 应用层数据报 之间的边界.

比如:约定好分割符.(比如:以"\n"作为分隔符......)

再比如:约定好每个包的长度.(比如:每个包都是5个字节......)

等等...

这样就可以很好的解决粘包问题了.

9.异常情况

在TCP传输过程中可能会出现一些不可抗力的因素导致TCP无法整除正常连接

大致有以下四种情况:

  1. 进程崩溃了

  1. 电脑关机了(正常进行手动关机)

  1. 电脑掉电了(死机、拔电源等不正常关机)

  1. 网络断开了

进程崩溃和电脑关机

进程崩溃其实就是进程关闭了,进程关闭就会进行资源的关闭,也就会执行socket的close,此时会进行四次挥手,此时属于是一个正常的断开.

电脑关机和进程崩溃类似,因为电脑要关机需要先关闭所有的进程,在关闭进程的时候也会进行四次挥手(虽然可能没有挥完就关机了,但是无大碍),也属于是一个正常的断开.

电脑掉电和网络断开

电脑掉电需要分析两种情况

第一种:服务器掉电了

服务器掉电了,就意味着客户端的请求无法得到ack回应,此时就会进行超时重传,但是也不会得到回应,接下来会尝试进行TCP重连,同样也会失败,此时客户端就会单方面放弃连接.

第二种:客户端掉电了

客户端掉电后,服务器长时间没有收到请求,此时服务器会给客户端周期性的发送一个消息(心跳包)却对方工作是否正常,如果没有回应,就会放弃连接.

(心跳包属于保活机制,会周期性的发送消息,如果没有回应认为对方挂了)

网络断开就相当于客户端和服务器各自执行上面的操作.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

追梦不止~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值