##TCP 简单介绍

TCP 是因特传输层的一个面向连接的可靠传输协议,用以一个主机的应用程序向另一主机的应用程序发送数据。因为其是面向连接的,因此在发送数据之前,两个进程必须先“握手”,建立确保可靠传输的相关参数。

TCP 的连接建立过程

TCP 的连接建立被称之为“三次握手”是因为其在建立连接时在两个主机之间发送了3个报文段。并且在连接建立时可通过TCP报头设置相关参数,如首部长度、接收窗口字段(愿意接收的字节数), etc.
因为TCP建立的是可靠传输,因此在连接建立与发送时离不开的重要字段就是序号字段与确认号字段。TCP将传输的数据看成是一个有序的字节流,因此序号字段为该segment首字节的字节流编号,同时由于TCP是全双工的,还可能会接收到数据,因此确认号设置为期望收到的下一字节的序号。

具体来说,TCP连接建立的过程为:

capture from 一口Linux
第一步:SYN,发送方发送SYN标志位置1的TCP报文段,并随即地选择一个初始序号。
第二步:SYNACK, 接收方从提取出TCP SYN报文段,为该TCP连接分配TCP缓存与变量。最后,接收方也会选择自己的初始序号。
第三步:连接建立,发送方收到SYNACK报文段后,也需要为该链接分配缓存与变量,将SYN标志位置0表示连接已建立,此时TCP报文段可以负载数据进行发送了。

而对于TCP连接终止过程:则是通过连接的任意一方发送终止报文(FIN标准位置1),另一方返回FINACK,并在自身的数据传输完成后也发送一个FIN报文。在接收到终止报文的确认后,两边对于该连接的所有资源都被释放了。

capture from 一口Linux

这里在顺带提一下为什么需要“三次握手”:因为TCP建立的是双向(全双工)的可靠连接,因此在接收到一个方向的连接建立请求时需要返回一个确认信息(ACK),只不过在客户进程向服务进程发送连接建立请求时,服务进程的连接建立请求信息可以放在确认信息报文里一同发送。因此两端的两个连接建立报文发送过程可以被简化为三次(三次握手)。
而对于四次挥手,由于一方发送终止请求,另一个确认后,另一方仍可能有数据需要传输,因此得等到另一方数据传输完毕后才能发送终止请求。对其确认后两边都再没有数据需要发送,便可以是否该连接的相关资源。

另外,当TCP服务器接收一TCP SYN报文,但该主机对应端口不接受连接时,会向源主机发送RST标志位置1的特殊重置报文,以告诉源主机不要再继续发送了。

TCP 连接的安全性

TCP 连接建立时面临一个安全威胁,即SYN泛洪攻击,攻击者发送大量SYN报文而不进行三次握手的后续步骤,导致服务器不断为这些半开连接分配资源造成浪费甚至消耗殆尽。为应对该攻击,SYN cookie机制应用而生。
SYN cookie工作方式为:
当接收到SYN报文段时,并不会为此消耗资源来生成一个半开连接,而是根据SYN报文用户端的相关信息与服务器单独知道的秘密数来生成一个初始TCP序列号(cookie),因此服务器发送的时带有cookie作初始序列号的SYNACK分组。
由于服务器不维护任何cookie与对于SYN报文的信息,不分配任何资源,因此避免了SYN防洪攻击的危害。
而对于正常的TCP连接,返回cookie对应的ACK时(ACK_seq = cookie + 1)再根据报文内相关信息(与SYN报文段内一致)与秘密数再生成一次cookie,若该cookie + 1 = ACK_seq,则认为是合法的。服务器生成一个全开的TCP连接。

TCP 发送/接收

在上层将数据交付给TCP进行发送时,TCP会将数据引导到该对应连接的发送缓冲(send buffer)。接着TCP就以报文/Segment为单位将数据交付给下层的网络层进行发送。segment大小受到最大报文长度MSS(Maximum Segment Size)约束,与链路层最大传输单元MTU相关,一般为MTU(1500Bytes) - TCP/IP Header(normally 20+20 = 40Bytes) = 1460Bytes.
同理,当TCP在另一端接收到一个segment后,便将其内数据放入改TCP连接的接收缓存(recieve buffer)中,上层应用程序便可以从此缓存中读取数据流。

TCP 确认(ACK)机制

TCP提供的是一个可靠传输,确保进程从TCP接收缓存中读取到的数据流是正确、按序的字节流,即需要与发送端发送的字节流相同。
为了保证数据报的正确传输,需要对对于报文的送达情况作确认(接收方发送ACK报文给发送方)。TCP提供的是累积确认的机制,即只确认该流中正确接收到(至第一个丢失字节为止)的字节。
对于ACK的产生,RFC5681建议:

事件 接收方ACK操作
当具有期望序号的按序报文到达 正确接收字节变化,ACK变化,ACK报文等待传输,但此时会进行delayed动作,最多等待500ms,若下一个具有期望序列号的报文segment到达,ACK变化,立即发送累计ACK,以确认这两个按序报文段。
当比期望序号大的失序报文到达 接收字节流产生间隔,立即发送冗余ACK,其确认字段号为间隔低端的序列号
当能填补接收字节流间隔的报文到达 若该报文确认号为间隔的低端,则立即发送ACK

TCP通过ACK确认机制来对报文的各个发送场景进行处理:正确送达、丢失和失序。
对于正确送达的报文,正常地返回ACK报文进行确认即可,而对于未正常抵达的报文,通常有两种情况:丢失与失序。下面将介绍对于这两种情况地处理:
对于丢失报文的处理,其采用”超时/重传“机制来进行处理。那么就面临一个问题:超时的阈值该如何设定,依据RFC6298关于管理TCP定时器的建议。可以仅在某个时刻做一次SampleRTT的测量,并依据指数加权移动平均EWMA估计一个EstimatedRTT与波动情况DevRTT。这样超时的阈值便为

TimeoutInterval = EstimatedRTT + 4*DevRTT

当然,这只是建议,在实际部署中各个服务商都可以根据自己的需求来设置超时阈值。

超时重传机制存在一个明显的问题,只有在超过超时阈值时才进行丢包重传的处理,增加了端到端的延时,因此TCP设计了冗余ACK (duplicate ACK)机制,冗余ACK的产生意味着接收方探测到了数据流中地一个间隔,可能是由于报文丢失或乱序抵达。由于TCP的累积确认机制,其只得对已接收到的最后一个按序字节数据进行确认(冗余ACK),若TCP接收方收到对相同数据地3个冗余ACK,便可以将其当作一种指示,仍未发生 了丢包,进行快速重传。

同时,对于这些失序到达的报文,TCP一般选择先缓存保留失序的字节,并等待缺少的字节送达以填补缺失的间隔。

SACK

更进一步地思考,当TCP发送端进行快速重传时,到底应该发送多少个报文呢?这就要引入SACK (selective acknowledgement) [RFC2018] 机制了。SACK数据报会在ACK报文的基础上,再添加最近接收到的序列号的范围,这样TCP发送端就会知道接收端的数据接收情况,知道缺失了哪些数据块,重传时就可以确定需要重新发送的数据范围了。

capture from TCP 可靠传输的实现(二)TCP的重传机制