TCP协议

6 篇文章 0 订阅

目录

TCP协议特点

TCP报文结构

TCP内部的工作机制

确认应答

超时重传

连接管理

滑动窗口

流量控制

 拥塞控制

延时应答

捎带应答

面向字节流

异常情况


TCP协议也是传输层非常重要的协议,相比于UDP协议复杂不少.

TCP协议特点

有连接,可靠传输,面向字节流,全双工.

其中有连接,面向字节流,全双工这些特点我们能在TCP代码中看出,但是可靠传输这个特点是TCP内部的机制,和编码的关系不大,我们无法感知的那么清楚.

TCP报文结构

 TCP报文 = TCP报头(首部) + TCP载荷

其中和UDP不同的是,TCP的报头长度是可变的(不像UDP一样固定是8个字节).

4位首部长度就描述了TCP报头的长度.可变是因为选项的存在.

选项option是对当前TCP报文的一些属性进行解释说明的.

因此我们可以利用首部长度计算出选项的长度:选项之前的部分长度是一定的(固定是20字节),所以首部长度-20字节得到的就是选项部分的长度.

需要注意的是,这里的首部长度是4位(4 bit),表示0-15,但是这里的单位不是字节,而是4字节.

如果首部长度的值是5,表示整个TCP报头是20字节,相当于没有选项;如果首部长度值是15,表示整个TCP报头是60字节,那么选项部分就是40字节.

保留resevered

现在没有作用,但是以后不一定.(为以后TCP的拓展升级做准备)引入了保留位,TCP的升级操作会降低不少的成本,如果后续TCP引入了一些新的功能,就可以使用这些保留位的字段.此时,对于TCP本来的报头结构影响是比较小的,老的设备即使不升级也更容易兼容.

报头中其他的部分我们需要了解TCP的内部工作机制.


TCP内部的工作机制

TCP是一个复杂的协议,里面有很多机制,我们当前主要讨论TCP提供的10个比较核心的机制.

确认应答

确认应答机制是TCP实现可靠传输的最核心机制!

可靠传输不是说发送方能100%把消息发送给接收方,而是尽力发送,如果是在发送不过去,也要让发送方知道消息没有送达.

A给B发送消息,B在收到之后就会返回一个应答报文(ACK,acknowledge的缩写).此时A收到应答之后,就知道了刚才发送的数据已经顺利到达B了. 

考虑一个更为复杂的情况

网络上可能会存在"后发先至"的情况,在这种场景下,收到消息的顺序是存在变数的.

导致"后发先至"的原因:两个主机之间,路线有多条,数据报1和数据报2走的可能也是不同的路线,数据报1转发路径上的路由器和交换机和数据报2的就不同了,有的转发速率快,有的转发速率慢,那么此时,这两个数据报的到达顺序,就存在变数了,可能先发送的但是是后到的.

主机A连续发送两条消息,消息A和消息B,但由于出现了"后发先至",主机B先收到了消息B后收到了消息A,那么B就会先返回消息B的应答报文后返回消息A的应答报文.此时这里的应答就会发生错乱.

结论:网络中:后发先至"这种现象是客观存在的,无法避免.因此应答报文到达的顺序也是可能发生变动的,此时就需要考虑如何规避这种顺序错乱带来的歧义.

如何解决"后发先至"问题,方法很简单,就是给传输的数据和应答报文都进行编号,就可以了.

当我们引入了序号之后,就不怕顺序错乱了,即使顺序上错乱了,也可以通过序号来区分当前的应答报文是针对哪个数据进行的了.

这就是报头中32位序号和32位确认序号的作用了.

任何一条数据(包括应答报文)都是带有序号的.

确认序号,则是只有应答报文才有.(普通报文中确认序号字段里的值没有意义)

这一条报文是否是应答报文,取决于ACK这个标志位.如果ACK这个标志位为1,表示当前报文是应答报文,如果是0,表示不是应答报文. 

TCP的序号并不是按照"一条两条"这样的方式来编号.

TCP是面向字节流的,TCP的序号也是按照字节来编号的.

