TCP从原理深析

目录

一.认识TCP协议:

二.浅谈TCP协议结构:

三.TCP可靠性机制:

(1)确认应答:

(2)超时重传:

(3)连接管理:

四.滑动窗口:

五.面向字节流——粘包问题:

六.TCP异常情况处理:

七.总结:


一.认识TCP协议:

TCP全称为“传输控制协议”,与UDP一样,都是传输层的协议,不一样的是,TCP并不限制报文长度,即便是比较大的数据,也可以进行传输,并且,相比于UDP协议的不可靠传输,TCP会通过先进行获取链接(客户端服务器在内核中的连接),来实现可靠性的网络传输,而且它的报文协议更为完善,这也使得它相比于UDP更加安全可靠且高效,最后还有一点区别,UDP是面向数据报进行传输的,而TCP协议则是以字节流的方式(传输的数据以字节为单位)进行传输的。

二.浅谈TCP协议结构:

首先,来看一下TCP协议的结构都有哪些部分:

上述图片中的内容就是TCP协议的结构,知道了结构中都有哪些部分,接下来就可以搞清楚它们的作用和意义了。

1.源端口和目的端口——源端口就是请求发送方的端口号,而目的端口则是接收方使用的端口号,通过这两个部分就可初步实现数据的准确传输。

2.首部长度——报头长度是用来确认报头到哪里结束,载荷从哪里开始的,并且TCP与UDP不同,它的报头长度是“可变长”的,那么4位首部长度是否意味着它最多就只能变长4bit呢?其实并不是这样的,这里的基础单位是“4字节”,所以,总长其实还要乘个4。(TCP报头的最小长度为20字节)

3.6位保留位——当首部长度不够用时,保留位会进行扩容。

4.32位序号——为传输的数据中的第一个字节添加一个序号,用来标记传输的数据的先后顺序,以此明确数据的请求和响应关系(就是本条数据是回应哪条数据的)。(TCP可靠传输的一个因素)

5.32位确认序号——给应答报文使用的,确认从当前序号开始的数据都已经收到了(确认应答机制是TCP可靠传输的最核心机制)。

6.6个标志位——标志位就是6位保留位后面那6个,它们的作用各有不同,其中第二个标志位“ACK”是用来指定当前报文是普通报文(ACK为0,此时32位确认序号是无效的)还是应答报文(ACK为1)的;第四个标签“RST”用来表示复位报文(为1表示为复位报文),复位报文用于重置TCP连接(当网络很差,出现传输数据丢包时,为防止丢包TCP添加了超时重传机制,而当重传了多次依旧丢包时,就会触发复位报文重置机制,让客户端和服务器尝试重新连接);第五个标签“SYN”用于指定当前报文是不是一个同步报文段,同步报文段指的是申请连接时所发送的请求报文,这里提到了建立连接,有建立就会有断开,而断开连接就需要用到第六个标签“FIN”,“FIN”指的是结束报文段,是断开连接时所发送的请求报文(其实建立和断开连接并不复杂,但也并不是像这样解释两个标签就能理解的,后面会详细理解建立和断开连接这个过程,也就是“三次握手,四次挥手”)。

7.16位窗口大小——该字段仅针对应答报文有效,表示当前接收方接收缓冲区的剩余空间大小。根据这个字段就可以让发送方自行判断,灵活调整发送的数据的量。这里的16位并非指定这个“窗口”的大小最多只有64kb,而是在16位的基础上进行了左移操作,至于移动的多少,则是由扩展因子决定的,而扩展因子则要结合实际情况来确定。

三.TCP可靠性机制:

TCP的可靠性机制主要包含以下三个方面,由它们共同实现TCP的可靠性传输,其中,确认应答机制为可靠性传输的核心机制。

(1)确认应答:

确认应答机制是TCP可靠性中最核心的机制,通过给传输的数据的首位字节添加一个序号,来通知服务器当前已经接收到序号为多少的数据了,下一次就应该接收“该序号加载荷长度”的数据了,通过这种方式避免了通信双方请求与应答出现后发先至的情况,使双方能够准确的进行通信。

(2)超时重传:

