【Linux网络】传输层协议TCP(上)
本文详细介绍了TCP协议的核心机制和关键概念。首先,TCP协议段格式包括标准报头和有效载荷,通过四位首部长度进行分离。其次,确认应答机制通过序号和确认序号确保数据传输的可靠性,并支持捎带应答以提高效率。报头字段中的窗口大小用于流量控制,标志位则用于标识报文类型。超时重传机制通过动态调整时间间隔来应对网络环境变化,确保数据可靠传输。连接管理机制通过三次握手和四次挥手建立和断开连接,确保双方通信意愿和
一、TCP协议段格式
TCP全称为“传输控制协议”,人如其名,就是要对数据的传输进行一个详细的控制,TCP的协议段格式如下图:
如何将报头和有效载荷进行分离?根据上图,选项以上的属于20字节的标准报头,而选项是不定长的,通常为0字节,而选项下的数据则是有效载荷。进行分离,就要看标准报头里的四位首部长度,4个bit位,即从0000到1111,也就只能表示[0,15]的数,但约定其基本单位为4字节,也就是说能表示[0,60]字节的tcp报头范围,最小都有20字节,实际上tcp报头字节范围为[20,60],换成基本单位就是[5,15]。
那么为什么只有报头大小,而没有报文大小呢? 因为TCP是面向字节流的,它并不清楚有效载荷(数据)是否是一个完整的,它只需要将报头去掉然后将数据传到接收缓冲区中让用户自己分析即可。这里需要注意的是,这里并不存在数据和下一个TCP报头粘在一起的情况,因为每一个TCP报文(报头+数据)都是维护在一个sk_buff中的,只会存在多个报文的有效载荷的数据粘在一起的情况,这个就是接受缓冲区里的情况,这是用户自己解决的。
二、确认应答机制
什么叫做可靠性?具有应答,才可以保证历史消息的可靠性。而通信中最新的报文永远没有应答,因此最新报文的可靠性是无法保证的。
那么TCP通信的一般过程,就是客户端和服务端有一方发消息,另一方就必须应答,但是不对应答做应答,否则就是无限套娃了。
但是上述过程效率太低了,其实真实的情况下,一方连续发多个报文,然后另一方连续发多个应答,这就是更通用的过程,效率更高一些。那么如果应答相对发送的消息少了一个,又该如何知道是丢失了哪一个报文呢?这就要给消息带上序号了,而应答也要带上确认序号(确认序号=序号+1),这样确认序号之前的所有信息,就能确定全部收到了,而下一次发送就要从确认序号开始加上报文的长度。

而且序号解决了段乱序的问题,即消息按顺序发,但不一定是按顺序接受的问题,这个问题是不可靠的。
所谓的应答其实也是一个报文,不过是一个不携带数据的报头而已。但这样说的话,为什么协议段格式里会有两个序号呢?一个不就能解决问题了吗?
通常情况下像服务端收到请求报文后,是需要返回数据的(如网页等),那么何不直接将确认序号和数据和在一起发送呢?这样就是捎带应答,不仅存在对上个报文的确认序号,还自带该消息的序号。
三、报头字段
1.窗口大小
若接受方的接收缓冲区已经快满了,但发送方仍然在不停发送数据,那么这些新发的数据就会被丢弃,尽管能重发,但这些报文在传输时是浪费了很多资源的,这并不合理,因此需要在报头上加上16位窗口大小来进行流量控制。一台主机接受数据的能力由接受缓冲区剩余空间的大小决定,因此把该数字写到16位窗口大小中,发送方就能对发送速度进行动态调整。
2.标志位