假设一条数据报是1000个字节,假设从1开始编号,此时第一个字节序号就是1,第二个字节序号就是2,一直到第1000个字节序号就是1000.但是由于这1000个字节都属于同一个TCP报文,所以TCP报头里只记录当前的第一个字节的序号,此时报头序号里填写的就是1.接下来如果接着发送第二条数据,此时第二个TCP数据报里的第一个字节序号就相当于是1001,如果这条数据报的长度也是1000个字节,那么最后一个字节的序号就是2000,由于这1000个字节也是属于同一个TCP报文,所以这条数据报报头里的序号填写的是1001.填写第一个字节的序号.

可以看出,TCP的字节序号是一次累加的,这个依次累加的过程对于后一条数据来说,起始字节的序号就是上一条数据报最后一个字节的序号在加上1.每个TCP报头里只需要填写头一个字节的序号即可.

值得注意的是,一次TCP连接的序号不一定是从1开始的,双方在建立连接的时候会有一个协商的过程.

此时可以看到,第一条应答报文的报头的确认序号里填写的是1001.

表示的含义:字节序号<1000的数据已经确认收到了;A接下来应该从1001这个序号继续发送数据.

小结:TCP可靠传输.最主要是通过应答机制来保证的,通过应答报文,就可以让发送方清楚的知道数据传输是否成功了.进一步的引入了序号和确认序号,针对多组数据进行详细的区分.


超时重传

我们在上述讨论确认应答的过程中,只是讨论了传输成功的情况,那么如果丢包了呢?

丢包涉及到两种情况:

1.发送的数据丢了

2.返回的ACK丢了

上述两种情况对于发送方来说,看到的结果都是没有收到ACK,区分不了是上述哪种情况,但是对这两种情况会一视同仁,都会认为是丢包了.

发生丢包后,TCP还会尝试挽救一下,因此TCP引入了超时重传机制.在丢包的时候,就要重新新再发一次同样的数据.毕竟丢包还是一个小概率事件,重发数据还是有很大可能传输成功的.

那么TCP是如何判断是丢包了呢?

TCP直接引入了一个时间阈值,发送方在发送数据之后,就会等待ACK,此时就会开始计时.如果在时间阈值之内,没有收到ACK,此时不管是ACK还在传输过程中,还是ACK丢了,都被视为是丢包了.

超时重传:超过一定时间,没有响应,就重新传输数据.

关于这个超时时间到底是多少ms,这没有确切的答案,因为这个时间是可以配置的,并且不同系统上的默认值也可能是存在差别的.

如果是上述原因引起的丢包,返回的ack丢了,那么就会出现这样一种情况:对于主机B来说,1-1000的数据,收到了两次. 

这个事情是细思极恐的,如果这是一个支付请求,就会引起很大的麻烦.

所以TCP对于这种重复数据的传输,是有特殊处理的,去重.

TCP存在一个"接受缓冲区"这样的存储空间(操作系统内核里的一段内存),每个TCP的socket对象,都有一个接受缓冲区.主机B收到主机A的数据,其实是B的网卡读到数据了,然后把这个数据放到了B对应的socket的接受缓冲区中,在缓冲区中,根据数据的序号,TCP很容易识别到当前的接受缓冲区里的这两条数据是否是重复的,如果是重复的,就把后来的这份数据直接丢弃了,保证应用程序read读取到的数据一定是不重复的.TCP使用这个接受缓冲区,也会对收到的数据进行重新排序,使应用程序read到的数据是有序的(和发送顺序一致).

重传的数据是有可能在次丢包的,因此超时重传可能重传 n 次,但是n也不会很大,因为重传多次,还是重传不过去,此时继续重传意义已经不大了.如果连续多次丢包,说明当前网络出现了重大问题.

因此,当重传到一定次数后,就不会在继续重传,会认为网络出现了故障,接下来TCP会尝试重置连接(相当于断开重连一样),如果重置还是失败,就彻底断开连接了.

小结:可靠传输是TCP最核心的部分,TCP的可靠性就是通过确认应答+超时重传来进行体现的.

其中确认应答描述的是传输顺利的情况,超时重传描述的是传输出现问题的情况,这两者相互配合,共同支撑TCP的可靠性!!


连接管理

连接(Connection)

 管理就是描述了连接如何创建和如何断开的.

