《计算机网络-自顶向下方法》3.5 面向连接的运输:TCP 读书笔记

目录

  • 导语
  • [一、3.5.1 TCP连接](#一、3.5.1 TCP连接)
  • [二、3.5.2 TCP报文段结构](#二、3.5.2 TCP报文段结构)
  • [三、3.5.3 往返时间的估计与超时](#三、3.5.3 往返时间的估计与超时)
  • [四、3.5.4 可靠数据传输](#四、3.5.4 可靠数据传输)
  • [五、3.5.5 流量控制](#五、3.5.5 流量控制)
  • [六、3.5.6 TCP连接管理](#六、3.5.6 TCP连接管理)
  • 本节核心总结(必看)
  • 结语

导语

  大家好呀~经过前面3.1到3.4节的铺垫,我们终于来到了运输层最重磅的一节------TCP(Transmission Control Protocol,传输控制协议)!如果说UDP是一个"甩手掌柜",那TCP就是一个"事无巨细的大管家"------它要管连接建立、管数据可靠、管流量大小、管拥塞避免、管连接释放,几乎把运输层能做的事全包了。

  还记得3.4节我们学的可靠数据传输原理吗?rdt 1.0到rdt 3.0的进化、GBN和SR的流水线协议------那些都是"理论模型"。而TCP,就是这些理论在真实世界中的工程化实现。TCP把序号、确认、重传、超时、滑动窗口这些概念全部落地,还额外加了流量控制和拥塞控制两大杀器。可以说,理解了TCP,就理解了因特网为什么能可靠地传输海量数据。

  这一节内容非常多,是整本书最核心的章节之一。我们会从TCP连接的本质讲起,然后拆解TCP报文段的每一个字段,接着深入RTT估计和超时重传的数学原理,再看TCP如何实现可靠数据传输,然后学习流量控制机制,最后搞懂三次握手和四次挥手的连接管理。准备好了吗?让我们开始这场TCP深度之旅吧~


一、3.5.1 TCP连接

1. TCP是什么?------"先打电话再说话"的协议

  TCP被称为面向连接(connection-oriented)的运输层协议。什么叫"面向连接"?通俗类比:UDP就像寄明信片 ------写好地址直接扔邮筒,不管对方收没收到;而TCP就像打电话------你得先拨号,对方接听了,你们才能开始通话,通话结束后还要挂电话。这个"拨号-通话-挂机"的过程,就是连接的建立、数据传输和释放。

  但要注意,TCP的"连接"并不是像电路交换那样在中间预留了一条专用通路。TCP连接是逻辑上的------它只存在于两端的TCP实体中,中间的路由器对TCP连接一无所知。TCP连接提供的是**全双工(full-duplex)**服务:数据可以在两个方向上同时流动,就像打电话时双方可以同时说话一样。

  TCP连接也是**点对点(point-to-point)**的:一个发送方对应一个接收方,不支持一对多的广播或多播。这和UDP形成对比------UDP可以一个发送方给多个接收方发数据(广播),但TCP只能一对一。

2. TCP连接的组成------套接字对

  一条TCP连接由什么唯一标识呢?答案是套接字对(socket pair),即:

  • 发送方IP地址 + 发送方端口号
  • 接收方IP地址 + 接收方端口号

  这四个数组合在一起,就能在全网唯一确定一条TCP连接。比如你的电脑(IP: 192.168.1.100,端口: 54321)访问百度服务器(IP: 110.242.68.3,端口: 80),这条连接就由(192.168.1.100, 54321, 110.242.68.3, 80)唯一确定。

  这也解释了为什么一个服务器可以同时处理成千上万的客户端连接------虽然服务器的IP和端口固定,但每个客户端的IP和端口不同,所以套接字对不同,连接也就不同。

3. TCP连接的建立过程------三次握手

  TCP连接的建立需要经过三次握手(three-way handshake),我们会在3.5.6节详细讲解。这里先有个概念:客户端先发一个SYN报文,服务器回一个SYN+ACK报文,客户端再发一个ACK报文,连接就建立了。三次握手的目的是同步双方的初始序号,并确认双方都有发送和接收能力。

  连接建立后,双方就可以开始传输数据了。TCP会把应用层交下来的数据看成无结构的字节流(byte stream)------它不关心数据的边界,只负责按序、可靠地把字节流从一端搬到另一端。这就像一条水管,水(数据字节)从一端进去,从另一端出来,TCP保证水不会漏、不会混、顺序不会乱。


二、3.5.2 TCP报文段结构

  TCP传输的数据单元叫做报文段(segment) 。一个TCP报文段由**首部(header)数据(data)**两部分组成。首部长度通常是20字节(没有选项时),最多可以到60字节(有选项时)。下面我们逐字段拆解TCP首部。

1. 源端口号和目的端口号(各16位)

  **源端口号(Source Port Number)目的端口号(Destination Port Number)**各占16位,用于多路复用和多路分解------这和3.2节讲的一样。源端口标识发送方的应用进程,目的端口标识接收方的应用进程。

2. 序号(32位)

  序号(Sequence Number)占32位,是TCP可靠传输的核心。TCP把数据看成字节流,每个字节都有一个编号。报文段的序号就是这个报文段所携带数据的第一个字节的编号

  举个例子:如果一个报文段的序号是500,携带了1000字节数据,那么这个报文段覆盖的字节范围是500~1499,下一个报文段的序号就应该是1500。

  连接建立时,双方会各自选择一个初始序号(Initial Sequence Number,ISN)。注意,初始序号不是从0开始的!而是随机选择的,这是为了防止某些攻击(比如预测序号注入伪造报文)。

3. 确认号(32位)

  确认号(Acknowledgment Number)占32位。TCP使用累积确认(cumulative acknowledgment)------确认号表示"我期望收到的下一个字节的序号",也就是说,确认号之前的所有字节都已经正确收到了。

  比如接收方已经正确收到了字节0~999,那么它会发送确认号1000,表示"0到999我都收到了,接下来给我发1000开始的数据吧"。

  这里有个重要概念:捎带确认(piggybacking)。因为TCP是全双工的,A给B发数据时,B的确认信息可以"搭便车"放在B给A发的数据报文段里一起带过去,不需要单独发一个确认报文。这样可以提高效率。

4. 首部长度(4位)

  **首部长度(Header Length)**占4位,以4字节为单位。因为TCP首部有可变长的选项字段,所以需要这个字段来告诉接收方首部到底有多长。最小值是5(5×4=20字节,无选项),最大值是15(15×4=60字节)。

5. 标志位(6位)

  TCP首部有6个标志位(flag bits),每个位代表一种控制功能:

  • URG(Urgent):紧急指针有效位。表示这个报文段中有紧急数据,需要优先处理。
  • ACK(Acknowledgment):确认号有效位。连接建立后,除了第一个SYN报文,所有报文的ACK都应该置1。
  • PSH(Push):推送位。表示接收方应该立即把数据交给应用层,不要等缓冲区满了再交。
  • RST(Reset):复位位。用于重置一条混乱的连接,或拒绝一个非法的连接请求。
  • SYN(Synchronize):同步序号位。在三次握手中用于同步初始序号。
  • FIN(Finish):结束位。在四次挥手中用于释放连接。

  这6个标志位是TCP连接管理的核心工具。SYN和FIN用于连接的建立和释放,RST用于异常处理,ACK用于确认,URG和PSH用于数据传输的特殊控制。

6. 接收窗口(16位)

  接收窗口(Receive Window)占16位,用于流量控制(flow control)。它表示接收方当前还能接收多少字节的数据,单位是字节。发送方根据这个值来调整自己的发送速率,避免发送太快把接收方的缓冲区撑爆。我们会在3.5.5节详细讲解。

  接收窗口最大为65535字节(2^16-1)。但在高速网络中,这个值可能不够用,所以TCP有一个窗口缩放选项(Window Scale Option),可以把窗口扩大到2^30字节,这就是所谓的"大窗口"或"长肥管道"优化。

7. 校验和(16位)

  校验和(Checksum)占16位,用于检测报文段在传输过程中是否发生了比特差错。计算方法和UDP类似,也需要加上一个伪首部(pseudo header),包含源IP、目的IP、协议号和TCP长度。虽然TCP运行在不可靠的IP之上,但校验和能提供端到端的差错检测。

8. 紧急指针(16位)

  **紧急指针(Urgent Pointer)**占16位,只有当URG标志位为1时才有效。它指向紧急数据的最后一个字节的位置,告诉接收方"从序号开始到序号+紧急指针的位置,这些数据是紧急的,请优先处理"。实际应用中很少用到,但Telnet等交互式应用可能会用它来发送中断信号(比如Ctrl+C)。

9. 选项(可变长)

  **选项(Options)**字段是可变长的,最长40字节。常见的选项有:

  • 最大报文段长度(Maximum Segment Size,MSS):告诉对方我最大能接收的报文段数据部分长度。
  • 窗口缩放(Window Scale):扩大接收窗口的表示范围。
  • 时间戳(Timestamp):用于更精确地计算RTT和防止序号回绕。
  • 选择确认(Selective Acknowledgment,SACK):允许接收方确认乱序到达的报文段,提高重传效率。

  选项字段让TCP具备了良好的可扩展性------新的功能可以通过选项来添加,而不需要改变TCP的基本首部结构。


三、3.5.3 往返时间的估计与超时

  TCP的可靠传输依赖于超时重传 机制:发出去的报文段如果在一定时间内没收到确认,就认为丢了,重新发送。但这个"一定时间"------也就是超时重传时间(Retransmission TimeOut,RTO)------该设多长呢?这是一个非常关键的问题。

1. 为什么不能用固定的超时时间?

  如果RTO设得太短:包可能没丢,只是ACK还在路上,结果白白重传了,浪费带宽。如果RTO设得太长:包真的丢了,要等很久才重传,性能很差。而且网络状况是动态变化的------有时拥堵、有时顺畅,RTT(往返时间)一直在波动。所以TCP必须动态估计RTT,并根据估计值动态调整RTO。

2. 样本RTT(SampleRTT)

  **样本RTT(SampleRTT)**是指:从发送一个报文段到收到它的确认之间所经历的时间。每发一个报文段,就能测一个SampleRTT。但注意,TCP不会对重传的报文段测量SampleRTT------因为你不知道这个ACK是对原始报文的确认还是对重传报文的确认,测出来不准。

3. 估计RTT(EstimatedRTT)

  单个SampleRTT波动太大,不能直接用来设RTO。TCP使用**指数加权移动平均(Exponential Weighted Moving Average,EWMA)**来平滑RTT估计:

复制代码
EstimatedRTT = (1 - α) × EstimatedRTt + α × SampleRTT

  其中α是一个介于0和1之间的系数,推荐值为0.125(即1/8)。这个公式的意思是:新的估计值 = 旧估计值的87.5% + 新样本的12.5%。这样,历史值占大头,新样本只占一小部分,估计值就比较平滑,不会因为单个样本的波动而剧烈变化。

  打个比方:EstimatedRTT就像你对一个城市天气的"印象"------昨天的天气(新样本)会影响你的印象,但之前长期的印象(旧估计)占主导,不会因为一天突然变冷就觉得这个城市一直很冷。

4. RTT偏差(DevRTT)

  光有平均RTT还不够,还需要知道RTT的波动程度。如果RTT很稳定,RTO可以设得接近EstimatedRTT;如果RTT波动很大,RTO就要设得大一些,留足余量。

  TCP用DevRTT来估计RTT的偏差程度:

复制代码
DevRTT = (1 - β) × DevRTT + β × |SampleRTT - EstimatedRTT|

  其中β的推荐值为0.25(即1/4)。DevRTT本质上是SampleRTT与EstimatedRTT之差的绝对值的EWMA,反映了RTT的波动幅度。

5. 超时重传时间(RTO)

  有了EstimatedRTT和DevRTT,RTO的计算公式就很直观了:

复制代码
RTO = EstimatedRTT + 4 × DevRTT

  为什么是4倍DevRTT?这是一个经验值------在大多数网络环境下,RTT的分布近似正态,均值加4倍标准差能覆盖99.99%以上的情况,既不会因为正常波动而误重传,也不会在真正丢包时等太久。

  另外,RTO有一个最小值1秒的限制。即使EstimatedRTT和DevRTT都很小,RTO也不会低于1秒。这是为了防止在局域网等低延迟环境下RTO过小导致大量误重传。

6. Karn算法

  前面提到,TCP不对重传的报文段测量SampleRTT。这就是**Karn算法(Karn's Algorithm)**的核心思想:当发生重传时,不更新RTT估计。因为重传后收到的ACK,你无法确定它是对原始报文的确认还是对重传报文的确认------如果是对原始报文的确认,那SampleRTT会被高估;如果是对重传报文的确认,那SampleRTT会被低估。干脆不测,等下一个正常发送的报文段再测。

  但Karn算法有一个补充:每次重传后,RTO翻倍(也叫指数退避,exponential backoff)。这是因为重传通常意味着网络拥塞,此时应该更加保守,给网络更多恢复时间。这个策略和以太网的CSMA/CD退避机制思想一致。


四、3.5.4 可靠数据传输

  TCP在不可靠的IP层之上提供了可靠的数据传输服务。它综合运用了我们在3.4节学的所有机制:序号、确认、重传、超时、滑动窗口。下面我们看看TCP具体是怎么做的。

1. TCP发送方的事件

  TCP发送方主要响应三类事件:

(1)收到应用层数据:发送方把应用层数据封装成TCP报文段,填上序号(当前未发送的第一个字节的编号),然后发送。同时启动定时器(如果还没启动的话)。

(2)定时器超时:重传导致超时的那个报文段,然后重启定时器。注意,TCP的定时器是针对最早的未确认报文段的,类似GBN的单一定时器。

(3)收到ACK:根据确认号更新发送窗口。如果确认号大于当前的base(最早未确认字节的序号),说明有新的数据被确认了,滑动窗口,更新base。如果还有未确认的数据,重启定时器;如果全部确认了,停止定时器。

2. 快速重传(Fast Retransmit)

  超时重传有一个问题:如果RTO设得比较大(比如1秒以上),丢包后要等很久才重传,性能很差。TCP引入了**快速重传(Fast Retransmit)**机制来解决这个问题。

  快速重传的原理:如果发送方收到了3个重复的ACK (即总共收到4个相同确认号的ACK,第一个是正常的,后面三个是重复的),就认为这个确认号对应的报文段丢了,立即重传,不需要等定时器超时。

  为什么是3个重复ACK?因为重复ACK通常是由乱序到达引起的------接收方收到了后面的报文段,但前面的还没到,就会重复发送对前面缺失报文段的确认。如果只是轻微乱序,可能收到1-2个重复ACK后缺失的报文段就到了;但如果收到3个重复ACK,说明缺失的报文段很可能真的丢了,此时重传比较靠谱。

3. TCP的可靠传输:GBN和SR的混合体

  TCP的可靠传输机制既不是纯粹的GBN,也不是纯粹的SR,而是两者的混合:

  • 像GBN的地方:使用累积确认,发送方只维护一个定时器(针对最早的未确认报文段)。
  • 像SR的地方:接收方会缓存乱序到达的正确报文段,不会丢弃。当缺失的报文段到达后,再按序交给应用层。

  此外,TCP还支持**选择确认(Selective Acknowledgment,SACK)**选项。启用SACK后,接收方可以告诉发送方"我收到了哪些乱序的报文段",这样发送方就可以只重传真正丢失的那些,而不是从缺失处开始全部重传。SACK让TCP更接近SR协议,提高了丢包时的传输效率。

4. 一个具体的例子

  假设发送方发送了报文段A(序号0-999)、B(1000-1999)、C(2000-2999)、D(3000-3999),其中B在传输中丢失了:

  • 接收方收到A,回ACK 1000
  • B丢了,接收方收不到
  • 接收方收到C(乱序),缓存C,回ACK 1000(重复ACK,表示还在等1000开始的数据)
  • 接收方收到D(乱序),缓存D,回ACK 1000(又一个重复ACK)
  • 发送方收到3个重复ACK 1000,触发快速重传,立即重传B
  • 接收方收到B,现在B、C、D都齐了,按序交给应用层,回ACK 4000

  看到了吗?通过快速重传,发送方不需要等定时器超时就能发现丢包并重传,大大提高了性能。而接收方缓存乱序报文段的特性,也避免了C和D被白白重传。


五、3.5.5 流量控制

1. 什么是流量控制?------"别把我撑爆了"

  **流量控制(Flow Control)**是TCP的一项重要服务,目的是让发送方的发送速率不要太快,确保接收方能来得及处理。通俗类比:就像你往一个桶里倒水,如果倒得太快,水就会溢出来。流量控制就是让倒水的人根据桶里还剩多少空间来调整倒水速度。

  注意区分流量控制拥塞控制

  • 流量控制是端到端的问题------发送方太快,接收方处理不过来。
  • 拥塞控制是网络中的问题------发送方太快,中间路由器处理不过来。

  两者目的不同,但都会让发送方降低速率。这一节先讲流量控制,拥塞控制留到3.7节。

2. 接收窗口(rwnd)

  TCP通过**接收窗口(Receive Window,rwnd)**来实现流量控制。接收方在每个TCP报文段的"接收窗口"字段中填入自己当前还能接收的字节数,发送方看到这个值后,就会确保自己已发送但未确认的数据量不超过这个值。

  具体来说,发送方维护以下变量:

  • LastByteSent:最近发送的最后一个字节的序号+1
  • LastByteAcked:最近被确认的最后一个字节的序号+1
  • rwnd:接收方通告的接收窗口大小

  发送方必须保证:LastByteSent - LastByteAcked <= rwnd。也就是说,已发送未确认的数据量不能超过接收窗口。

3. 接收窗口的计算

  接收方维护一个接收缓冲区,应用层从缓冲区中读取数据。接收窗口的大小等于:

复制代码
rwnd = RcvBuffer - [LastByteRcvd - LastByteRead]

  其中:

  • RcvBuffer:接收缓冲区的总大小
  • LastByteRcvd:最近从网络收到的最后一个字节的序号+1
  • LastByteRead:应用层最近读取的最后一个字节的序号+1

  LastByteRcvd - LastByteRead就是缓冲区中已收到但应用层还没读取的数据量。用总缓冲区减去已占用的,就是还能接收的空间,也就是rwnd。

4. 零窗口问题与持续计时器

  如果接收方的缓冲区满了,rwnd就会变成0。发送方收到rwnd=0后,就会停止发送数据,等待接收方重新通告一个非零的窗口。

  但这里有一个潜在的死锁问题:如果接收方后来有了空间,发送了一个rwnd非零的ACK报文,但这个ACK丢了怎么办?发送方还在等非零窗口,接收方以为已经通知了发送方,双方就卡死了。

  TCP用持续计时器(Persistent Timer)来解决这个问题。当发送方收到rwnd=0时,启动持续计时器。计时器超时后,发送方发送一个窗口探测报文段(Window Probe)------这个报文段只携带1个字节的数据,用来探测接收方当前的窗口大小。接收方收到探测报文后,会回复当前的rwnd。如果还是0,就重置持续计时器继续等;如果非零了,就恢复发送。

5. 糊涂窗口综合征(Silly Window Syndrome)

  还有一个经典问题叫糊涂窗口综合征(Silly Window Syndrome,SWS)。想象一下:接收方的缓冲区只剩1个字节了,它通告rwnd=1。发送方收到后,发一个只带1字节数据的报文段(加上20字节首部,总共41字节)。接收方处理了这1字节,又通告rwnd=1,发送方又发1字节......这样每个报文段只传1字节数据,效率极低,就像每次只往桶里倒一滴水。

  解决方法有两个方向:

  • 接收方:不要一有小空间就通告,而是等缓冲区有了足够大的空间(至少达到MSS或缓冲区的一半)再通告。
  • 发送方 :使用Nagle算法(Nagle's Algorithm)------如果发送的数据小于MSS,且还有未确认的数据,就先攒着,等数据攒够一个MSS或者收到确认后再发。

  Nagle算法在减少小报文方面非常有效,但在某些交互式应用(如Telnet、游戏)中可能导致延迟增加,所以这些应用通常会禁用Nagle算法(设置TCP_NODELAY选项)。


六、3.5.6 TCP连接管理

  TCP是面向连接的协议,连接的建立和释放都需要精心设计。这一小节我们学习TCP最著名的两个机制:三次握手 建立连接和四次挥手释放连接。

1. 三次握手------建立连接

  TCP连接的建立需要客户端和服务器之间交换三个报文段,这就是三次握手(Three-Way Handshake)

过程详解:

  第一步(客户端→服务器) :客户端发送一个SYN报文段 (SYN标志位置1),其中包含客户端选择的初始序号client_isn。此时客户端进入SYN_SENT状态。这个报文段的作用是:"服务器你好,我想和你建立连接,我的初始序号是client_isn。"

  第二步(服务器→客户端) :服务器收到SYN后,为该连接分配TCP缓冲区和变量,然后回复一个SYN+ACK报文段(SYN和ACK标志位都置1)。其中:

  • 确认号 = client_isn + 1(表示收到了客户端的SYN)
  • 序号 = server_isn(服务器自己的初始序号)

  此时服务器进入SYN_RCVD状态。这个报文段的作用是:"客户端你好,收到你的请求了,我的初始序号是server_isn,确认你的序号client_isn。"

  第三步(客户端→服务器) :客户端收到SYN+ACK后,也为连接分配缓冲区和变量,然后回复一个ACK报文段 (ACK标志位置1,SYN标志位为0)。确认号 = server_isn + 1。这个报文段可以携带数据,也可以不携带。此时客户端进入ESTABLISHED状态。

  服务器收到这个ACK后,也进入ESTABLISHED状态。连接建立完成,双方可以开始传输数据了。

为什么是三次而不是两次?

  两次握手的问题:假设客户端发了一个SYN,但这个SYN在网络中滞留了,客户端以为它丢了,就重发了一个SYN,建立了连接,传输完数据,释放了连接。这时那个滞留的旧SYN才到达服务器。如果是两次握手,服务器收到旧SYN后就会建立连接并等待数据,但客户端根本没有数据要发,服务器就白白维护了一条无用的连接。三次握手可以避免这个问题------服务器收到旧SYN后回SYN+ACK,但客户端不会回第三个ACK,连接就建立不起来。

  另外,三次握手还能确保双方的初始序号都得到了对方的确认,这是可靠传输的基础。

2. SYN洪泛攻击

  三次握手中有一个安全隐患:服务器在收到SYN后就分配了缓冲区和变量,但此时连接还没完全建立。攻击者可以伪造大量源IP地址,发送大量SYN报文,服务器为每个SYN分配资源,但永远收不到第三个ACK,最终服务器资源耗尽,无法处理正常请求。这就是SYN洪泛攻击(SYN Flood Attack)

  防御方法:SYN Cookie(SYN饼干)。服务器收到SYN后,不立即分配资源,而是根据源IP、目的IP、源端口、目的端口和一个秘密密钥计算出一个cookie,作为初始序号放在SYN+ACK中发回去。如果客户端是真实的,会回复ACK,其中的确认号 = cookie + 1。服务器验证这个cookie有效后,才分配资源建立连接。这样,攻击者不回复ACK,服务器就不分配资源,攻击就失效了。

3. 四次挥手------释放连接

  TCP连接的释放需要四个报文段,这就是四次挥手(Four-Way Handshake)。因为TCP是全双工的,两个方向的数据通道需要分别关闭。

过程详解:

  第一步(客户端→服务器) :客户端发送一个FIN报文段 (FIN标志位置1),表示"我没有数据要发了,我要关闭这个方向的连接"。此时客户端进入FIN_WAIT_1状态。

  第二步(服务器→客户端) :服务器收到FIN后,回复一个ACK报文段 ,确认号 = FIN的序号 + 1。此时服务器进入CLOSE_WAIT 状态,客户端进入FIN_WAIT_2状态。这个ACK只是确认收到了FIN,服务器可能还有数据要发。

  第三步(服务器→客户端) :服务器把剩余的数据发完后,发送一个FIN报文段 ,表示"我也没有数据要发了,我也要关闭这个方向的连接"。此时服务器进入LAST_ACK状态。

  第四步(客户端→服务器) :客户端收到服务器的FIN后,回复一个ACK报文段 ,确认号 = 服务器FIN的序号 + 1。此时客户端进入TIME_WAIT状态。

  服务器收到这个ACK后,进入CLOSED状态,连接释放。客户端在TIME_WAIT状态等待**2MSL(Maximum Segment Lifetime,最大报文段生存时间)**后,也进入CLOSED状态。

4. TIME_WAIT状态------为什么要等2MSL?

  TIME_WAIT状态是TCP连接管理中最容易被问到的问题。客户端在发完最后一个ACK后,为什么不直接关闭,而是要等2MSL呢?有两个原因:

  原因一:确保最后一个ACK能到达服务器。如果客户端发的最后一个ACK丢了,服务器会重发FIN。客户端在TIME_WAIT状态下还能收到这个重发的FIN,然后重新发送ACK。如果客户端直接关闭了,服务器重发FIN后收不到ACK,就无法正常关闭。

  原因二:让旧连接的报文段在网络中消失。MSL是一个报文段在网络中能存活的最长时间。等待2MSL可以确保本次连接的所有报文段都从网络中消失,这样下一次新连接(使用相同的套接字对)就不会收到旧连接的残留报文段,避免混淆。

  MSL的推荐值是2分钟,但实际实现中常用30秒、1分钟等。2MSL就是1~4分钟不等。这也是为什么服务器端经常出现大量TIME_WAIT状态的连接------高并发服务器需要处理大量短连接,每个连接释放后都要等2MSL才能完全释放资源。

5. 同时关闭和同时打开

  TCP还支持一些特殊情况:

  • 同时关闭(Simultaneous Close):双方同时发送FIN,此时四次挥手会变成某种特殊的交换过程,但最终都要经过TIME_WAIT。
  • 同时打开(Simultaneous Open):双方同时向对方发起连接请求,此时会交换四个报文段(而不是三次握手的三个),最终也能建立连接。但这种情况非常罕见。

本节核心总结(必看)

  这一节内容非常丰富,我们用最简洁的方式回顾核心要点:

一、TCP连接

  • TCP是面向连接、全双工、点对点的协议
  • 连接由套接字对(源IP+源端口,目的IP+目的端口)唯一标识
  • TCP把数据看成无结构的字节流

二、TCP报文段结构

  • 首部通常20字节,含源/目的端口、序号、确认号、标志位、接收窗口、校验和等
  • 序号是报文段第一个数据字节的编号
  • 确认号是期望收到的下一个字节的编号(累积确认)
  • 6个标志位:URG、ACK、PSH、RST、SYN、FIN
  • 选项字段支持MSS、窗口缩放、时间戳、SACK等扩展

三、RTT估计与超时

  • EstimatedRTT = (1-α)×EstimatedRTT + α×SampleRTT,α=0.125
  • DevRTT = (1-β)×DevRTT + β×|SampleRTT - EstimatedRTT|,β=0.25
  • RTO = EstimatedRTT + 4×DevRTT,最小1秒
  • Karn算法:重传时不更新RTT估计,且RTO翻倍

四、可靠数据传输

  • 综合运用序号、确认、重传、超时、滑动窗口
  • 快速重传:收到3个重复ACK立即重传,不等超时
  • TCP是GBN和SR的混合体:累积确认+接收方缓存乱序报文
  • SACK选项让TCP更接近SR,只重传真正丢失的报文

五、流量控制

  • 通过接收窗口rwnd控制发送速率,防止接收方缓冲区溢出
  • 持续计时器解决零窗口死锁问题
  • Nagle算法和接收方延迟通告解决糊涂窗口综合征

六、连接管理

  • 三次握手建立连接:SYN → SYN+ACK → ACK
  • 四次挥手释放连接:FIN → ACK → FIN → ACK
  • TIME_WAIT等待2MSL:确保最后ACK到达 + 旧报文段消失
  • SYN Cookie防御SYN洪泛攻击

关键概念清单

  • 面向连接(Connection-Oriented):传输前先建立逻辑连接
  • 全双工(Full-Duplex):数据可双向同时传输
  • 报文段(Segment):TCP的数据传输单元
  • 序号(Sequence Number):字节流编号
  • 累积确认(Cumulative ACK):确认号之前全部收到
  • 快速重传(Fast Retransmit):3个重复ACK触发重传
  • 流量控制(Flow Control):端到端的速率匹配
  • 接收窗口(Receive Window):接收方剩余缓冲区大小
  • 三次握手(Three-Way Handshake):连接建立过程
  • 四次挥手(Four-Way Handshake):连接释放过程
  • TIME_WAIT:等待2MSL的状态
  • SYN洪泛(SYN Flood):拒绝服务攻击

结语

  这一节我们全面学习了TCP------因特网运输层最重要、最复杂的协议。从TCP连接的本质,到报文段的每一个字段,从RTT估计的数学公式,到可靠传输的快速重传机制,从流量控制的接收窗口,到三次握手和四次挥手的连接管理,每一个机制都经过了几十年的打磨和优化。

  TCP的设计哲学值得我们细细品味:它不追求理论上的完美,而是在真实网络环境中不断妥协和优化。比如RTO的计算公式用的是经验值(4倍DevRTT),快速重传用3个重复ACK作为阈值,这些都是在理论和实践之间找到的平衡点。

  但是,TCP还有一个非常重要的话题我们还没讲------拥塞控制。流量控制解决的是"接收方处理不过来"的问题,而拥塞控制解决的是"网络中间路由器处理不过来"的问题。下一节3.6我们将先学习拥塞控制的基本原理,然后3.7节深入TCP的拥塞控制算法(慢启动、拥塞避免、快速恢复)。这部分内容是TCP的灵魂,也是面试高频考点,让我们继续前进吧!

  如果你觉得这篇笔记对你有帮助,欢迎点赞收藏~有问题也欢迎留言讨论,我们一起进步!

相关推荐
草莓熊Lotso1 小时前
【Redis 初阶】特殊数据类型、渐进式遍历与数据库操作生产指南
linux·网络·数据库·redis·tcp/ip·缓存·bootstrap
芯盾时代1 小时前
金融新规全条款合规落地映射的解决方案
网络·网络安全·金融
姚不倒1 小时前
负载均衡架构设计:F5 ↔ Nginx/HAProxy ↔ Keepalived 全链路映射
运维·网络·nginx
好评1242 小时前
【Linux】应用层自定义协议与序列化
linux·运维·网络
春风解人意2 小时前
从零开始学习嵌入式P33----网络基础之HTTP
网络·学习·http
zbyyd2 小时前
Linux 网络编程:IO 多路复用
linux·运维·c语言·网络
SKH.2 小时前
网络(3)TCP通信
网络·网络协议·tcp/ip
μθημα2 小时前
Kubernetes 微服务网络实践:Service 与 Ingress 从入门到灰度发布
网络·微服务·kubernetes
晊晌_h2 小时前
嵌入式从0到精通——Linux 网络通信|TCP、HTTP 网络编程
linux·c语言·网络