所谓标志位其实本质就是报头中的比特位,那么为什么要有标志位呢?服务器作为接受方,收到的报文一定存在不同的类型,有的是传输数据,有的是申请建立连接等,针对不同的报文,接收方要有不同的做法,因此就需要用标志位来表示报文类型。
建立通信前,要先进行三次握手后才能进行正常通信,前两次握手,不能携带数据,因为三次握手还没有完成,因此这时发送的只有报头,这个时候像上述的窗口大小就已经发送了,因此后续正常通信时第一次是知道窗口大小的。而断开连接则需要四次挥手,因为断开连接需要双方同意,双方都发起一个断开连接并且应答。
SYN:同步标志位,建立连接,握手过程使用的标志位
ACK:表面报文是一个应答报文
FIN:通知对方,本端要关闭了,带FIN的就是结束报文段
PSH:提示接收端立刻从缓冲区把数据读走
RST:对方要求重新建立连接
URG:紧急指针是否有效
在三次握手建立通信这里存在一个问题,那就是最后一条应答是没有应答的,也就是说最后一个ACK服务端有没有收到客户端是不知道的,对于客户端而言只要第三次握手发出就代表通信建立好了,而服务器只有收到第三次握手才认为通信建立好,这就存在着一个时间差,这也就导致了连接建立是否成功认知不一致的问题,如果第三次握手服务端没有收到,那么客户端认为通信建立好了但服务端认为没有建立好的问题,若此时客户端开始发数据,服务端却认为连接都没有建立,此时服务器就会返回RSR来重置连接。当然,若通信的过程中出现任何的问题,也都可以进行重置。
最后再来谈谈URG这个标志位,若URG为0,那么报头中的16位紧急指针无效,为1则代表有效。这个紧急指针有什么用呢?我们知道TCP使用序号保证了其可靠性,也就是说接受缓冲区中的数据是按照序号依次被读取的,但若此时有一个暂停或者取消传输的报文呢?难道要等前面的报文都被读取到了再执行暂停或者取消吗?肯定不行,这就需要传URG来让该报文进行优先处理。这个16位紧急指针其实是指的当前报文的有效载荷中,特定偏移量处有紧急数据,紧急数据只有一个字节,即状态码。
四、超时重传机制
我们先重新理解一下丢包,没有收到应答存在以下两种情况,一种是直接丢数据,一种是丢应答:
那么作为发送方,没有收到ACK意味着什么?这并不意味着丢包,这只能意味着对方可能没有收到数据,但也可能是收到了的,就像上图的第二种情况,因此无法保证可靠性。若在特定的时间间隔收不到应答,就会判定报文丢失,进行重发,所以只有同时满足没收到应答并且超时才意味着丢包了。上图第二种情况下,服务端存在数据重复的问题,但序号的存在能很容易地去重。
那么这个特定的时间间隔是多长呢?由于网络环境是变化的,这个时间也一定是变化的:

五、连接管理机制

图上的SYN_SENT,ESTABUSHED等都是状态,也就是整数(宏值),若有多个客户端向服务端发起连接,那么服务端一定会具有多个连接状态,这就需要被管理,于是就会有记录连接的结构体存在,因此建立连接是有成本的。
上图中connect就是发起三次握手,这由客户端的OS自动完成,如果服务端不进行accept,其实连接仍然能建立,也就是说accept并不参与三次握手,这也就是由服务端的OS自动完成的,accept只是去拿文件描述符而已。
那么为什么要进行三次握手呢?只有三次握手才能验证全双工,也就是验证客户端和服务端都有接收消息和发送消息的能力,看的就是外部条件是否满足。同时也能以最小成本100%确认双方通信意愿。
而断开连接的本质其实就是建立双方断开连接的共识,具体做法是客户端和服务端都需要向对方发送断开连接的请求,就是四次挥手。
六、TIME_WAIT

主动断开连接的一方,要进入一个TIME_WAIT的状态,例如上图中的客户端,理论上客户端从发送最后一个ACK开始它的四次握手就已经结束了,但其并没有立即进入CLOSED状态,而是等待一定的时间后再进入CLOSED状态。
这个一定时间其实是等待两倍的MSL(TCP报文最大生存时间),这样就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失,不要引起下一次通信错误。同时,主动断开连接后,在TIME_WAIT期间不能立马重启该端口,这样也防止了迟到的报文传到该端口的新连接上。
当然可能还有一个原因,在TIME_WAIT期间还能保证最后一个报文可靠到达,假设最后发送的一个ACK丢失了,对方就会再发一个FIN,这时虽然客户端的进程不在了,但TCP连接还在,仍然能够重发一个ACK。如果在这期间没有收到任何消息,就代表对方收到了最后一个ACK。
这种情况下,可以使用序号来判断新连接收到的报文是否是上个连接的产物,同样能够避免问题。
更多推荐


所有评论(0)