只有确认应答是完全不够的。网络通信,网络是一个非常不稳定的因素,网络太差就会出现丢包的现象,而这时,如果通信双方一旦一方出现了丢包现象,就会导致另一方无法接收到信息,就会出现通信障碍,这时候就需要超时重传机制来进行弥补。通过一定时间的等待,如果超出了等待时间,就要进行重传,不过此时又会出现一个新的问题,如果是发送方(客户端)丢包,重传是没有任何问题的,但如果是服务器端的应答报文出现了丢包,此时,再进行重传就会出现一个请求处理两次的情况了,如果是一般场景下也许没有什么影响,但如果是转账或者充值,就不再合适了,那么这个时候,为了避免一个请求处理两次的情况就需要进行去重,那如何进行去重呢?其实这里根据前面提到的序号就可以进行去重了,它的底层大致逻辑如下:

TCP会为每一个通信的socket对象创建一个内存空间,称为“接收缓冲区”,客户端发送的数据并不会直接交给服务器,而是储存在了接收缓冲区中(这里也是生产者消费者模型的一种应用),再由服务器调用read方法,将接收缓冲区中的队首元素读走,再进行处理。而这里存储的数据正是按照“序号”进行有序排列的,一旦队首元素序号已经大于再传来的数据的序号时,就说明该请求已经被服务器读取过了,就不需要再次进行处理了也就实现了去重,不仅如此,通过有序的接收缓冲区,也可以避免后发先至的情况。去重之后,由于已经读取过当前的请求了,理应传来新的,所以,这也说明服务器的应答丢失了,这时,服务器只需要再次重新进行一次响应即可。

(3)连接管理:

前面提到TCP是有连接的,而这个连接则是在内核中进行的,在代码上就表现为简简单单的一个serverSocket.accept()。那,它的实际过程又是怎样的呢?它的作用又是什么呢?解答如下:

TCP建立连接的过程又称“三次握手”,其具体的含义就是指,先让客户端与服务器进行“打招呼”,确保双方都能正常的接收到对方的数据并作出响应,客户端会先向服务器发送一个SYN报文尝试请求与服务器建立连接,然后,服务器则会根据收到的请求返回响应ACK,同时,服务器也会尝试和客户端建立连接,向客户端发送一个SYN报文,然后客户端再返回一个ACK,至此,连接才算是真正的初步建立了。那么,为什么服务器又要再向客户端发送一次请求呢?客户端这边的请求通过了不就相当于建立了连接吗?其实则不然,试想一下我们平时打电话的情况,我们首先需要确认一下双方的设备是否都正常,你能不能正常讲话,能不能听到对方的话,对方有能不能也像你一样保证设备正常,故而,服务器向客户端发送连接请求也是必不可少的。图示过程如下:

有连接也有断开,而断开机制我们则称之为“四次挥手”。跟“三次握手”的原理一样,也是先由客户端发起断开连接请求,也就是FIN报文,服务器再根据接收到的请求做出应答(返回一个ACK),然后,再由服务器发起FIN报文,并由客户端做出应答(ACK)。仔细看这里会发现,除了一个是连接一个是断开,这两者几乎是没有差别的,那,为什么连接叫“三次握手”,而断开就成四次了呢?断开不可以也变成三次吗?或者说,连接不能变成“四次握手”吗?这里先来说明一下后者,连接其实是可以叫做“四次握手”的,这样做其实就是将中间的服务器做出应答并发送连接请求(ACK+SYN)这个整体给拆开了 ,这样是完全可以的,但是并没有必要,并且这样做了会很浪费成本,网络通信的每一个请求都是具有一定成本的,能节省的,为什么要浪费呢?再来说第一个问题,那把断开连接中服务器的ACK跟FIN也一并发送,不也可以节省成本吗?最理想的情况下(客户端跟服务器都没问题并正常断开连接)的确是这样的,但是,如果出现了意外呢?如果服务器出现了异常,并未及时发出FIN请求呢?这种情况并非不会出现,事实就是,往往还挺常见的,那么这个时候,就无法将二者作为一个整体发送了,也就成为了“四次挥手”。那么,如果服务器一直不进行断开,不会出问题吗?其实是并不会的,这其中也有类似超时等待的机制,只不过一旦超时,客户端会强行断开连接,同时,如果在强行断开前进行了数据读取等操作也是不会出问题的,如果在断开前接收缓冲区中还有数据的话,则会成功读取到,否则则会抛出异常,是不会让客户端出现非法读取等非法操作的。断开连接过程图如下:

以上就是TCP可靠传输机制的总结,因为TCP的可靠性并不可见,我们无法明确的靠肉眼感知到,只能通过解读实际原来的方式来进行理解,理解TCP究竟为什么是可靠的,是靠什么来确保它的可靠性的。

