TCP 协议的相关(部分)特性

TCP的报文结构

TCP 协议的相关(部分)特性_第1张图片

首部长度:

        与UDP不同,TCP没有有描述数据的长度的变量(UDP的是UDP长度),但有描述TCP的报头的长度,为什么要和UDP不同呢?其实是UDP的报头长度是固定的,而TCP的报头是“变长”的。其首部长度就是描述报头的长度,首部长度所占4个比特,但这并不是说用4个比特位(范围为0 -> 15)来描述报头长度,这里的单位为4个字节,也就是把这4个比特位所构成的数值再乘以4个字节,才是真正的报头长度,因此TCP的报头,最大长度为60字节,TCP报头的前20个字节是固定,因此TCP报头的最短长度为20字节。为什么说报头是变长的呢?关键在于这个可变选项,选项可以有多个,也可以没有选项,这就使得报头的长度是变化的,也就影响着报头的长度。所以要使用首部长度来确认报头从哪里结束,数据从哪里开始。

保留位

        现在暂时没用,先占个位置,后面有需要时再用(倘若后面首部长度太小,就可以使用保留位)。因此保留位是给未来升级拓展的空间。

校验和

         与UDP的校验和一样。

16 位紧急指针
        标识哪部分数据是紧急数据;
URG
        紧急指针是否有效
PSH
        提示接收端应用程序立刻从TCP 缓冲区把数据读走
        

32位序列号和32位确认序列号:

        TCP可靠传输的最核心的实现机制为确认应答即当接收方收到数据后,就返回一个确认应答,来确认收到数据,这就保证了可靠传输。但是即使如此,网络传输中也有可能出现后发先至的问题,这就可能导致得到的语意不同,也就导致结果不同,那么应该如何解决呢?TCP的解决方法为给每个字节的数据进行了编号,即位序列号,只要前面的序列号没有到来,即使后面的序列号都堆满了它也不发送,当前面的序列号到了后,就将数据发出去,接收方收到后,就返回一个确认应答,应答中就包含了下一个数据的序列号从什么时候开始(如收到的数据的序列号最大为1000,即下次的数据序列号就从1001开始),这个序列号就为32位确认序列号(为收到的最后一个序列号加一),因此只要知道这一串字节的开始编号和数据的长度,那么每个字节的编号自然也就知道了,所以只需要在TCP报头中,把这串字节第一个字节的编号表示出来,再结合报文长度,此时每个字节的编号就确定了,而这个编号就保存在报头中的32位序列号中,同样的,确认报文也有序列号,这就解决了数据传输过程的后发先至问题。

ACK

        既然有确认序列号和序列号,那么就得有办法区分出来这个报文为普通报文还是应答报文。

而这就得ACK来解决,当ACK为0时,则表示这是一个普通报文,此时只有32位序列号是有效的,当ACK为1时,则表示这是一个确认报文,此时32位序列号和确认序列号都是有效的。

RST

        这些都建立在数据传输到对方,但是如果发出数据后,接收方并没有收到数据,也就是丢包,那么应该怎么办呢?那么就得重传,当传输方等待一定时间后,没有收到确认应答,那么就要进行重传,也称为超时重传超时重传是对确认应答的重要补充,也是保证可靠性的重要机制。但是丢包问题有两种情况,一种是传的数据丢了,一种是应答报文丢了。这两种情况都将导致收不到确认应答,而传输方无法分辨是那种情况造成的,就都进行了超时重传。如果是数据丢了,在重传一份就好,如果是应答报文丢了,那么接收方将收到两个一模一样的数据,这就又出现问题,因此接收方在收到数据后,会对接收到的数据进行去重,保证应用程序读到的数据不是重复的。那么如何高效的判定当前收到的数据是否是重复的呢?其实是使用TCP的序列号来进行判定的依据,当出现两个相同的序列号,那么就可以确定是相同的,TCP会在内核中,会给每个Socket对象都安排一个内存空间,相当于队列,也称为接收缓冲区,收到的数据都会放到接收缓冲区里,并且按照序列号来排序,此时就能很容易的判定当前收到的数据是否是重复数据,当读完数据后,数据就可以从队列中删除掉了。若是该数据删除后,又来一个重复的数据,这样接收缓存区里就没有该数据,那么是否会进入呢?其实不会,因为该数据被删除后,那么就说明当前最小的序列号都比删除的序列号大,当又进来一个比它小的序列号,那么就可以判定为重复数据。倘若重传后,传输方还是收不到确认应答,那么又该如何?将继续重传,不过等待超时时间将随着重传次数会越来越长,当重传次数到达一定次数后,将不再重传,此时就会尝试重置TCP连接(RST为1时,表示该报文为复位报文),如果复位操作重置操作也无法成功,最终就只能放连接了。RST就是要求重新建立连接

