TCP/IP是互联网的核心协议,也是大多数网络应用的核心协议。就前面一段时间面试中问到的TCP/IP问题,这里给出一个简单的小结。
TCP由RFC79三、RFC112二、RFC132三、RFC200一、RFC2018以及RFC2581定义。
(1) TCP概述
a. TCP提供的是面向链接的全双工服务。
TCP全部的数据会匹配到由源地址,目的地址,源端口,目的端口构成的一个TCP链接之上。TCP链接是一种须要创建的资源,能够经过以后会讲到的握手机制来完成。UDP是一种基于尽力而为机制的协议,不存在UDP链接资源的创建,资源的处理每每由应用层协议代劳了。
b. TCP是提供的可靠服务。
TCP有确认机制来保证数据包的可靠到达,
TCP有CRC校验机制来保证数据包的无差错性,UDP的CRC是可选的,
TCP会从新排序乱序的数据包和丢弃重复的数据,
TCP可以提供流量控制机制,使用滑动窗口算法,
TCP能提供拥塞控制与恢复机制,存在多种TCP拥塞控制模型,
TCP能协商发送的数据报文长度。
TCP报头。
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port | Destination Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Acknowledgment Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data | |U|A|P|R|S|F| |
| Offset| Reserved |R|C|S|S|Y|I| Window |
| | |G|K|H|T|N|N| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum | Urgent Pointer |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
TCP Header Format
对于TCP头的标记位,SYN标记只在三次握手(或四次握手)的时候的被置位,ACK标记会在握手以后全部的TCP报文中被置位。固然也有一些特殊状况,好比有些状况下RST报文不会置位ACK。
这些规则也许在配置复杂的ACL中有用。
(2) TCP协议栈的状态机 (摘自RFC793)
a. TCP链接的创建。TCP链接的创建有主动打开,被动打开以及同时打开三种状况。
三次握手比较清楚,要强调的是ISN,就是初始序列号的选择问题,序列号是32位的,针对不一样的OS,初始序列号的选择每每也是有规律的。
TCP传输的最大报文长度也是在三次握手中协商的。具体说是在也仅在SYN报文中协商的。MSS = MTU - ip_header_len - tcp_header_len。MSS这里也是为了防止分片,提升网络带宽利用率。
TCP三次握手中,最后一个报文ACK,不须要再有额外的确认机制,若是这个ACK在网络中丢弃了,TCP协议栈也有其余的机制来处理。
除了三次握手,还有一种很特殊的应用状况,就是TCP两端同时打开的状况(发送syn),这种状况没有描述在上面的状态机中。
举例子来讲,A经过源端口7777发起到B的目的端口8888的链接的同时,B也经过源端口8888发起对A的目的端口7777的TCP链接。
b. TCP链接的关闭
TCP链接的关闭也有主动关闭,被动关闭和同时关闭三种状况,这三种状况在上面的TCP状态机中都有描述。
TCP链接的关闭须要报文四次交互,由于TCP是一个全双工的服务,因此每一个方向的链接都关闭后,TCP的链接才是完整的拆除。
状态机中,主动关闭和同时关闭最后都会进入到一个TIME_WAITE状态。针对TCP主动关闭的最后一个报文应该是ACK,确认对端的FIN报文。这个状态的概念是该TCP链接的资源并无彻底释放,由于还要确保最后一个ACK报文可以无误的到达对端,确认对端的FIN,不然就仍然要重传ACK。
这个等待的过程(或者资源没有彻底释放的过程)须要等待2MSL时间(考虑报文一次往返)。MSL是最大报文生存时间,RFC793中为2分钟,根据不一样的TCP实现,通常是30s或者1分钟。
因此在TIME_WAITE状态内,该TCP链接所使用的端口和链接资源,不能被继续使用。可是不少TCP实现并无这个限制,只要新的TCP链接所使用的ISN大于TIME_WAITE状态TCP链接所使用的最后序号便可。实现中每每使用
new ISN = latest ISN in time_waite + 128000
IP报文的最大生存时间是TTL值,TCP报文的最大生存时间是MSL,二层上没有报文最大生存时间的概念,存在风暴的可能。
(3) TCP的滑动窗和定时器
a. TCP的报文确认机制。
TCP使用的是滑动窗口机制来发送数据流,因此TCP协议容许连续发送多个TCP分组而不等待对端的确认。
因此发送的分组数据和确认不是一对一的关系。
TCP中,对数据的确认每每是延迟的,通常状况是两个TCP数据对应一个确认,在时延定时器没有溢出的状况下。若是时延定时器溢出了,那么天然也会发送确认报文。
可是,针对存在交互大量微小报文的TCP应用,过于频繁的确认会致使网络利用率的低效,因此TCP支持一种Nagle算法。
b. 延时定时器
当TCP收到报文时候,启动延时定时器,好比200ms。
c. Nagle算法
TCP链接上只能存在一个未被确认的微小报文(41字节的TCP报文),在该确认到达前,TCP仅仅收集微小报文,当确认到达后,以一个分组的形式发出去。
固然,某些应用须要关闭Nagle算法。
d. 滑动窗口机制
窗口合拢(左移):在收到对端数据后,本身确认了数据的正确性,这些数据会被存储到缓冲区,等待应用程序获取。但这时候由于已经确认了数据的正确性,须要向对方发送确认响应ACK,又由于这些数据尚未被应用进程取走,这时候便须要进行窗口合拢,缓冲区的窗口左边缘向右滑动。注意响应的ACK序号是对方发送数据包的序号,一个对方发送的序号,可能由于窗口张开会被响应(ACK)屡次。
窗口张开(右移):窗口收缩后,应用进程一旦从缓冲区中取出数据,TCP的滑动窗口须要进行扩张,这时候窗口的右边缘向右扩张,实际上窗口这是一个环形缓冲区,窗口的右边缘扩张会使用原来被应用进程取走内容的缓冲区。在窗口进行扩张后,须要使用ACK通知对端,这时候ACK的序号依然是上次确认收到包的序号。
窗口收缩,窗口的右边缘向左滑动,称为窗口收缩,Host Requirement RFC强烈建议不要这样作,但TCP必须可以在某一端产生这种状况时进行处理。
e. 重传定时器
目的是为了得到对端的确认报文。若是屡次重传仍然没有得到确认,则会发送复位报文RST。
这里咱们再来看一下TCP的三次握手。
A(发起端) ---> syn ---> B(服务器)
A(发起端) <--- syn/ack <--- B(服务器)
若是TCP客户端A的最后一个ACK丢失了,TCP服务器B没有收到,会是一种什么状况?
这个时候A已经进入到了Establish状态,然而B还只是Syn_Recev状态,因此服务器会重传syn/ack报文,只到链接的最终创建。可是客户端A已经到创建状态了,因此A是有可能发送TCP数据给服务器B的。
因此TCP的两端,最终状态机是有可能不一致的。
后面会详细讲述重传和拥塞控制机制。
f. 坚持定时器
因为TCP没有对ACK的确认机制,因此当接收端窗口从0恢复到必定值的时候,若是接收端发给发送端的ACK报文(标识窗口大小)丢失了,发送端就永远不知道接收端的窗口恢复状况了。
因此发送端会定时发送带一个字节的ACK给接收端,查看接收端的确认报文中的窗口信息。
g. 保活定时器
因为物理缘由,处于IDLE状态的TCP链接一端崩溃的时候,TCP有保活机制来判断对端是否仍然工做。这个设计存在争议,也许应用层应该实现该功能。RFC1122中有描述,保活定时器默认是关闭的。下面截取了一些RFC描述。
Implementors MAY include "keep-alives" in their TCP implementations, although this practice is not universally accepted. If keep-alives are included, the application MUST be able to turn them on or off for each TCP connection, and they MUST default to off.
(4) TCP拥塞控制算法:慢启动、拥塞避免、快速重传和快速恢复
针对拥塞控制,主要有四种模型,即TCP TAHOE,TCP RENO,TCP NEWRENO和TCP SACK。TCP TAHOE模型是最先的TCP协议之一,它由Jacobson提出。
Jacobson观察到,TCP报文段(TCP Segment)丢失有两种缘由,其一是报文段损坏,其二是网络阻塞,而当时的网络主要是有线网络,不易出现报文段损坏的状况,网络阻塞为报文段丢失的主要缘由。针对这种状况,TCP TAHOE对原有协议进行了性能优化,其特色是,在正常状况下,经过重传计时器是否超时和是否收到重复确认信息(dupack)这两种丢包监测机制来判断是否发生丢包,以启动拥塞控制策略;
在拥塞控制的状况下,采用慢速启动(Slow Start)算法和“拥塞避免”(Congestion Avoidance)算法来控制传输速率。 1990年出现的TCP Reno版本增长了“快速重传 ”(Fast Retransmit)、“快速恢复”(Fast Recovery)算法,避免了网络拥塞不严重时采用“慢启动”算法而形成过分减少发送窗口尺寸的现象,这样TCP的拥塞控制就主要由这4个核心算法组成。
a. 超时与重传
RTT的计算与RTO的计算
b. 慢启动和拥塞避免算法
慢启动算法的目的是为了保证TCP发送方发送分组的速率应该匹配收到该分组确认报文的速率,这样的设计可以应用于低速链路的广域网应用。为了实现慢启动机制,为TCP链接增长了一个新的窗口,拥塞窗口cwnd,该窗口初始化为一个报文段(非一个字节,而是一个TCP最大传输报文段大小,MSS)。这样一个方向上的TCP链接有两个窗口,一个是接收窗口用于接收方的流量控制,一个是拥塞窗口用于发送方的流量控制。发送方以这两个窗口中的小值做为方式上限。
慢启动算法:指数算法,cwnd默认为1,当收到一个ack确认时候,cwnd增长为2,当收到两个ack确认时候,cwnd增长为4,接着8,...
拥塞避免算法的目的,是为了防止中间路由器因为网络拥塞引发的数据包超时或者丢包。拥塞避免算法须要用到两个变量,一个是cwnd窗口大小,一个是ssthresh慢启动阈值,对于一个给定的初始链接,cwnd为1,ssthresh为65535。
当拥塞发生(超时或者重复确认),当拥塞发生时候,ssthresh被设置为cwnd和接收窗口中小值的一半,若是是超时引发的拥塞,则cwnd设置为1。
拥塞避免算法:若是cwnd大于ssthresh,每收到一个数据报文的确认,cwnd=cwnd+1/cwnd,cwnd窗口大小单位仍然是mss。
拥塞避免算法实际上是和慢启动配合使用的。cwnd和ssthresh都是动态的值,虽然初始值为1和65535。
当真正拥塞发生的时候,若是是超时或重复ack引发的拥塞,ssthreash会置为cwnd和接收窗口大小的一半,cwnd会降为1,而后执行慢启动算法,直到cwnd大于ssthresh的时候,执行拥塞避免算法;
在慢启动算法期间和拥塞避免算法期间,TCP的发送速率都是在增加的,只是一个是指数增加方式,一个是线性增加方式。
c . 快速重传和快速恢复算法
TCP链接中有两种状况会引发重复的ack,一种是乱序报文,一种是丢包。
快速重传:当发送方收到三个重复的ack后,不会进入慢启动状态,而是马上重传丢失的报文。由于只有接收方收到新的报文段的时候,才会发送重复的ack,这代表TCP链接上仍然有数据流动,因此应该避免使用慢启动降速。
快速恢复:
第一步,当收到第三个重复的ack的时候,ssthresh设置为当前cwnd的一半,重传丢失的报文。设置cwnd为ssthresh加上3倍的报文段大小(cwnd=cwnd/2 + 3)。
第二步,每收到一个重复的ack,cwnd增长1并发送一个分组。
第三步,当下一个确认新数据的ack到达的时候,设置cwnd为上面第一步中ssthresh值,这个ack应该是对重传报文的确认,同时也是对丢包后面的中间报文的确认。
最后,在收到三个重复ack的状况下,速度减半。
快速重传算法首次出如今4.3BSD的Tahoe版本,快速恢复首次出如今4.3BSD的Reno版本,也称之为Reno版的TCP拥塞控制算法。
能够看出Reno的快速重传算法是针对一个包的重传状况的,然而在实际中,一个重传超时可能致使许多的数据包的重传,所以当多个数据包从一个数据窗口中丢失时而且触发快速重传和快速恢复算法时,问题就产生了。所以NewReno出现了,它在Reno快速恢复的基础上稍加了修改,能够恢复一个窗口内多个包丢失的状况。具体来说就是:Reno在收到一个新的数据的ACK时就退出了快速恢复状态了,而NewReno须要收到该窗口内全部数据包的确认后才会退出快速恢复状态,从而更一步提升吞吐量。
SACK就是改变TCP的确认机制,最初的TCP只确认当前已连续收到的数据,SACK则把乱序等信息会所有告诉对方,从而减小数据发送方重传的盲目性。好比说序号1,2,3,5,7的数据收到了,那么普通的ACK只会确认序列号4,而SACK会把当前的5,7已经收到的信息在SACK选项里面告知对端,从而提升性能,当使用SACK的时候,NewReno算法能够不使用,由于SACK自己携带的信息就可使得发送方有足够的信息来知道须要重传哪些包,而不须要重传哪些包。
(5) TCP的应用
前几天和公司作防火墙限速的同事聊天, 咱们公司新的防火墙限速实现方案就用到了TCP窗口机制. 做所周知, QoS除了分类,测速,队列还有调度一类的借助硬件的算法之外,在基于缓存或者丢包的限速基础上,最好还要下降TCP端到端的真正发送的速率,不然容易引发TCP的一系列拥塞控制动做。咱们软件新的设计,就是经过修改ACK方向的通告窗口大小,来控制发送发的速率,可以在限速的基础上,同时下降发送方的发送速率。