Files
notes_estom/计算机网络/3.4 传输层-TCP.md
2023-03-14 20:18:32 +08:00

16 KiB
Raw Permalink Blame History

TCP

参考文献

1 概述

TCP提供的是一种面向连接的、可靠的字节流服务。

  • 面向连接使用TCP的两端在彼此交换数据之前必须先建立一个TCP连接。TCP连接是点对点的在一个TCP连接中仅有两方可以彼此通信TCP不使用广播和多播。TCP的连接和电话网络的连接不同它对中间的转发设备即路由器、交换机是透明的连接的信息只存在于连接的两个端系统之上。
  • 可靠TCP保证数据传输的可靠性。
  • 字节流两个应用程序通过TCP连接交换8bit字节构成的字节流。字节流服务中接收方无法了解发方每次发送了多少字节可以确保的是一端将字节流放到TCP连接上同样的字节流将出现在TCP连接的另一端。另外TCP对字节流的内容不作任何解释。TCP不知道传输的数据字节流是二进制数据还是ASCII字符、EBCDIC字符或者其他类型数据。对字节流的解释由TCP连接双方的应用层解释。
  • TCP提供的是全双工的服务。

2 TCP报文

TCP首部它通常是20个字节。

  • 源端和目的端的端口号用于TCP的多路复用和多路分解即标识发端和收端应用进程。
  • 序号标识字节流。TCP用序号对字节流中的每个字节进行计数一个报文段的序号被设置为该报文段中第一个数据字节的计数值。序号是32bit的无符号数序号到达2321后又从0开始。当建立一个新的连接时SYN标志变1。序号字段包含由这个主机选择的该连接的初始序号ISNInitialSequenceNumber。该主机要发送数据的第一个字节序号为这个ISN加1因为SYN标志消耗了一个序号。由于TCP提供的是全双工的服务即连接双方可以同时独立地发送数据因此连接的每一端必须保持每个方向上的传输数据序号。
  • 确认序号确认序号包含发送确认的一端所期望收到的下一个序号。因此确认序号应当是上次已成功收到数据字节序号加1。只有ACK标志1时确认序号字段才有效。发送ACK无需任何代价因为32bit的确认序号字段和ACK标志一样总是TCP首部的一部分。因此一旦一个连接建立起来这个字段总是被设置ACK标志也总是被设置为1。
  • 首部长度首部中32bit字的数目。这个字段占4bit因此TCP最多有60字节的首部。正常的长度是20字节。
  • 6个标志比特
    • URG紧急指针有效。
    • ACK确认序号有效。
    • PSH接收方应该尽快将这个报文段交给应用层。
    • RST重建连接。
    • SYN同步序号用来发起一个连接。
    • FIN发端完成发送任务。
  • 窗口大小:通告给对端的本段窗口大小。用于流量控制。
  • 检验和整个TCP报文段的校验和覆盖了TCP首部和TCP数据。必须给出由发送端设置接收端验证。类似于UDP的校验和校验和计算是用了和UDP相同的一个伪首部。
  • 紧急指针只有当URG标志置1时紧急指针才有效。紧急指针是一个正的偏移量和序号字段中的值相加表示紧急数据最后一个字节的序号。
  • 选项包括TCP所支持的一些选项。最常见的可选字段是最长报文大小又称为MSS(MaximumSegmentSize)。每个连接方通常都在通信的第一个报文段为建立连接而设置SYN标志的那个段中指明这个选项。它指明本端所能接收的最大长度的报文段。

3 TCP的多路复用和解复用

  • 由于TCP是面向连接的需要目地端口、目地IP、源IP、源端口提供连接两端的足够的信息。TCP使用源IP+源端口+目地IP+目地端口的四元组来标志一个socket。
  • 在TCP工作时无论发送端还是接收端都必须首先建立连接在连接建立完成后socket即拥有了它所需的四元组的信息之后的收发都经过socket进行。

4 TCP的状态迁移

状态迁移图

图中粗的实线箭头表示正常的客户端状态变迁,粗的虚线箭头表示正常的服务器状态变迁。

  • 两个导致进入ESTABLISHED状态的变迁对应打开一个连接。
  • 两个导致从ESTABLISHED状态离开的变迁对应关闭一个连接。ESTABLISHED状态是连接双方能够进行双向数据传递的状态。
  • 图中左下角放在一个虚线框内的4个状态标为“主动关闭”。
  • 状态CLOSE_WAIT和LAST_ACK也用虚线框住并标为“被动关闭”。
  • CLOSED状态不是一个真正的状态而是这个状态图的假想起点和终点。
  • 只有当SYN_RCVD状态是从LISTEN状态正常情况进入而不是从SYN_SENT状态同时打开进入时从SYN_RCVD回到LISTEN的状态变迁才是有效的。这意味着如果我们执行被动关闭进入LISTEN收到一个SYN发送一个带ACK的SYN进入SYN_RCVD然后收到一个RST而不是一个ACK便又回到LISTEN状态并等待另一个连接请求的到来。