SYN

        上述传输的前提是,传输方与接收方建立了连接,因此传输时得先建立连接,那么应该如何建立连接呢?这就是著名的三次握手,传输方先向接收方发送一个打招呼的数据(使用打招呼来触发特定场景),这个数据不会携带业务信息,只是告诉我要与你建立连接,它们建立连接的过程,就需要三次这样的打招呼的数据交互,这个打招呼就是握手,三次打招呼也就是三次握手。如A和B说,我想与你建立连接,B对A说好啊,B对A说,我想和你建立连接,A对B说好啊,这样看其实是四次,不过中间两次可以合并,B对A说好啊,我也想和你建立连接,这样就是三次了。合并之后,减少了封装和分用的过程,降低了成本,提升了效率。SYN就是请求建立连接的请求,也就是握手,我们把携带SYN标识的称为同步报文段。因此三次握手就是客户端向服务器发出SYN,服务器向客户端发出SYN和ACK,客户端在向服务器发出SYN。三次握手也是一种保证可靠性的机制,TCP要想保证可靠传输,前提是网络路径得畅通,TCP的三次握手,就是要验证网络通信是否通畅,以及验证每个主机发送能力和接收能力是否正常,恰好三次就可以验证完成。三次握手还起到“消息协商”这样的效果,比如双方的序列号从哪开始,当一个数据很久才到,但此时已经断开联系了,因此就可以判定这个数据为之前连接的,就可以丢弃了。

FIN

        既然有连接,那么就得有断开连接,断开连接被称为四次挥手,四次挥手与三次握手非常相似,不过三次握手中的第一次握手必须由客户端发起,而四次挥手则两者皆可。FIN则是通知对方,本端要关闭了,我们称携带FIN标识的为结束报文段。它的过程为客户端向服务器发起FIN,服务器收到后返回ACK,在返回一个FIN(并没有合并),客户端收到FIN后,在返回一个ACK。那么为什么中间的两次不能向三次握手一样合并呢?其实是FIN的触发由应用程序代码来控制,当调用了socket.close或者进程结束就会触发FIN,而ACK由系统内核控制的,只要收到FIN就返回ACK,因为close的执行时间不确定,所以就不能和ACK合并。如果服务器始终不进行close,客户端的连接就始终不断开吗?当服务器收到FIN后,此时服务器的TCP状态,就处于CLOSE_WAIT状态,此时虽然这里的连接没有关闭,但是这个连接其实已经不能用了,当前Socket进行读操作,如果数据还没读完(缓冲区还有数据),能正常读到的,如果数据已经读完,此时就会读到EOF。如果进行写操作后,则会抛出异常。因此只能进行close,如果服务器迟迟不进行close,那么客户端就会进行单方面的断开连接(客户端把自己保存的对端的信息就删除了)。如果通信过程中,出现丢包,又咋处理?当最后一个ACK丢了后,此时发出者就视为四次挥手已经结束了,那么它能立刻进行断开连接吗?不能,因为它可能出现丢包问题,因此发出者在发出ACK后会等待一段时间,看看是否有重传来的FIN,有的话在重传一次ACK,没得话就认为没有丢包,就可以断开连接,释放对端信息。