四.滑动窗口:

通过上面的介绍已经可以知道TCP是可靠传输的,但,不论是那种机制这其中都涉及到了一个对应答的等待。无论是确认应答或者是超时重传,客户端都需要等接收到了服务器端的应答才能进行下一步的通信。那么,这个大量的等待过程就会让主机间的通信效率大大降低,而引入滑动窗口,就是为了更好的减小等待带来的影响,提高通信效率。接下来,就来总结一下滑动窗口的逻辑原理以及它是如何让主机间的通信效率提高的。

在最初我们学习中所使用的TCP通信图中,数据都是一条一条发送的,而实际上,TCP通信中,数据都是以一组的形式进行传输的,这样一来,就不再需要一个数据等待一次ACK了,就可以让多个数据都在一次等待后完成发送,而这里一组数据的多少,就是所谓的“窗口”。那么,知道了“窗口”滑动又是什么意思呢?这里滑动并不是单纯的向后移动那么简单,因为,这里还要考虑到一个问题,当多个数据批量发送过去之后,服务器会针对每个数据返回一个ACK,那么此时,是等首个数据的ACK被接收到了就去发送下一个数据,还是要等所有数据的ACK都被接收到了再去发送下一组数据呢?很明显,这里答案就是选择前者的处理方式,因为等待所有数据ACK,等待时间还是过长了,而在等待的过程中,客户端一直处于空闲的情况,这依旧没有提高什么效率,所以,正确的处理方式就是,每当首位的数据ACK被接收到了,就让“窗口”向后移动一位,这样一来,就可以保证在“窗口”大小不变的情况下,客户端一直都在等待固定数量的ACK,并在等待的同时完成了后面数据的发送,大大提高了通信的效率。这里如果只靠想象可能有点抽象,所以可以用下图进行辅助理解:

上图中,主机A向主机B一次发送了一组(四个)数据,随后,当主机A接收到了“2001”,也就是首位数据的ACK时,就进行了一次滑动,并将“5001~6000”这条数据发送给了主机B,这个过程也就是我们所说的“滑动”,并在“滑动”的过程中又保证了“窗口”的大小不变,这个整体过程就是所谓的“滑动窗口”(本质上就是批量发送数据)。

憋急,还没完,滑动窗口的目的是提高TCP的通信效率,但这个前提是保证可靠性的情况下,所以,这里就不得不考虑一个问题,那就是,如果在批量传输的过程中发生了丢包怎么办?这里跟前面提到的超时重传情况一样,也是分成两种丢包类型,一种是发送方数据报丢失,另一种是ACK丢失,对于后者而言,只要不是所有的ACK都丢失,其实是没有影响的,因为只要有一个ACK接收到了,这也就意味着它前面的数据都已经被接收到了,就直接向后“滑动”即可,那么,如果ACK全部丢失了,那其实也就意味着网络已经出现严重问题了,此时本就已经不再具备可靠传输的前提条件了,自然也就没有讨论的意义了。那么,如果是发送方数据报丢失呢?此时,就必须重传了,但是何时重传怎样重传,又成为了新的问题。在前面确认应答机制中提到过,接收方也就是服务器,会根据序号来进行确认,告诉给客户端,接下来应该传输从序号为多少开始的数据了,而一旦出现了数据报丢失的情况,服务器就会因为并未受到来自客户端的自己“指定的”数据,而进行索要,直到收到了为止,也就是一直返回上个数据的ACK,所以,一旦当一组数据中有某个或者几个数据报丢失了,服务器就会不停的索要丢失的数据报,也就是说,此时服务器会一直返回上一个数据报的ACK,而当一组数据都返回这样一个结果时,客户端也就知道当前出现了从序号为多少开始的数据报丢失情况了,这时,客户端就会重传丢失的数据报,重传后,服务器会根据接收缓冲区中已经读到的最后一个数据报的序号作为ACK进行返回,注意,这里的“最后一个数据报”并非指的是位置上的最后一个,而是在确保没有其他丢包的情况下的最后一个,也就是说,如果有其他丢包的情况,服务器就会以当前丢失的数据报的上一个数据的ACK作为返回结果,直到丢失的数据报都重传成功了为止。只有文字理解过于抽象,同样结合图示进行理解:

正如上图所示,由于“1001~2000”的丢失,导致其他的数据报返回的ACK都是在索要“1001”的,而当“1001~2000”重传成功过后,由于服务器已经成功接收到了其它数据报了,所以就直接返回了“最后一个”数据报的ACK了(注意:这里要结合上图正确的理解“最后一位数据报”的意义)。 

五.面向字节流——粘包问题:

前面提到,TCP与UDP不同,UDP是面向数据报的协议,而TCP则是面向的字节流,这一点我们在代码层面也是可以很明显地感受到的,就如下图:

这里可以明显看到,TCP是通过输入流输出流来进行读取和写入的,但是,面向数据报我们知道,一次发送的就是一个完整的数据,但是,面向字节流则不一样,它每次读取到的都是一定数量字节,这个时候就有可能会出现明明是一条数据结果读出了多条或者多条数据读成了一条等等,而这种情况也被称之为“粘包问题”。 而这里,就要来总结一下,粘包问题要如何解决。

由于TCP已经被规定死了,它只能读取字节,所以想在传输层解决这个问题是不太可能了,不过,我们可以在在应用层传输数据格式上进行改变。这里有两种常用方法,一种是在应用层约定给每个数据报间添加一个分隔符,这样一来,在进行读取时就可以以分隔符为界限来进行读取了;第二种,则是在应用层约定以每个数据报的前两位位长度指示位,指明当前数据报有多少字节,如此一来,当接收方进行读取时,就可以先读取两个字节,知道了长度,再根据长度来进行读取,进而防止粘包问题。

六.TCP异常情况处理:

网络传输总会遇到各种各样的意外,而一旦出现意外时,通信协议就会做出相应的反应来处理当前的异常状况,而这里,就来盘底点一下TCP遇到异常状况时是怎么处理的:

1.进程崩溃:当进程突然崩溃时,主机间的通信也会随之断开,但这种断开是正常的断开还是非正常的呢?按照一般逻辑来想,也许就会觉得这里是非正常的断开,但其实此处是正常的断开了连接。为什么是正常的呢?因为,进程崩溃并不代表主机崩溃了,主机间的正常断开,也就是“四次挥手”依旧是可以正常进行的,故而,进程崩溃对于TCP而言并没有什么影响,并不需要刻意处理。

2.主机关机:首先要明确,此处所说的主机关机指的是主机的正常关机,而不是强行拔电源等导致的非正常关机。结合一下现实其实就可以猜到,主机正常关机对于TCP来讲也是没有什么影响的,也并不需要刻意的处理,这是为什么呢?因为,主机在正常关机时会强制关闭所有进程,而这个时候就像进程崩溃一样,也会触发“四次挥手”,不同的是,另一方所返回的ACK和FIN请求,本机可能无法接收到,不过这依旧不重要,因为,当对方主机多次重传没有得到回应时就会自动强制断开连接了。

3.主机掉电(/网线断开):主机掉电指的就是上面所说的强行拔电源,这就会导致主机进入非正常的关机,并且,拔电源是不会让主机有反应时间的,因为一瞬间就关机了,那么这个时候也就没办法正常触发“四次挥手”,也就无法正常断开连接,此时,TCP就需要特殊处理了。此时,又会分成两种情况,一种是接收方掉电了,另一种是发送方掉电,若是接收方掉电,则相对好处理一些,因为接收方一旦掉电,发送方就不可能收得到ACK报文,也无法成功重置连接,自然也就会强制中断连接;而另一种,就相对比较复杂了,一旦发送方掉电,接收方就会陷入等待的状态,并且他也不清楚自己要等多久,这时候,TCP就给出了一种解决方案,那就是让接收方周期性的向发送方发送一个不携带任何业务逻辑的“心跳包”,这个“心跳包”的主要目的就是让发送方定期给接收方返回一个ACK,以确保发送方在线并且网络状况正常,而一旦这边接收不到ACK了,就会向前者一样,强制断开连接。

七.总结:

TCP的相关特性非常的多,本文也只是挑的重点特性来总结,与UDP相比,二者显得各有优缺点,在适当的场景下选择合适的协议,才能做到效益最大化,除了这两个协议其实还有其他的一些名义上的“传输层协议”,为什么说是名义上的呢,因为它们的真正实现大多其实都在应用层,而传输层只不过是用UDP当最实现的基础而已。所以,合适的场景选择合适的协议,理解协议的特点才是最首要的。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值