状态迁移图说明

  1. 2MSL等待状态

    TCP中有一个报文段最大生存MSLMaximum Segment Lifetime时间的概念。

    TIME_WAIT状态也称为2MSL等待状态。它是指当TCP连接的一方执行主动关闭时在它方完最后一个ACK即对对端FIN的ACK该连接必须在TIME_WAIT状态停留的时间为2倍的MSL。这样的目的是可以让TCP再次发送最后的ACK以防这个ACK丢失另一端超时并重发最后的FIN

    2MSL等待导致当一个TCP连接处于该状态时该连接的socket使用的四元组不能被重新使用即不可能在这个时间段内再重建具有相同四元组的socket。现实中这种限制甚至更为严重--该socket的所使用的本地端口将不能被重新使用。在连接处于2MSL等待时任何迟到的报文段将被丢弃。

    只有主动关闭的一端会进入该状态,被动关闭的一端不会进入该状态。由于这个特性,所以应该尽量让客户端执行主动关闭,服务器执行被动关闭。因为客户端的端口是可以随意选取的,而服务器必须使用为客户端所知道的端口,因而该状态对客户端影响很小,但是如果是服务器进入了该状态就会影响较大。

  2. 平静时间

    2MSL等待状态可以防止将来自一个连接(即四元组相同的连接)的迟到的报文段解释为新连接的一部分。

    但如果进入2MSL等待状态的主机由于故障重启了并且在MSL内完成重启并使用相同的信息建立了一个应该处于2MLS等待状态的连接。这时在故障前从这个连接发出的迟到的报文段会被错误地当作属于重启后新连接的报文段。无论如何选择重启后新连接的初始序号都会发生这种情况。

    为了防止这种情况RFC793指出TCP在重启动后的MSL秒内不能建立任何连接。这就称为平静时间(quiettime)。只有极少的实现版遵守这一原则因为大多数主机重启动的时间都比MSL秒要长。

  3. FIN_WAIT_2状态

    FINWAIT2状态中本端已经发出了FIN并且另一端也已对它进行确认。如果是半关闭则到这一步就可以结束了但是如果不是半关闭则会等待对端发送FIN来关闭另一个方向的数据传输此时对端处于CLOSEWATI状态。但是如果对端不发送FIN本端就一直处于FINWAIT2状态而对端也将处于CLOSEWAIT状态并一直保持直到对端决定进行关闭。

    linux实现通过shutdown来支持半关闭而close则用来执行一个全关闭即期望对端也关闭其在另一个方向的发送。而在在linux实现中如果执行的是全关闭则会设置一个超时间来防止连接在FIN_WAIT_2状态一直等待。

5 TCP的连接管理

建立连接

TCP通过三次握手完成建立连接的工作

  1. 连接发起者发送一个SYN段指明自己期望连接的对端的端口以及初始序号。这个SYN段为报文段1。
  2. 对端发回包含对端的初始序号的SYN报文段报文段2作为应答。同时将确认序号设置为连接发起者的ISN加1以对连接发起者的SYN报文段进行确认。一个SYN将占用一个序号。
  3. 连接发起者必须将确认序号设置为对端的ISN加1以对对端的SYN报文段进行确认报文段3

终止连接

TCP是全双工的因而每个方向的传输都必须单独进行关闭。TCP允许一个方向的传输被关闭而另一个方向的传输不关闭这就是TCP的半关闭。由于TCP支持半关闭因而关闭一个连接需要4次握手。

当连接的一端完成它的数据发送任务后它就可以发送一个FIN来终止这个方向的连接这意味着本端将不会再向对端发送数据。当一端收到一个FIN后它必须通知应用层另一端已经终止了那个方向的数据传送但是这一端仍能发送数据。程序这样做。正常关闭过程

  1. Client发送一个FIN用来关闭Client到Server的数据传送Client进入FIN_WAIT_1状态。

  2. Server收到FIN后发送一个ACK给Client确认序号为收到序号+1与SYN相同一个FIN占用一个序号Server进入CLOSE_WAIT状态。

  3. Server发送一个FIN用来关闭Server到Client的数据传送Server进入LAST_ACK状态。

  4. Client收到FIN后Client进入TIME_WAIT状态接着发送一个ACK给Server确认序号为收到序号+1Server进入CLOSED状态完成四次挥手。

连接建立的超时

由于各种原因连接可能会无法建立。在连接无法建立时TCP不是立即返回失败而是会尝试重新建立连接因为连接无法建立的原因可能是建立连接的请求被丢弃了所以重新尝试是很有必要的。

当建立连接失败时在TCP放弃重试之前它会尝试多次。重试通过定时器实现并且采用了指数后退的方式来确定重试的时间但同时也定义了一个最大的时间限制在这个时间点还没成功的话就会放弃。

同时打开