TCP的建立连接过程(三次握手)和断开连接过程(四次挥手).

建立连接的过程:我们称之为三次握手

 上图描述的客户端和服务器建立连接的过程,在此过程中一共有三次交互,我们把每次通信,都称之为是一次"握手",所以这个过程叫做三次握手.

客户端主动给服务器发起的建立连接的请求,称为"SYN",同步报文段,也是TCP报头里的标志位之一.

客户端是主动的一方,服务器是被动的一方,一定是客户端先向服务器发起"建立连接请求".

客户端服务器这个概念,只是按照主动不主动来进行区分的,同一个程序,在不同的场景下可能作为服务器也可能作为客户端.

客户端先给服务器发送建立连接请求,服务器收到请求后,向客户端发送建立连接请求和ACK,客户端收到后,在向服务器发送ACK.

服务器向客户端发送的syn和ack本来可以分两次发,但是这里合并了,因此封装分用两次的成本一定比封装分用一次的成本要高,所以是合并发送.

三次握手的意义:

1.让通信双方各自建立对对方的"认同".

2.验证通信双方各自的发送能力和接受能力是否是正常的.

3.在握手的过程中,双方来协商一些重要的参数.(TCP通信过程中,有些数据,通信双方是要同步的,此时就需要有这样的交互过程,恰好可以利用三次握手的机会,来完成数据的同步)


 两边的是TCP的状态,tcp也是存在状态的,不同的状态描述了当前tcp在做什么.

建立连接阶段,主要认识两个状态:

1.LISTEN 服务器的状态

表示服务器已经准备就绪,随时可以有客户端来建立连接.

2.ESTABLISHED 服务器和客户端都有

表示连接建立完成,接下来就可以正常通信了.


TCP断开连接的过程,叫做四次挥手

"挥手"和"握手" 一样都是形象的叫法,都是客户端和服务器之间的数据交互.

四次挥手:通信双方各自向对方发起一个断开连接的请求,在各自给对方一个回应.

FIN(断开连接的请求)的发起,不是有内核控制的,而是由应用程序,调用socket的close方法(或者进程退出)才会触发FIN. 

ACK则是由内核控制的,是收到FIN之后,立即返回ACK.

那为什么这里的中间两个过程不能合并呢?

因为服务器在收到客户端的FIN之后,由内核控制的ACK会立即发送给客户端,而服务器发送FIN的时机,是要等到服务器的应用程序执行到对应的close方法,才会触发FIN,这两者之间会有一个时间差,所以不会合并.当然特殊情况下,如果这个时间差很短,也是有可能合并的.

四次挥手中涉及到的两个重要的TCP状态:

1.CLOSE_WAIT

出现在被动发起断开连接的一方.等待关闭(等待调用close 方法关闭socket).

2.TIME_WAIT

出现在主动断开连接的一方.

假设是客户端主动断开连接,当客户端进入TIME_WAIT状态的时候,相当于四次挥手已经挥完了,最后一个ACK已经发出.此时这里的TIME_WAIT状态是要保持当前的TCP状态不要立即就释放.

为什么不要立即释放呢?为什么TIME_WAIT会保留一会连接?

理由就是,最后一个ACK刚刚发出去,还没送到,如果这个ACK丢包了呢?

TIME_WAIT 会等,如果等了一段时间之后,没有收到重传的FIN,此时就会认为,最后一个ACK没有丢包,于是就彻底的释放连接了.

此时,如果恰好最后一个ACK丢了,又恰好重传的FIN也丢包了,此时客户端等待了一段时间,没有收到重传的FIN,就认为连接可以彻底释放了.当然这种情况出现的概率很小.

TIME_WAIT具体等待多长时间,就真正释放呢?

约定一个时间,2MSL.

如果A经历了2MSL,还没有收到重传的FIN,就认为发送的ACK正常到达了.

MSL:指的是互联网上,两个节点之间,数据传输消耗的最大时间. 

 


滑动窗口

可靠性和传输效率之间本身是矛盾的.

在保证可靠性的基础上,来尽可能的提高传输效率.(尽量降低效率的折损)

对于基本的确认应答来说,每次发送一个数据,都要等到ack到了再发送下一个数据.