滑动窗口

        由于TCP保证可靠传输的前提下,效率较为低下,为了提升它的传输效率,滑动窗口出现了,使用滑动窗口并不能使TCP变得比UDP快,但可以缩小差距。前面的传输虽然可以保证可靠性,但是要花费大量的时间花费在等待ACK上,使用滑动窗口就是为了减少等待时间。为了减少等待时间,可以一次性发出一组数据,发这一组数据的过程中,不需要等待ACK,就直接往前发,此时,就相当于使用“一份等待时间”等待多个ACK。而这个把一次发多个数据,不用等待ACK这样的大小,称为窗口。窗口越大,此时批量发送的数据越多,效率也就越高。但是窗口并不能无限大,如果是无限大,那岂不是不需要等待ACK了,那就是不可靠传输了,并且如果无限大的话,中间的设备是否能处理这么大的数据量也是一个问题。那么什么是滑动呢?其实是当批量发送数据后,当收到一个ACK后就立即在发送一个数据,不用等待所有的ACK到达后在发送下一个数据。这样就能保证每次要等待的ACK的次数都是固定的,直观上来看,就像窗口滑动了一个格子。这就是滑动窗口。但是如果按照这种方式传输后,丢包怎么办?这种情况有两种,一种是ACK丢了,一种是数据丢了。首先是第一种情况,如果ACK丢了,不需要进行任何处理,因为当收到后面的序列号的ACK后,就说明前面的数据已经接收到了,因此并不需要进行处理。除非是所有的ACK全丢了。其次是第二种情况,当传输的数据丢失后,那么就必须进行重传,那么什么时候进行重传呢?当一个数据丢失后,后面的数据到达后,接收方就将返回一个ACK,里面的确认序号则会包含缺失数据的序列号,因为确认序号就是下一个要获得的数据编号,当多个ACK报文的确认序号都返回同一个序号,那么就可以判定该序号的数据丢失了,需要重传,当重传的数据到达后,返回ACK的确认序号则为后面要传的数据序号,而不是从这个序号开始重新传输。(接收方有个缓冲区在接收数据,将根据序号排序这些数据,如果发现缺失,那么就会索要这个数据,当得到这个数据,如果还有缺失,则会继续索要,如果没有,则直接索要最后一个数据的下一个。)此时,就相当于使用最小的成本来完成这个重传数据的操作,(只是把丢的数据重传)这种方式也称快速重传,是超时重传结合滑动窗口产生的变形操作。但是并不是所有的TCP都要使用滑动窗口,当数据量小的情况下,就不需要使用滑动窗口。

16位窗口大小

        上面说到滑动窗口的大小不是越大越好,为了避免窗口过大,导致接收方处理不过来和丢包问题等使得传输效率降低。因此就需要流量控制根据接收方的处理能力,来限制发送方的发送速度(窗口大小)。那么如何衡量接收方的处理速度呢?这里就使用接收缓冲区剩余空间的大小来作为衡量指标就会直接把接收缓冲区的剩余空间大小,通过ACK报文反馈给发送方,作为发送方下一次数据的窗口大小依据,因此16位窗口大小只对ACK报文有意义。而这个接收缓冲区的剩余空间大小就是16位窗口大小。这里的16位并不意味着窗口大小最大的就是64kb,选项中其中有一个选项就是窗口大小扩展因子,实际的窗口大小是16位扩展因子 <<(向左移动) 扩展因子,此时就可以是能够表示的窗口大小,因此窗口的大小还是非常大的。当接收缓冲区满了之后,发送方会停止发送,虽然发送方停止发送数据了,但也不知道接收方什么时候可以腾出空间来接收数据,就会周期性的发送“窗口探测包”(不会携带具体数据),只是为了触发ACK来查询当前接收缓冲区的情况,一旦发现有空间,就继续发送数据,接收方就可以根据窗口大小来反向限制发送方的传输速度了。

        拥塞控制

        虽然TCP 有了滑动窗口这个大杀器,能够高效可靠的发送大量的数据。但是如果在刚开始阶段就发送大量的数据,仍然可能引发问题。因为网络上有很多的计算机,可能当前的网络状态就已经比较拥堵。在不清楚当前网络状态下,贸然发送大量的数据,是很有可能引起雪上加霜的。TCP引入 慢启动 机制,先发少量的数据,探探路,摸清当前的网络拥堵状态,再决定按照多大的速度传 输数据;如果通畅,就增大数据量,如果不通畅,就减小数据量。而这个数据量就是拥塞窗口。 发送开始的时候,定义拥塞窗口大小为1每次收到一个ACK应答,拥塞窗口加1每次发送数据包的时候,将拥塞窗口和接收端主机反馈的窗口大小做比较,取较小的值作为实际发送的窗口; 像这样的拥塞窗口增长速度,是指数级别的。 " 慢启动" 只是指初使时慢,但是增长速度非常快。当指数增长到一个阈值后,就会从指数增长变成线性增长。当TCP 开始启动的时候,慢启动阈值等于窗口最大值;在每次超时重发的时候,慢启动阈值会变成原来的一半,同时拥塞窗口置回1。所以,拥塞控制为先慢增长,然后指数增长,在线性增长,然后回归小窗口。因此拥塞控制和流量控制,共同的限制了滑动窗口在保证可靠性的前提下高效快速的传输数据。