极少数情况下会出现两个应用程序同时执行主动打开的情况。对于同时打开有一个要求即双方都需要使用为对方所知的端口作为本地端口否则就无法实现同时打开。TCP可以支持同时打开并且对于同时打开其最终建立的是一条连接而不是两条同时打开需要连接双方交换4个报文段比正常的三次握手多一个。

  1. 两端几乎在同时发送SYN并进入SYNSENT状态
  2. 当每一端收到SYN时状态变为SYNRCVD同时它们都再发SYN并对收到的SYN进行确认
  3. 当双方都收到SYN及相应的ACK时状态都变迁为ESTABLISHED

同时关闭

连接的双发也可能在同一时间发送第一个FIN来执行主动关闭。TCP支持同时关闭同时关闭与正常关闭使用的段交换数目相同。

  1. 应用层发出关闭命令连接双发各发送一个FIN并均从ESTABLISHED变为FIN_WAIT_1。
  2. 连接双方收到对端的FIN后状态由FIN_WAIT_1变迁到CLOSING并发送最后的ACK。
  3. 当收到最后的ACK时状态变化为TIMEWAIT

复位连接

RST标志表示复位用来复位连接。

  1. TCP会在自己认为的异常时刻发送RST报文段复位连接。一种常见情况是当连接请求到达时目的端口没有进程正在听。另一种场景是尝试在半打开的连接上发送数据。

  2. 终止一个连接。终止一个连接的正常方式是一方发送FIN这种方式被称为有序释放。在这种方式中FIN在所有排队数据都发送后才会被发送一般不会有任何数据丢失。但是连接的一方也可以选择通过RST来释放一个连接这被称为异常释放。异常终止一个连接对应用程序来说有两个优点

    • 直接丢弃任何待发数据并立即发送复位报文段无需ACK对RST报文段进行确认。
    • RST的接收方会区分另一端执行的是异常关闭还是正常关闭。并可以通知应用程序。

TimeWait状态

https://zhuanlan.zhihu.com/p/523678245

主动关闭放

如果我们来做个类比的话TIME_WAIT的出现对应的是你的程序里的异常处理它的出现就是为了解决网络的丢包和网络不稳定所带来的其他问题

第一,防止前一个连接【五元组,我们继续以 180.172.35.150:45678, tcp, 180.97.33.108:80 为例】上延迟的数据包或者丢失重传的数据包被后面复用的连接【前一个连接关闭后此时你再次访问百度新的连接可能还是由180.172.35.150:45678, tcp, 180.97.33.108:80 这个五元组来表示也就是源端口凑巧还是45678】错误的接收异常数据丢了或者传输太慢了参见下图

SEQ=3的数据包丢失重传第一次没有得到ACK确认 如果没有TIME_WAIT或者TIME_WAIT时间非常端那么关闭的连接【180.172.35.150:45678, tcp, 180.97.33.108:80 的状态变为了CLOSED源端口可被再次利用】马上被重用【对180.97.33.108:80新建的连接复用了之前的随机端口45678】并连续发送SEQ=1,2 的数据包 此时前面的连接上的SEQ=3的数据包再次重传同时seq的序号刚好也是3这个很重要不然SEQ的序号对不上就会RST掉此时前面一个连接上的数据被后面的一个连接错误的接收 第二,确保连接方能在时间范围内,关闭自己的连接。其实,也是因为丢包造成的,参见下图:

主动关闭方关闭了连接发送了FIN 被动关闭方回复ACK同时也执行关闭动作发送FIN包此时被动关闭的一方进入LAST_ACK状态 主动关闭的一方回去了ACK主动关闭一方进入TIME_WAIT状态 但是最后的ACK丢失被动关闭的一方还继续停留在LAST_ACK状态 此时如果没有TIME_WAIT的存在或者说停留在TIME_WAIT上的时间很短则主动关闭的一方很快就进入了CLOSED状态也即是说如果此时新建一个连接源随机端口如果被复用在connect发送SYN包后由于被动方仍认为这条连接【五元组】还在等待ACK但是却收到了SYN则被动方会回复RST 造成主动创建连接的一方由于收到了RST则连接无法成功 所以你看到了TIME_WAIT的存在是很重要的如果强制忽略TIME_WAIT还是有很高的机率造成数据粗乱或者短暂性的连接失败。

CloseWait

被动关闭方。

CLOSE_WAIT才可怕因为CLOSE_WAIT很多表示说要么是你的应用程序写的有问题没有合适的关闭socket要么是说你的服务器CPU处理不过来CPU太忙或者你的应用程序一直睡眠到其它地方(锁或者文件I/O等等)你的应用程序获得不到合适的调度时间造成你的程序没法真正的执行close操作。

关于主动关闭和被动关闭的讨论

请求的发起方和接收方都可能主动关闭或者被动关闭。与请求发出方和接收方无关。

例如服务器响应超时客户端等不及了会触发超时时间主动关闭此时虽然服务器收到了fin包并且协议层可以返回ack进行确认进入了closewait状态。但是服务器因为还在处理超时的任务肯定要继续等待任务处理完成才会调用close函数主动关闭。