滑动窗口的本质就是不等待的批量发送一组数据,然后使用一份时间来等待着一组数据的多个ACK.

 把不需要等待,就能发送的数据的最大的量,称为"窗口大小".

上述图里的窗口大小就是4000.

那么滑动怎么理解呢?

当批量发送了窗口大小的数据之后,发送方就要等待ack了,那么什么时候继续发送数据呢?

不是说,等待所有的ack到达,才继续往下发送,而是到一个ack,就继续往下发一条,这样就让我们此处等待的ack始终都是4条了.

窗口大小始终不变,这一过程,形象的称为是滑动.


上述情况下,丢包了怎么办?

1.ack丢了

 此种情况,不需要做任何处理.

关键要点就在于确认序号的含义,表示从该序号往前的所有数据都已经确认到达了.

就算是1-1000的ack丢了,主机A收到2001的ack,此时主机B再索要2001开头的数据,那么主机A已经知道2001之前的数据已经送达了.(1-1000,1001-2000的都已经到达了,2001的ack其实已经涵盖了上一个1001的ack的信息)

2.数据丢了

上图可以看到 ,1001-2000的数据丢包了,接下里2001-3000的数据到达主机B之后,B给A返回的确认序号仍然是1001(此时和你发来的这个数据序号是什么,关系不大)

意思就是在索要1001开头的数据.

接下里的几个数据,B返回的确认序号都是1001,此时A就能感知到,虽然A发送了不少数据,但是B还是在索要1001,说明1001 B没有收到,就需要对1001进行重传了.

进行重传之后,B收到了,就会检查2001-3000,3001-4000,4001-5000,5001-6000,6001-7000的数据是否收到了,如果都收到了,返回的ack的确认序号就是7001了.


上述丢包重传的方式,叫做"快速重传"(重传操作只重传了丢失的数据),这个可以视为超时重传机制在滑动窗口下的变形.

如果当前传输数据密集,按照滑动窗口来传输数据,此时按照快速重传来处理丢包.

如果当前传输数据稀疏,不在按照滑动窗口来传输数据,此时就还是按照之前的超时重传处理丢包.


流量控制

这是一种干预发送窗口大小的机制.

滑动窗口,窗口越大,传输效率就越高,但是窗口也不能无限大.

1.完全不等ack,可靠性不能保障.

2.窗口太大,也会消耗大量的系统资源

3.发送速度太快,接收方处理不过来,发了也是白发.

所以,接受方的处理能力,就是一个很重要的约束数据,发送方的发送速度,不能超出接受方的处理能力.

流量控制要做的工作就是根据接收方的处理能力,来协调发送方的发送速率.

如何衡量接收方的处理能力?

一个量化的方法:计算接收方一秒能处理多少个字节,但是这种方式实际操作起来,比较麻烦.

更简单的方法就是:直接看接受方接受缓冲区的剩余大小.

每次A给B发送一个数据,B就要计算一下接受缓冲区里剩余空间的大小,然后把这个值,通过ack来返回给A,A就会根据这个值来决定接下里发送的速率是多少(窗口大小是多少).

通过tcp报头里的16位窗口大小来描述这个值,报文是ack的时候,16位窗口大小才有效.

发送方就会根据这个数字来确定下一轮发送的窗口大小.(实际窗口大小不一定就是这个值)

16位是否就是意味着窗口大小最大是64kb呢?

不是的,TCP为了能让窗口更大,在报头里的选项部分引入了"窗口拓展因子".

比如现在的窗口大小已经是64kb,拓展因子里写了个2,意思就是让64kb<<2,左移两位=>256kb.

所以发送方窗口大小不是固定值,也不是配置的,而是随着传输过程的进行,动态调整的.


 拥塞控制

流量控制和拥塞控制共同决定了发送方的窗口大小

流量控制考虑的是接收方的处理能力.

拥塞控制描述的是传输过程中中间节点的处理能力.

前面考虑A的发送速率,只考虑了B的处理能力,而没考虑中间节点.

这其实是一个木桶效应.

接收方的处理能力,好量化衡量.但是中间节点,不好衡量.

既然不好直接量化,就通过实验的方式,来测试出一个合适的值.