延迟应答
        如果接收数据的主机立刻返回 ACK 应答,这时候返回的窗口可能比较小。那么是否有办法在条件允许的基础上尽可能的提高窗口的大小呢?延迟应答就是为了解决这种情况的,只需要在返回ACK的时候,拖延一点时间,利用这点时间就可以给应用程序腾出更多的消费数据的时间,接收缓冲区的剩余空间就会更大了,这样下次发的数据就可以更多了。因此,此处到底能提升多少性能取决于应用程序处理数据的能力,不过并不是所有的包都可以延长应答。
捎带应答
         在延迟应答的基础上,我们发现,很多情况下,客户端服务器在应用层也是 " 一发一收 " 的。意味着客户 端给服务器说了 "How are you" ,服务器也会给客户端回一个 "Fine, thank you"
那么这个时候 ACK 就可以搭顺风车,和服务器回应的 "Fine thank you" 一起回给客户端。
粘包问题
        
        首先要明确,粘包问题中的 " " ,是指的应用层的数据包。 在TCP的协议头中,没有如同UDP一样的 "报文长度" 这样的字段,但是有一个序号这样的字段。站在传输层的角度,TCP是一个一个报文过来的。按照序号排好序放在缓冲区中。站在应用层的角度,看到的只是一串连续的字节数据。那么应用程序看到了这么一连串的字节数据,就不知道从哪个部分开始到哪个部分,是一个完整的应用层数据包。 那么如何避免粘包问题呢?归根结底就是一句话, 明确两个包之间的边界
1)对于定长的包,保证每次都按固定大小读取即可;例如上面的Request结构,是固定大小
的,那么就从缓冲区从头开始按sizeofRequest)依次读取即可
2)对于变长的包,可以在包头的位置,约定一个包总长度的字段,从而就知道了包的结束位
3)对于变长的包,还可以在包和包之间使用明确的分隔符(应用层协议,是程序猿自己来定
的,只要保证分隔符不和正文冲突即可)
TCP 异常情况
进程终止:进程终止会释放文件描述符,仍然可以发送 FIN 。和正常关闭没有什么区别。机器重启:和进程终止的情况相同。机器掉电/网线断开:接收端认为连接还在,一旦接收端有写入操作,接收端发现连接已经不在了,就会进行reset 。即使没有写入操作, TCP 自己也内置了一个保活定时器,会定期询问对方是否还在。如果对方不在,也会把连接释放。 另外,应用层的某些协议,也有一些这样的检测机制。

        

可靠性: 校验和 序列号(按序到达) 确认应答 超时重发 连接管理 流量控制 拥塞控制
提高性能: 滑动窗口 快速重传 延迟应答 捎带应答

你可能感兴趣的:(tcp/ip,网络,网络协议)