之前受到Wireshark——从此我就喜欢上了它,就像是学武之人得到了一把称手好剑的启发,带着回顾、深入TCP的目标,回顾了《TCP-IP协议卷1》《图解TCP/IP协议》,受益匪浅。写这篇文章,希望自己能对TCP形成一个系统性的知识沉淀,也希望能给初学者一个基本概念的认识,读完本文再深入书籍,应该也是不错滴。
学习路径:
1、阅读《TCP-IP协议卷1》的TCP章节(相关知识非常全面,各种算法,但是因为拗口的翻译,比较难一时半会理解)
2、过一遍RFC 793 (TCP协议的规定,八几年就订好的,谢谢祖师爷赏口饭吃)
3、阅读《图解TCP/IP协议》的TCP章节(深入浅出,各种图示让你能更好的理解其作用过程,但描述的知识比较少,无法形成知识体系)
4、重复步骤1进行精读并输出xmind
建议最好还是结合 《TCP-IP协议卷1》+《图解TCP/IP协议》一起看,看不懂时可以过一下《图解TCP/IP协议》然后再结合《TCP-IP协议卷1》的实战看
本文依据《TCP-IP协议卷1》的知识思路进行总结
1、TCP:传输控制协议(对TCP协议的一个总览,并详解了TCP协议头的构成)
2、TCP连接的建立与终止(主要讲解了TCP的三次握手与四次挥手,并讲述了TCP连接的状态变迁,面试常问)
3、一些TCP的数据交互规则(这一章比较小,主要讲了几个数据交互的规则)
4、TCP成块的数据流(主要讲解了TCP数据传输中会出现的问题和问题解决方案,主要是滑动窗口,面试常问)
5、TCP的超时与重传(主要讲解了当TCP传输时遇到超时的问题如何解决,主要是通过拥塞避免算法)
6、TCP的坚持定时器(讲解一些确认通道可用性的定时器)
7、TCP的未来和性能(一些TCP的周边知识)
传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。
说白了TCP就是一种传输协议,就像HTTP协议一样,HTTP的目的是指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应,而TCP的目的是为了确保数据传输的可靠性,我给你一个数据包,你一定就要收到,收不到的话那么我就会给你重发,直到我超时放弃你了。
HTTP是建立在TCP之上的,当你建立起TCP连接之后,在上面传输的数据用的是HTTP协议。
经过TCP协议定义的三次握手之后,TCP协议建立一条虚拟的连接用于传输数据。
连接是指各种设备,线路,或网络中进行通信的两个应用程序为了相互传递消息而专有的、虚拟的通信线路,也叫虚拟电路。
一旦建立了连接,进行通信的应用程序只使用这个虚拟的通信线路发送和接收数据,就可以保障信息的传输。应用程序可以不用顾虑其链路的问题。TCP则负责控制链接的建立,断开,保持等管理工作。
数据被拆分成数据段,然后改通道进行传输,数据段传输的可靠性由TCP协议来保障。
在三次握手时,交互通信的双方会把自己最大的消息长度(MSS:Maximum Segment Size)告诉对方,TCP服务会把数据按MSS切分段发送
在TCP中,当发送端的数据到达接受主机时,接收端主机会返回一个收到消息的通知。这个消息叫做确认应答(ACK)。这个机制就好比我们经常在电视上看到人们经常用对讲机说话,当你收到别人发送的语音时,你都需要回复一句“收到”让发送者明确知道这条消息,可以安心的知道消息已被接受者接受。
当发送者在一定时间内没有收到接受者的回复时,发送者可以根据重传策略(后面会介绍)进行消息重传,保证消息能真正的发到接受者手中。
当数据包丢失的时候,TCP会根据重传策略(后面会介绍)进行消息重传,重传策略包含快速重传,还有超时重传两种场景。
TCP校验和(Checksum)是一个端到端的校验和,由发送端计算,然后由接收端验证。其目的是为了发现TCP首部和数据在发送端到接收端之间发生的任何改动。如果接收方检测到校验和有差错,则TCP段会被直接丢弃。此时发送端没有收到ACK,便会重新传递一个完好无损的数据包。
TCP协议将数据切分为多个小片段(数据被划分为合理长度),小片段由头部(header)和数据(payload)组成,为了确保抵达数据的顺序,TCP协议给每个片段的头部(header)都分配了序列号,接受者可以使用该序列号排序。
原因与3.5一致,接受者有了一波数据段的序列号,如果ACK因为网络关系没有回复给到发送者,导致发送者重传,那么发送者重传后,接收者可以利用序列号进行去重,并在下一个ACK中回复发送者。
TCP的流量控制主要使用的是滑动窗口协议算法,在后面会有介绍。
以上所述都是TCP连接可靠性的保证,那TCP为什么可以如此强大呢?其中一部分原因呀,是它定义了一个强大的TCP协议头,接下来,我们先来了解这个头,每一个数据段都要携带这么一个头。
字段解析:
端口号:所谓的端口,就好像是门牌号一样,客户端可以通过ip地址找到对应的服务器端,但是服务器端是有很多端口的,每个应用程序对应一个端口号,通过类似门牌号的端口号,客户端才能真正的访问到该服务器。为了对端口进行区分,将每个端口进行了编号,这就是端口号。(软考今年出了一题。。)
序号:对每个数据段做的标记,标志这某个数据段在某个数据中的位置(划重点,发送者利用该序号表示数据段是完整数据的哪一部分,设置的规则是上一个段序号+上一个段长度。接收者利用该序号表示发送者下一个应发送的应该是哪一部分的数据,设置的规则是收到的数据段序号+数据段的长度。接收者回复的ACK序号,是发送者下一个数据段的序号,代表的意思是收到该序号之前的数据段)
标志:
URG:紧急指针(代表紧急情况,接受者可以选择优先处理)
ACK:应答
PSH:推送数据(接收方应该尽快将这个报文段交给应用层)
RST:连接重置(遇到该状态码,代表你的连接已非常混乱,一般应用层会收到一个连接重置异常)
SYN:建立连接
FIN:结束
窗口大小:用于流量控制(窗口大小为字节数,起始于确认序号字段指明的值,这个值是接收端正期望接受的字节)
校验和:主要是检验数据的准确性
紧急数据:当URG标志1时有效,发送端向另一端发送紧急数据
可选字段:其他附件信息
由上可见呀,TCP协议中许多信息都是蕴含在TCP协议头里面,我们使用抓包工具抓包时,能分析统计到关于连接的情况都是根据头部信息得到的,对头部信息的掌握能快速让我们掌握TCP协议原理,对问题的定位我们能更深入层面的分析。
看完了TCP协议的基本信息介绍,我们老生常谈,继续来聊聊TCP的连接与断开问题,从连接断开的过程中我们能初步了解到TCP数据段的传输机制,与可靠性保证,首先,我们先来看看连接与断开的时序图。
TCP的连接,仿佛就像男女之间建立美妙的爱情关系
男:我爱你
女:嗯,我也爱你
男:嗯
相互的询问,相互的确认,爱情就这样产生。TCP也不过如此
clientA:client端发送一个SYN段指明server端打算连接的server的端口,以及初始序号ISN
serverB:server发回包含server的初始序号SYN报文段作为应答。同时将确认序号设置为client的ISN+1以对客户端的SYN报文段进行确认。一个SYN将占用一个序号
clientA:client必须将确认序号设置为server的ISN+1以对服务器的SYN报文段确认
以上的过程,我们关注两个问题
关于初始ISN
ISN随时间而变化,是一个32bit的计数器,每4ms加1,这样的选择序号的目的在于防止在网络中被延迟的分组在以后又被传送,而导致某个链接的一方对它作错误的解释。
为什么需要三次握手?
这个问题其实真的没有那么复杂,根据上文,我们可以知道TCP的可靠性保障机制之一就是消息确认机制,你想想呀,如果是两次握手的话,那少了一次ACK,server怎么知道到底client是收到自己的SYN请求没呢?这样可靠性就得不到保证了。
成年人的爱情就是龙卷风,来的快,走的快
女:对不起,我要跟你分手,我已经放下你了
男:嗯,我知道了
(男生并还没放下,男对女还是思念思念思念...)
(一段时间过去了,男生终于也放下了)
男:我也放下你了,我们就这样吧
女:嗯
相互的告别,相互的确认,当对方跟你告别时,你或许意犹未尽,终于等到你把最后一滴思念都给完了,那你也会跟她做最后的告别,一段感情到此为止。TCP也不过如此
clientA:向server发送一个FIN
serverB:发回一个ACK,ISN为收到的FIN的ISN+1
(serverB 继续把没发完的数据发发发,直到发完数据)
serverB:向client发送一个FIN
clientA:发回一个ACK,ISN为收到的FIN的ISN+1
这里我们需要关注一下这个问题:
为什么握手是三次、而挥手要四次?
这是由TCP的半关闭(half-close)造成的。既然一个TCP连接是全双工的,因此每个方向必须单独的进行关闭。这原则就是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向连接。当一端收到一个FIN,它必须通知应用层另一端已经终止了那个方向的数据传送。发送FIN通常是应用层进行关闭的结果。
TCP状态在百度上面找不到定义哈,说一下笔者的理解。TCP状态是TCP连接端在主动或被动做了某种操作后显示的状态,该状态可以展示某条连接当前的信息即此时该连接正在做的某种操作(正在连接、已连接、发送数据…),在Linux上,我们可以通过以下命令看到所有TCP连接的状态情况
ss -ta
对笔者来说,TCP状态主要还是便于做故障分析,假定几个场景场景
#当前服务器的负载
ss -ta|grep 'ESTABLISHED'|wc -l
#服务端CLOSE_WAIT堆积,应用层没对FIN做响应,导致连接数占满
ss -ta|grep 'CLOSE_WAIT'|wc -l
这个图真可以,有时候状态也会忘,多看看真会熟悉滴
为什么要存在TIME_WAIT状态?
TIME_WAIT状态也称为2MSL等待状态。每个具体的TCP实现必须选择一个报文段最大生存时间MSL。它是任何报文段被丢弃前在网络内的最大时间。
也就是说在2MSL时间内,虽然可以重新启动服务器,但是这个服务器还是要平静的等待2MSL时间的过去才能进行下一次连接。
关于FIN_WAIT_2状态
只有当另一端的进程完成这个连接关闭,客户端才会从FIN_WAIT_2状态进入TIME_WAIT状态。这意味着我们client可能永远保持这个状态。另一端也将处于CLOSE_WAIT状态,并一直保持这个状态直到应用层决定进行关闭。
许多伯克利实现采用如下方式来防止这种在FIN_WAIT_2状态的无限等待。如果执行主动关闭的应用层将进行全关闭,而不是半关闭来说明它还想接受数据,就设置一个定时器。如果这个连接空闲10min75s ,TCP 将进入CLOSED状态。
最大报文段长度(MSS)是什么?
我们在上面的交互图中可以看到当发起连接时,双方都会把自己的MSS告诉对方,表示TCP传送另一端的最大块的数据长度。
同一时间发送的段=滑动窗口/MSS。
重视复位报文段RST
一般来说,无论如何一个报文段发往基准的连接出现什么错误,TCP都会发出一个报文段,一般来说可能会有以下场景
TCP可否同时打开/同时关闭?
TCP是特意设计为了可以处理同时打开,对于同时打开它仅建立一条连接而不是两条连接。TCP协议也允许这样的同时关闭。
TCP选项的作用?
TCP选项,是包含在TCP头部里面一些其他的相关信息。如最大报文段长度、窗口扩大因子、时间戳等。
TCP服务器端程序设计是怎样的,socket?
当一个新的连接请求到达服务器时,服务器接收这个请求,并调用一个新进程来处理这个新的客户请求。不同操作系统使用不同的技术来调用新的服务器进程。在这个服务程序中,我们关注一下
TCP服务器端口号:只有处于LISTEN的进程能够接受新的连接请求。处于ESTABLISHD的进程不能接受SYN报文段。
连接限制:可限定本地IP地址、远端IP地址
呼入请求队列(划重点):到达多个连接请求,但是服务器正忙,并无法创建线程或进程去满足它,那么把这个连接丢入这个队列(backlog),不止一次在中间件调优中看到该参数(参见Redis),队列使用的规则
这一章节是一个小章节,主要讲一下玉数据交换有关的规则,主要是ACK的延迟确认、Nagle算法、窗口大小的通告
在经典书籍翻译是经受时延的确认,什么意思呢?指的是通常TCP在接收到数据时并不立即发送ACK;相反,它会延迟发送,以便将ACK与需要沿该方向发送的数据一起发送。绝大多数实现采用的时延为200ms,也就是说,TCP将以最大200ms的时延等待是否有数据一起发送。
该算法要求一个TCP连接上最多只能一个未被确认的未完成小分组,在该分组的确认到达之前不能发送其他的小分组。相反,TCP收集这些少量的分组,并在确认到来时以一个分组的方式发送出去。该算法的优越之处在于它是自适应的:确认到达的越快,数据也就发送的越快。而在希望减少微小分组数目的低速广域网上,则会发送更少的分组。下图是在xshell上面的Nagle选项
当我们关闭Nagle算法时,可以让小消息必须无延时的发送,以便为进行某种操作的交互用户提供实时的反馈
TCP都在每个数据包上面向对方通告自己当前能处理的窗口大小,便于发送方以最优的策略发送数据包
这一章的名字听着很抽象,其实讲的就是滑动窗口协议。TCP以一个段为单位,每发一个段进行一次确认应答处理,这样的传输方式有一个缺点。那就是,包的往返时间越长通信性能就越低。
为了解决这个问题,TCP引入了窗口的概念。即使在往返时间较长的情况下,它也能控制网络性能的下降,确认的应答不再是以每个分段,而是以更大的单位进行确认时,转发时间将会被大幅度缩短。也就是说,发送端主机,在发送了一个段以后不必要一直等待确认应答,而是继续发送。还记得我们之前说的MSS吗?每一次的数据包大小不能超过MSS,每一波数据大小不能超过接收方的接收窗口大小。
滑动窗口协议(Sliding Window Protocol),属于TCP协议的一种应用,用于网络数据传输时的流量控制,以避免拥塞的发生。该协议允许发送方在停止并等待确认前发送多个数据分组。由于发送方不必每发一个分组就停下来等待确认,因此该协议可以加速数据的传输,提高网络吞吐量。其单位为字节。
接下里,我们先看看看滑动窗口的可视化视图,掌握了这个可视化图,我们能更好理解滑动窗口的滑动二字意义在哪里,是如何控制数据包传输的
再接下来看看窗口的状态变化
虽然看完上面的视角图,你可能会有些疑惑,我汇总了一次请求的数据交换与窗口的变化图,仔细一品,感受滑动窗口的运作机制。
这其中
发送方使用该标志通知接收方将所收到的数据全部提交给接收进程。这里的数据包括与PUSH一起传送的数据以及接收方TCP已经为接收进程收到的其他数据
有了TCP的窗口控制,收发主机之间即使不再以一个数据段为单位发送确认应答,也能够发送大量数据包。然而,如果在通信刚开始时就发送大量数据,也可能会引起其他问题。
一般来说,在网络出现拥堵时,如果突然发送一个较大量的数据,极有可能会导致整个网络瘫痪。故有了慢启动的过程。
慢启动为发送方的TCP增加了另一个窗口:拥塞窗口(congestion window),记为cwnd。当与另一个网络的主机建立TCP连接时,拥塞窗口被初始化为1个报文段(即另一端通告的报文段大小)。每收到一个ACK,拥塞窗口就增加一个报文段(cwnd以字节为单位,但是慢启动以报文段大小为单位进行增加)。发送方取拥塞窗口与通告窗口中的最小值作为发送上限。拥塞窗口是发送方使用的流量限制,而通告窗口则是接受方使用的流量限制。
发送方开始时发送一个报文段,然后等待ACK。当收到该ACK时,拥塞窗口从1增加为2,即可以发送两个报文段。当收到这两个报文段的ACK时,拥塞窗口就增加位4。这是一种指数增加的关系。
还记得TCP头中的URG字段吗?它使一端可以告诉另一端有些具有某种方式的“紧急数据”已经放置在普通的数据流中。另一端被通知这个紧急数据已被放置在普通数据流中,由接收方决定如何处理。
可以通过设置TCP首部中的两个字段来发出这种从一端到另一端的紧急数据已经被放置在数据流中的通知。URG bit 被置为1,并且一个16 bit的紧急指针被置为一个正的偏移量,该偏移量必须与TCP首部中的序号字段相加,以便得出紧急数据的最后一个字节的序号。
如Telnet 和 Rlogin。当交互用户键入中断键时,或FTP当交互用户放弃一个文件传输时。
根据上文的确认机制我们可以知道,慢启动是呈现指数级上涨的,速度是非常快的,但是如果接收方接收方处理不过来,或者是大家都因为速度太快发生网络拥堵的话,那快的意义就没有了,所以呀,TCP需要在慢启动一定的时间后,慢下来,我们称它为拥塞避免算法。
但是这个拥塞避免算法是广义的一个过程,包含了慢启动的过程,又包含了快重传,快恢复,超时重传,还有拥塞避免算法(这里指的是真正的一个算法,而不是过程)过程,以下算法过程是从经典书中摘录,结合图,好好过两遍,悟到了整个过程你就懂了。
拥塞避免算法和慢启动算法需要对每个连接维持两个变量
对一个给定的连接
TCP的输出
当拥塞发生时(超时或收到重复确认)
当新的数据被对方确认时,就增加cwnd,但增加的方法依赖于我们是否正在进行慢启动或拥塞避免
从上文的拥塞避免算法中我们可以看到,当3次重复收到ACK的时候,代表网络堵塞,有丢包的现象产生,所以TCP会降低速度,并重传其报文段。
快速重传:当重复收到三个重复ACK时,我们就重传丢失的数据报文段,而无须等待超时定时器溢出。
快速恢复:当快速重传后,立刻执行拥塞避免算法,即是快速恢复。
算法执行过程
为什么一定要是三个重复ACK才会触发快速重传?
假如这只是一些报文段的重新排序,则在重新排序的报文段被处理并产生一个新的ACK之前,只可能产生1~2个重复的ACK。如果一连串收到3个或3个以上的重复ACK,就非常可能是一个报文段丢失了。
重新分组
当TCP超时并重传时,它不一定要重传同样的报文段。相反,TCP允许进行重新分组而发送一个较大的报文段,这将有助于提高性能。
这一章开始,我们了解一些周边的小知识。
什么是坚持定时器?
当客户端被通告窗口为0时,客户端将停止发送数据。会引起客户设置其坚持定时器。如果该定时器时间到时客户还没有接收到一个窗口更新,它就探查这个空的窗口更新是否丢失。
计算坚持定时器时使用了普通的TCP指数退避。窗口探查包含一个字节,但是所返回的窗口为0的ACK并不是确认该字节,因此这个字节被持续重传。
坚持状态与重传超时之间的不同是TCP从不放弃发送窗口探查。这些探查每隔60s发送一次,这个过程将持续到或者窗口被打开,或者应用进程使用的连接被终止。
糊涂窗口综合症
指的是少量的数据将通过连接进行交换,而不是满长度的报文段
故障可能发生的原因
避免措施
保活定时器,主要是保持TCP连接的,比如你经常遇到的JDBC线程池,里面就有一个保活的概念。TCP是一条虚拟的链路,虽然通信双方进行握手建立虚拟通道,但是建立通道后如果不发生数据交互的话,那双方都不知道对方是不是还保持socket会与自己继续通讯,所以有了这个保活定时器。这也解决了之前疑惑的TCP连接的保活问题了。
通常是由服务器端设置这个功能
算法描述
TCP协议在草案中定义的窗口大小最大为65535bit,那是当时的网络情况比较差,这个窗口大小已经够用了。现在可是5G时代65535算个啥,所以呀,TCP就在头中的选项做了对窗口大小的扩展,以适应快速的网络环境。
至于时间戳选项就没得说了,用于计算包的往返时间,在wireshare给你展示包的发送时间,也是美美的。
全文总结:之前对于TCP是陌生又熟悉,有了整套知识架构的支撑后,现在排查问题的过程中都觉得自信满满的,文章始终无法深入介绍TCP的完整知识,希望本文能基于你帮助,后面继续深入看书+tcpdum+wireshark,我深信你终能吃透TCP