拥塞控制,本质上就是通过这样实验的方式,来逐渐找到一个合适的窗口大小(合适的发送速率).

 纵轴就是尝试以多大的窗口进行发送.

横轴是发送轮次.

0轮,窗口大小是1,以非常慢的速率发送数据(此处1不是1字节,而是1单位,一个单位代表多少个字节,我们不研究).

发现传输顺利,没有丢包,就扩大窗口.

1轮,窗口大小就是2 扩大一倍....

初始阶段,由于初始的窗口比较小,每一轮不丢包都会使窗口大小扩大一倍(指数增长).

当增长速率达到阈值后,此时指数增长,就变成了线性增长.

增长的前提就是不丢包.

在接下来,传输过程一旦丢包了,说明此时发送速率已经接近网络极限,此时就会把窗口大小一下缩成很小的值(重复刚才指数增长和线性增长的过程).

拥塞窗口不是固定的值,而是一直动态变化的,随着时间的推移,逐渐达到一个动态平衡的过程.

拥塞窗口和流量控制的窗口,共同决定了发送方实际的发送窗口.(拥塞窗口和流量控制的窗口的较小值).


延时应答

也是一个提升一个效率的机制.

是在滑动窗口的基础之上,在做一些处理,在接收方能处理得了的条件下,尽可能把窗口大小放大一点.

延时,收到数据之后,不是立即返回ack了,而是稍等一会在返回,那么在等待的时间里,接收方的应用程序就能够把接受缓冲区里的数据在消费一波,此时缓冲区的剩余空间就会更大了.

 实际上,延时应答采取的方式,就是在滑动窗口下,ack不在每一条都返回了,就像上图一样,此处是隔一条返回一个ack.


捎带应答

也是提高效率的机制.

在延时应答的基础上,引入的捎带应答.

服务器客户端程序,最典型的模型,就是"一问一答",业务上的请求和响应.

 本来1,2是不同的时机发送的,但是由于TCP存在延时应答,由于延时应答机制,就导致等待ACK的过程中,B就要给A发送业务上的数据了,就可以让业务数据捎上这个ACK一起发送过去.

捎带应答本身就是在说"能合并"这个事情,延时应答是提高了合并的概率.


面向字节流

由于TCP的面向字节流特点,引入了一个麻烦事,粘包问题.

接受缓冲区,其实就是把刚才这里的多个数据都放到一起了,应用程序read读取的时候,读到哪里才算是一个完整的应用层数据报呢?

由于TCP是面向字节流的,一次读1个字节,读N个字节都是可以的.

这就导致一次读到的数据,可能是半个应用层数据报,可能是一个应用层数据报,也可能是多个应用层数据报.这个现象就叫做粘包问题.

在TCP层次,没有在socket api中告诉我们应该读几个字节,具体怎么读,完全是由程序员自己负责的.

我们肯定是希望每次读到的是整个应用层数据报,后续才好处理.

解决方案:应用层约定好应用层协议即可,明确应用层数据报和应用层数据报之间的边界就可以了.

1.约定好分隔符.

2.约定每个包的长度.


异常情况

传输过程中,出现了不可抗力.

1.进程崩溃

2.主机关机(按照正常流程关机)

上述两种情况,进程没了,对应的pcb就没了,对应的文件描述符表就释放了,相当于socket.close(),此时内核会继续完成四次挥手,此时仍然是一个正常断开的过程.

3.主机掉电

4.网线断开

上述两种情况是来不及挥手的.

假设是接收方掉电了,发送方仍然在继续发送数据,发送完数据就要继续等待ack,ack等不到,超时重传,重传几次,还是没有应答,此时在尝试重置TCP连接,重置失败,彻底断开连接.

发送方掉电,接收方发现,没有数据发送过来了,没数据可能是发送方挂了,可能是发送方在组织数据,接收方不知道是哪种情况,先等待,同时接受方需要周期性的给发送方发送一个信息,确认下对方是否还是工作正常.

周期性给对方发送信息,我们称为是心跳包,来确认通信双方是否在正常的工作状态,也是一种保活机制.


上述是TCP比较核心的十大机制,但是TCP是一个复杂的协议,不仅仅这十个机制.

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值