TCP/IP详解卷一,从入门到放弃。实在坚持不下去了,本来都准备换书了,结果今天头条面试又问到了TCP相关内容。
那就再坚持一下吧。把TCP相关内容,做一个简单的整理吧。算是读书笔记,毕竟只看书不记点什么的话,容易犯困。。
基本相关
TCP数据被封装在一个IP数据报中,如下所示:
-
如上图所示,TCP首部的个数字段,如果不计任选字段,它通常是20个字节
- 每个TCP段都包含源端口和目的端口号,用于寻找发端和收端应用进程。这两个值加上IP首部中的源端IP和目的端IP唯一确定一个TCP连接。
- 序号用来标识从TCP发端向TCP收端发送的数据字节流,它表示在这个报文段中的第一个数据字节。如果将字节流看作在两个应用程序间的单向流动,则TCP用序号对每个字节进行计数。序号是32 bit的无符号数,絮叨到达2^32 - 1后从0开始。
- 当建立一个新的连接时,SYN标志变为1。序号字段包含由这个主机选择的该连接的初始序号ISN。该主机要发送的数据的第一个字节序号为ISN + 1,因为SYN标志消耗了一个序号。
- TCP为应用层提供全双工服务。这意味数据能在两个方向上独立地进行传输。因此,连接的每一端必须保持在每个方向上的传输数据序号。
- TCP首部中有6个标志比特。它们中的多个可同时被设置为1。
- URG 紧急指针有效
- ACK 确认序号有效
- PSH 接收方应该尽快将这个报文段交给应用层
- RST 重建连接
- SYN 同步序号用来发起一个连接。
- FIN 发端完成发送任务
- TCP的流量控制由连接的每一端通过声明的窗口大小来提供。窗口大小为字节数,起始于确认序号指明的值,这个值是接收端正期望接收的字节。窗口大小是一个16bit字段,因而窗口大小最大为65536字节。
- 检验和覆盖了整个TCP的报文段:TCP首部和TCP数据。这是一个强制性的字段,一定是由发端计算和存储,并由接收端验证。
- 只有当URG标志置1时紧急指针才有效。紧急指针是一个正的偏移量,和序号字段中的值相加表示紧急数据最后一个字节的序号。
- 最常见的可选字段是最长报文大小,又称为MSS(Maximum Segment Size)。每个连接方通常在通信的第一个报文段(为建立连接儿设置SYN标志的那个段)中指明这个选项。它指明本端所能接收的最大长度的报文段。
TCP连接的建立与终止
建立连接协议
-
为了建立一条连接
1) 请求端(通常称为客户)发送一个SYN段指明客户打算连接的服务器的端口,以及初始序号(ISN)。这个SYN段为报文段1
2) 服务器发回包含服务器的初始序号的SYN报文段(报文段2)作为应答。同时将确认序号设置为客户的ISN加1以对SYN报文段进行确认。一个SYN将占用一个序号。
3) 客户必须将确认号设置为服务器的ISN加1以对服务器的SYN报文段进行确认(报文段3)这三个报文段完成连接的建立。这个过程也为三次握手(three-way handshake)
- 发送第一个SYN的一端将执行主动打开(active open)。接收这个SYN并发回一个SYN的另一端将执行被动打开。
当一端为建立连接而发送它的SYN时,它为连接选择一个初始序号。ISN随时间而变化,因此每个连接都将具有不同的ISN。RFC 793指出ISN可看作是一个32bit的计数器,每4ms加1。这样选择序号的目的在于防止在网络中被延迟的分组在以后又被传送,而导致某个连接的一方对它作错误的解释。
连接终止协议
- 终止一个连接需要经过4次握手,这由TCP的半关闭(half-close)造成的。既然TCP连接时全双工(即数据在两个方向上都能同时传递),因此每个方向需要单独地进行关闭。
- 当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向连接。当一端收到一个FIN,它必须通知应用层另一端已经终止了那个方向的数据传送。发送FIN通常是应用层进行关闭的结果。
- 收到一个FIN只意味着在这一方向上没有数据流动。一个TCP连接在收到一个FIN后仍能发送数据。而这对利用半关闭的应用来说是可能的。
最大报文段长度
- 最大报文段长度(MSS)表示TCP传往另一端的最大块数据的长度。当一个连接建立时,连接的双方都要通告各自的MSS。(MSS选项只能出现在SYN报文段中)。如果一方不接收来自另一方的MSS值,则MSS就定为默认值536字节。(这个默认值允许20字节的IP首部和20字节的TCP首部以适合576字节IP数据报)
- 一般来说,如果没有分段发生,MSS还是越大越好。报文段越大允许每个报文段传送的数据就越多,相对IP和TCP首部有更高的网络利用率。当TCP发送一个SYN时,或者是因为一个本地应用进程想发起一个连接,或者是因为另一端的主机收到了一个连接请求,它能将MSS设置为外出接口上的MTU长度减去固定的IP首部和TCP首部长度。
TCP的半关闭
- TCP提供了连接的一端在结束它的发送后还能接收来自另一端的数据的能力,这就是所谓的半关闭。
- 如果应用程序不调用close而调用shutdown,且第2个参数值为1,则插口的API支持半关闭。然而,大多数的应用程序通过调用close终止两个方向的连接。
TCP的状态变迁图
- 在这个图中要注意的第一点是一个状态变迁的子集是“典型的”。我们用粗的实线箭头表示正常的客户端状态变迁,用粗的虚线箭头表示正常的服务器状态变迁。
- 第二点是两个导致进入ESTABLISHED状态的变迁对应打开一个连接,而两个导致从ESTABLISHED状态离开的变迁对应关闭一个连接。ESTABLISHED状态是连接双方能够进行双向数据传递的状态。
2MSL等待状态
- TIME_WAIT状态也称为2MSL等待状态。每个具体TCP实现必须选择一个报文段最大生存时间MSL。它是任何报文段被丢弃前在网络内的最长时间。因为TCP报文段以IP数据报在网络内传输,而IP数据报则有限制其生存时间的TTL段。
- RFC 793指出MSL为2分钟。然而,实现中的常用值是30秒,1分钟或2分钟
- 对一个具体实现所给定的MSL值,处理的原则是:当TCP执行一个主动关闭,并发回最后一个ACK,该连接必须在TIME_WAIT状态停留的时间为2倍的MSL。这样可让TCP再次发送最后的ACK以防这个ACK丢失(另一端超时并重发最后的FIN)。
- 这种2MSL等待的另一个结果是这个TCP连接在2MSL等待期间,定义这个连接的插口(客户的IP地址和端口号,服务器的IP地址和端口号)不能再被使用。这个连接只能在2MSL结束后才能再被使用。
- 大多数TCP的实现强加了更为严格的限制,在2MSL等待期间,插口中使用的本地端口在默认情况下不能再被使用。
FIN_WAIT_2状态
- 在FIN_WAIT_2状态我们已经发出了FIN,并且另一端也已对它进行确认。除非我们实行半关闭,否则将等待另一端的应用层意识到它已经收到一个文件结束符说明,并向我们发一个FIN来关闭另一方向的连接。只有当另一端的进程完成这个关闭,我们这端才会从FIN_WAIT_2状态进入TIME_WAIT状态。
复位报文段
- 一般来说,无论何时一个报文段发往基准的连接(referenced connection)出现错误,TCP都会发出一个复位报文段(这里提到的“基准的连接”是指由目的IP地址和目的端口号以及源IP地址和源端口号指明的连接)
- 到不存在的端口的连接请求
- 产生复位的一个常见情况是当连接请求到达时,目的端口没有进程正在听。对于UDP来说,当一个数据报到达目的端口时,该端口没有在使用,它产生一个ICMP端口不可达的信息。而TCP则使用复位。
- 异常终止一个连接对应用程序来说有两个优点:(1) 丢弃任何待发数据并立即发送复位报文段;(2) RST的接收方会区分另一端执行的是异常关闭还是正常关闭
- 检测半打开连接
- 如果一方已经关闭或异常终止连接而另一方却不知道,我们将这样的TCP连接称为半打开(Half-Open)的。任何一端的主机异常都可能导致发生这种情况。只要不打算在半打开连接上传输数据,仍处于连接状态的一方就不会检测另一方已经出现异常。
- 半打开连接的另一个常见原因是当客户主机突然掉电而不是正常的结束客户应用程序后再关机。
同时打开
- 两个应用程序同时执行主动打开的情况是可能的,尽管发生的可能性极小。每一方发送一个SYN,且这些SYN必须传递给对方。这需要每一方使用一个对方熟知的端口作为本地端口。这又称为同时打开。
TCP是特意设计为了可以处理同时打开,对于同时打开它仅建立一条连接而不是两条连接。
一个同时打开的连接需要交换4个报文段,比正常的三次握手多一个。此外,要注意的是我们没有将任何一端称为客户或服务器,因为每一端既是客户又是服务器。
同时关闭
- 双方都执行主动关闭也是可能的,TCP协议运行同时关闭。
当应用层发出关闭命令时,两端均从ESTABLISHED变为FIN_WAIT_1。这将导致双方各发送一个FIN,两个FIN经过网络传送后分别到达另一端。收到FIN后,状态由FIN_WAIT_1变迁到CLOSING,并发送最后的ACK。都收到最后的ACK时,状态变化为TIME_WAIT。如下所示:
同时关闭与正常关闭使用的段交换数目相同。
TCP的交互数据流
经受时延的确认
- 通常TCP在接收到数据时并不立即发送ACK;相反,它推迟发送,以便将ACK与需要沿改方向发送的数据一起发送(有时呈这种现象为捎带ACK)。绝大多数实现采用的时延为200ms,也就是说,TCP将以最大200ms的时延等待是否有数据一起发送。
Nagle算法
- 在局域网上,微小分组通常不会引起麻烦,因为局域网一般不会出现拥塞。但在广域网上,这些小分组则会增加拥塞出现的可能。一种简单和好的方法就是采用Nagle算法。
- 该算法要求一个TCP连接上最多只能有一个未被确认的未完成的小分组,在该分组的确认到达之前不能发送其他的小分组。相反,TCP收集这些少量的分组,并在确认来时以一个分组的方式发出去。该算法的优越之处在于它是自适应的:确认到达得越快,数据也就发送得越快。而在希望减小微小分组数目的低速广域网上,则会发送更少的分组。
TCP的成块数据流
滑动窗口
- 使用可视化的方法来显示滑动窗口协议:
从上图看出,我们将字节从1至11进行标号。接收方通告的窗口称为提出的窗口(offered window),它覆盖了从第4字节到第9字节的区域,表明接收方已经确认了包括第3字节在内的数据,且通告窗口大小为6。窗口大小是与确认序号相对应的。发送方计算它的可用窗口,该窗口表明多少数据可以立即被发送。
- 当接收方确认数据后,这个滑动窗口不时地向右移动。窗口两个边沿得相对运动增加或减少了窗口的大小。我们使用三个术语来描述窗口左右边沿的运动:
- 称窗口左边沿向右边沿靠近为窗口合拢。这种现象发生在数据被发送和确认时
- 当窗口右边沿向右移动时将允许发送更多的数据,我们称之为窗口张开。这种现象发生在另一端的接收进程读取已经确认的数据并释放了TCP的接收缓存时。
- 当右边沿向左移动时,我们称之为窗口收缩。
PUSH标志
- 发送方使用该标志通知接收方将收到的数据全部提交给接收进程。这里的数据包括与PUSH一起传输的数据以及接收方TCP已经为接收进程收到的其他数据。
慢启动算法
- 该算法通过观察到新分组进入网络的速率应该与另一端返回确认的速率相同而进行工作。
- 慢启动为发送方的TCP增加了另一个窗口:拥塞窗口,记为cwnd。当与另一个网络的主机建立TCP连接时,拥塞窗口被初始化为1个报文段(即另一端通告的报文段大小)。当收到一个ACK,拥塞窗口就增加一个报文段(cwnd以字节为单位,但是慢启动以报文段大小为单位进行增加)。发送方取拥塞窗口与通告窗口中的最小值作为发送上限。拥塞窗口是发送方使用的流量控制,而通告窗口则是接收方使用的流量控制。
- 发送方开始时发送一个报文段,然后等待ACK。当收到该ACK时,拥塞窗口从1增加为2,即可以发送两个报文段。当收到这两个报文段的ACK时,拥塞窗口就增加为4。这是一种指数增加的关系。
TCP的超时与重传
引言
- 对每个连接,TCP管理4个不同的定时器:
- 重传定时器使用于当希望收到另一端的确认。
- 坚持定时器使窗口大小信息保持不断流动,即使另一端关闭了其接收窗口
- 保活定时器可检测到一个空闲连接的另一端何时崩溃或重启
- 2MSL定时器测量一个连接处于TIME_WAIT状态的时间
拥塞避免算法
- 拥塞避免算法和慢启动算法需要对每个连接维持两个变量:一个拥塞窗口cwnd和一个慢启动门限ssthresh。这样得到的算法的工作过程如下:
- 对一个给定的连接,初始化cwnd为1个报文段,ssthresh为65535个字节
- TCP输出例程的输出不超过cwnd和接收方通告窗口的大小。拥塞避免是发送方使用的流量控制,而通告窗口则是接收方进行的流量控制。前者是发送方感受到的网络拥塞的估计,而后者则与该连接上的可用缓存大小有关。
- 当拥塞发生时(超时或收到重复确认),ssthresh被设置为当前窗口大小的一半(cwnd和接收方通告窗口大小的最小值,但最少为2个报文段)。此外,如果是超时引起了拥塞,则cwnd被设置为1个报文段(这就是慢启动)。
- 当新的数据被对方确认时,就增加cwnd,但增加的方法依赖于我们是否正在进行慢启动或拥塞避免。如果cwnd小于或等于ssthresh,则正在进行慢启动,否则正在进行拥塞避免。慢启动一直持续到我们回到当拥塞发生时所处的半时候才停止,然后转为执行拥塞避免。
- 慢启动算法初始设置cwnd为1个报文段,此后没收到一个确认加1。这样会使窗口按指数方式增长:发送1个报文段,然后是2个,接着是4个...
- 拥塞避免算法要求每次收到确认时将cwnd增加1/cwnd。与慢启动的指数增加比起来,这是一种加性增长。我们希望在一个往返时间内最多为cwnd增加1个报文段(不管在这个RTT中收到了多少个ACK),然后慢启动根据这个往返时间中所收到的确认的个数增加cwnd.
快速重传与快速恢复算法
- 这个算法通常按如下过程进行实现:
- 当收到第3个重复的ACK时,将ssthresh设置为当前拥塞窗口cwnd的一半。重传丢失的报文段。设置cwnd为ssthresh加上3被的报文段大小。
- 每次收到另一个重复的ACK时,cwnd增加1个报文段大小并发送一个分组(如果新的cwnd允许发送)。
- 当下一个确认新数据的ACK到达时,设置cwnd为ssthresh(在第一步中设置的值)。这个ACK应该是在进行重传后的一个往返时间内对步骤1中的重传的确认。另外,这个ACK也应该是对丢失的分组和收到的第一个重复的ACK之间的所有中间报文段的确认。这一步采用的是拥塞避免,因为当分组丢失时我们将当前的速率减半。
ICMP的差错控制
- TCP能够遇到的最常见的ICMP差错就是源站抑制、主机不可达或网络不可达。
- 当前基于伯克利的实现对这些错误的处理是:
- 一个接收到的源站抑制引起拥塞窗口cwnd被置为1个报文段大小来发起慢启动,但是慢启动门限ssthresh没有变化,所以窗口将打开直至它或者开放了所有通路(受窗口大小和往返时间的限制)或者发生了拥塞。
- 一个接收到主机不可达或网络不可达实际上都被忽略,因为这两个差错都被认为是短暂现象。
重新分组
- 当TCP超时并重传时,它不一定要重传同样的报文段。相反,TCP允许进行重新分组而发送一个较大的报文段,这将有助于提高性能(当然,这个较大的报文段不能够超过接收方声明的MSS)。在协议中这是允许的,因为TCP是使用字节序号而不是报文段序号来进行识别它所要发送的数据和进行确认。