ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

传输层TCP协议

传输层TCP协议 TCP 全称为传输控制协议( Transmission Control Protocol ). 要对数据的传输进⾏⼀个详细的控制.TCP协议段格式TCP报文 TCP头部报头TCP数据段上图就是一个报文源/目的端口号:同UDP的报头数据表示数据是从哪个进程来到哪个进程去。4位TCP报头长度: 表示该TCP头部有多少个32位bit(有多少个4字节)。4位→0000~1111二进制→0~15十进制所以TCP头部最大长度是15×460字节15×4 60 字节15×460字节上半部分是20字节则选项大小范围是0~40字节TCP头部是可变长的。URG紧急指针是否有效ACK确认号是否有效PSH提示接收端应用程序立刻从TCP缓冲区把数据读走RST对方要求重新建立连接携带RST标识的称为复位报文段SYN请求建立连接携带SYN标识的称为同步报文段FIN通知对方本端要关闭了携带FIN标识的称为结束报文段URG标志位URG1表示 TCP 首部中的 16 位紧急指针字段有效其标识的紧急数据会被优先读取。紧急指针是偏移量用来标记紧急数据的结束位置。紧急数据的最后一个字节的序号 本报文段序号 紧急指针的值偏移量紧急数据的范围 [本报文段序号本报文段序号 紧急指针的值偏移量]例子本报文段序号seq 200紧急指针 8那么紧急数据最后一个字节的序号 200 8 - 1 207序号 200207 这 8 个字节是紧急数据。在 TCP 中URGUrgent标志位和带外数据Out-of-Band Data, OOB是一对紧密关联的概念用于处理紧急数据的优先交付。URG 标志位当 TCP 报头中URG 1时表示该报文段中包含紧急数据需要优先处理。紧急指针Urgent Pointer一个16位的字段指向紧急数据的最后一个字节的序号用于界定紧急数据的范围。带外数据OOB指那些需要绕过正常的数据流被应用程序优先接收和处理的数据。它并非真正独立的通道而是嵌入在普通数据流中的“标记”。工作机制发送端应用程序调用send(sock, data, len, MSG_OOB)发送带外数据。TCP 协议栈会构造一个报文段设置URG 1并将紧急指针指向该数据的最后一个字节。这个带外数据会被插入到正常的数据流中但被标记为紧急。接收端当 TCP 收到URG 1的报文段时会触发一个SIGURG信号如果应用程序已注册通知应用程序有紧急数据到达。应用程序调用recv(sock, buf, len, MSG_OOB)来读取这个紧急数据。读取后紧急数据会从数据流中被“剥离”应用程序继续按顺序处理剩余的普通数据。代码演示发送端constcharoob_dataX;// 发送1字节的带外数据send(sockfd,oob_data,1,MSG_OOB);接收端charoob_buf;recv(sockfd,oob_buf,1,MSG_OOB);// 读取带外数据printf(Received OOB data: %c\n,oob_buf);总结URG 标志位是 TCP 标记紧急数据的方式而 OOB 读取是应用程序从数据流中提取并优先处理这些紧急数据的手段。确认应答(ACK)机制TCP将每个字节的数据都进行了编号——即为序列号这里收到的确认序号 发送数据时TCP报头的序号 1并且确认应答的TCP报头中标志位ACK标记为1——说明是ACK类型的报头32位应答确认序号 32位序号1——说明之前序号的报文都已经送达而下一次报文中新的序号 应答确认序号 要发送的长度对于面向字节流的理解缓冲区就是一个char类型的数组这样里面存的数据每个字节天然都有一个序号数组下标。捎带应答捎带应答Piggybacking ACK是 TCP 里一个很实用的优化手段核心是在发送自己数据的时候顺便把对对方数据的确认应答ACK一起捎过去而不是单独发一个空的 ACK 确认应答包。主机 A 发数据给主机 B主机 B 收到后需要回 ACK同时也可能有自己的数据要发给 A如果每次都单独发 ACK确认应答会产生很多小包浪费带宽和网络资源。捎带应答就是把这两件事合并在 B 发给 A 的数据报文里顺便带上对 A 数据的确认号这样一个包就完成了两件事。既要确认应答又要发送数据——那就合并成一个打包发送过去。在这个过程中32位序号与32位确认序号都要使用32位确认序号为了确认应答使用32位序号为了发送数据使用确认序号与序号同时存在的意义向对方发送数据的同时也在做应答。为什么tcp包头中要有标志位标志位存在的意义区分报文类型超时重传机制一台主机向另一台主机发送数据的时候没有收到应答该怎么办对方主机进行等待在一个时间段内没有收到应答——超时然后判断超时——进行重传。主机 client 发送数据给 server 之后, 可能因为⽹络拥堵等原因, 数据⽆法到达主机 server 如果主机client 在⼀个特定时间间隔内没有收到B发来的确认应答, 就会进行重发但是, 主机A未收到B发来的确认应答, 也可能是因为ACK丢失了在第二种情况中主机B会收到很多重复数据ACK丢失导致发送了很多次。那么TCP协议需要能够识别出那些包是重复的包并且把重复的丢弃掉。这时候我们可以利用前面提到的序列号, 就可以很容易做到去重的效果。超时的时间怎么确定因为网络是波动的所以这个超时时间是动态变化的。TCP为了保证无论在任何环境下都能比较高性能的通信, 因此会动态计算这个最大超时时间。Linux中(BSD Unix和Windows也是如此), 超时以500ms为⼀个单位进行控制, 每次判定超时重发的超时时间都是500ms的整数倍。如果重发⼀次之后, 仍然得不到应答, 等待 2*500ms 后再进行重传。如果仍然得不到应答, 等待 4*500ms 进行重传. 依次类推, 以指数形式递增。累计到⼀定的重传次数, TCP认为⽹络或者对端主机出现异常, 强制关闭连接。连接管理机制在正常情况下, TCP要经过三次握手建立连接, 四次挥⼿断开连接。服务端状态转化:[CLOSED - LISTEN]服务器端调用listen后进入LISTEN状态, 等待客户端连接;[LISTEN - SYN_RCVD]⼀旦监听到连接请求(同步报文段), 就将该连接放入内核等待队列中, 并向客户端发送SYN确认报文.[SYN_RCVD - ESTABLISHED]服务端⼀旦收到客户端的确认报文, 就进入ESTABLISHED状态, 可以进行读写数据了.[ESTABLISHED - CLOSE_WAIT]当客户端主动关闭连接(调用close), 服务器会收到结束报文段,服务器返回确认报文段并进入CLOSE_WAIT;[CLOSE_WAIT - LAST_ACK]进入CLOSE_WAIT后说明服务器准备关闭连接(需要处理完之前的数据); 当服务器真正调⽤close关闭连接时, 会向客户端发送FIN, 此时服务器进⼊LAST_ACK状态, 等待最后⼀个ACK到来(这个ACK是客户端确认收到了FIN)[LAST_ACK - CLOSED]服务器收到了对FIN的ACK, 彻底关闭连接.客户端状态转化[CLOSED - SYN_SENT]客户端调用connect, 发送同步报文段;[SYN_SENT - ESTABLISHED]connect调用成功, 则进入ESTABLISHED状态, 开始读写数据;[ESTABLISHED - FIN_WAIT_1]客户端主动调用close时, 向服务器发送结束报文段, 同时进入FIN_WAIT_1;[FIN_WAIT_1 - FIN_WAIT_2]客户端收到服务器对结束报文段的确认, 则进⼊FIN_WAIT_2, 开始等待服务器的结束报文段;[FIN_WAIT_2 - TIME_WAIT]客户端收到服务器发来的结束报文段, 进⼊TIME_WAIT, 并发出LAST_ACK;[TIME_WAIT - CLOSED]客户端要等待⼀个2MSL(Max Segment Life, 报文最大生存时间)的时间, 才会进入CLOSED状态.三次握手TCP三次挥手目的是建立连接如何理解连接管理先描述在组织→内核数据结构维护连接需要成本的→时间和空间的成本注意发送的仍然是TCP报头只是报头中SYN或者ACK标志位设立为1或者0.标准三次握手流程就是C → SSYN我要连你我的初始序号是 XS → CSYNACK同意连接我的初始序号是 Y确认你的序号 X1——捎带应答C → SACK确认你的序号 Y1连接建立具体来说第一次握手SYN客户端向服务端发送一个 SYN 报文表明自己要发起连接并携带自己的初始序号ISN。客户端进入SYN_SENT状态。第二次握手SYNACK服务端收到 SYN 后回复一个 SYNACK 报文。其中 ACK 确认客户端的序号SYN 则携带自己的初始序号。服务端进入SYN_RCVD状态。第三次握手ACK客户端收到 SYNACK 后回复一个 ACK 报文确认服务端的序号。客户端进入ESTABLISHED状态服务端收到 ACK 后也进入ESTABLISHED状态连接正式建立。三次握手的本质是四次握手只不过中间的两次被捎带应答了。为什么建立连接是三次握手理由一需要保证网络信道是健康的确认全双工信道是通的三次挥手主机双方都会有确定的一次收发确认全双工。三次握手正好完成双向收发确认C 发 SYN → S 收到⇒ 证明C 能发S 能收S 发 SYNACK → C 收到⇒ 证明S 能发C 能收C 发 ACK → S 收到⇒ 再次确认C 能发S 能收理由二确认双方 OS / 协议栈正常、愿意通信。为什么不是一次两次核心原因之一是 SYN 洪水三次握手 天然防 SYN 洪水一两次握手 直接敞开大门。SYN洪水攻击攻击者疯狂发 SYN不回最后的 ACK结果是服务端一收到 SYN就分配资源、开半连接队列 → 队列塞满 → 正常用户连不上。如果是 两次握手客户端发 SYN服务端回 SYNACK双方直接认为连接已建立致命问题服务端只要收到一个 SYN就必须立刻分配资源攻击者一秒发 10w 个伪造 SYN →服务端一秒开 10w 个连接 →内存爆、队列满、服务直接宕机。三次握手为什么能抵御 SYN 洪水因为服务端只有在收到第三次 ACK 后才真正把连接变成 “已建立”在三次握手里客户端发 SYN → 服务端进入 SYN_RCVD半连接服务端回 SYNACK必须等客户端回 ACK才正式建立连接、分配完整资源这就带来两个天然防御伪造 IP 根本收不到 SYNACK永远发不出 ACK服务端不会给 “只发 SYN 不回 ACK” 的假连接长期占资源四次挥手TCP四次挥手的目的是断开连接TCP 是全双工两边要各自关各自的发送通道不能一起关所以必须四次。所以标准流程就是C → SFIN我发完了关我→你方向S → CACK收到你关了S → CFIN我也发完了关你←我方向C → SACK收到你关了注意4次挥手是双方OS自动完成的。ACK 和 FIN 不能合并因为收到 FIN 时本机可能还有数据没发完不能马上关。极端情况下可能收到FIN时本机数据刚好发完此时ACK就可以和FIN合并。——TCP 挥手从四次变成三次更常见情况客户端先调用 close() 发送 FIN服务端回 ACK 确认此时服务端若还有未发完的数据可继续向客户端发送客户端仅能回应 ACK等服务端数据发完再调用 close() 发送 FIN客户端最后回 ACK 确认双方完成全双工通道的独立关闭。关于单向断连shutdown() 正是为 TCP 「全双工」特性设计的单向关闭函数和 close() 有本质区别。核心结论shutdown(sock, SHUT_WR)只关闭发送方向我不发了但还能收shutdown(sock, SHUT_RD)只关闭接收方向我不收了但还能发shutdown(sock, SHUT_RDWR)双向关闭等价于 close() 核心效果操作核心差异对应场景shutdown(sock, SHUT_WR)仅关闭单向通道socket 还在四次挥手中 “先关发送、仍能收”close()双向关闭释放 socket 资源数据收发都完成后彻底关闭理解TIME_WAIT状态正常流程中在4次挥手的后两次中服务端被动关闭方向客户端发送FIN进入LAST_ACK状态客户端收到后发送响应ACK立即进入TIME_WAIT状态进行等待等待时长固定为2MSL服务端收到ACK后进入CLOSED状态服务端没收到 ACK的两种情况情况 1服务端的 FIN 丢包 → 客户端没收到仍停留在FIN_WAIT_2不会进TIME_WAIT情况 2客户端的 ACK 丢包 → 服务端超时后重传 FIN有限次数客户端在TIME_WAIT期间收到重传的 FIN会重新发 ACK客户端等待2MSL后确认无重传FIN进入CLOSED状态。TIME_WAIT 核心规则TCP 协议中主动关闭连接的一方会进入TIME_WAIT 状态需等待 **2 个 MSL报文最大生存时间**后才会完全关闭CLOSED 状态期间该端口无法被重新监听。TIME_WAIT副作用占用端口 / 文件描述符短时间内大量 TIME_WAIT 会导致端口耗尽例子首先启动 server ,然后启动 client ,然后用 Ctrl-C 使 server 终止,这时马上再运行 server , 结果是sybVM-8-5-ubuntu:~/codedir/Linuxweb/4.tcp_echo_server$ ./tcpserver8888[2026-3-612:42:11][Debug][pid:3088483][TcpServer.hpp][57]socket create success, sockfd is:3[2026-3-612:42:11][Fatal][pid:3088483][TcpServer.hpp][69]bind error, Address alreadyinuse端口号绑定失败而且会报 Address already in use 错误。这是因为,虽然server的应用程序终止了,但TCP协议层的连接并没有完全断开,因此不能再次监听同样的server端口场景触发原因用 Ctrl-C 终止 server 时server 成为主动关闭连接的一方因此其监听端口会因 TIME_WAIT 被占用短期内无法复用。注意MSL 实际取值RFC1122 规范 MSL 为 2 分钟但 Centos7/Ubuntu 等系统默认配置为 60 秒可通过cat/proc/sys/net/ipv4/tcp_fin_timeout命令查看当前系统的 MSL 实际值。为什么是 TIME_WAIT 的时间是 2MSL?TIME_WAIT 设为2MSL核心就两个目的确保都关闭,给重传留时间2MSL 1MSL最后一个 ACK 的去程 1MSL重传 FIN 的回程t 0 主动关闭方发出最后一个 ACKt ≤ 1 MSL ACK 到达被动关闭方如果没丢对方就此关闭完事但如果 ACK 丢了→ 被动方发现没收到 ACK会【重传 FIN】t ≤ 2 MSL 重传的 FIN 从被动方回到主动方需要最多 1 MSL2 MSL 之后 无论是 ACK 还是重传的 FIN都已在网络中彻底消失第二个目的2MSL 之后这条连接的所有旧报文都死透了四元组源 IP、源端口、目的 IP、目的端口才能安全复用于新连接不会出现新连接的包被旧连接的残留数据污染。解决TIME_WAIT状态引起的bind失败的方法客户端进入 TIME_WAIT 后对应的端口会被占用 2MSL 时间约 1-4 分钟此时如果新进程想复用这个端口绑定比如快速重启客户端会报 Address already in use 错误。使用setsockopt ()设置 socket 描述符的 选项 SO_REUSEADDR 为 1 , 表示允许在同一IP端口上已经存在 TIME_WAIT 状态的 socket 时新 socket 仍然能 bind——也就是旧连接还没走完 2MSL新进程可以先占上这个坑。流量控制接收端处理数据的速度是有限的。如果发送端发的太快导致接收端的缓冲区被打满这个时候如果发送端继续发送, 就会造成丢包继而引起丢包重传等等⼀系列连锁反应。因此TCP支持根据接收端的处理能力, 来决定发送端的发送速度. 这个机制就叫做流量控制(Flow Control)接收端将自己可以接收的缓冲区剩余空间大小放入 TCP 首部中的 “窗口大小” 字段通过ACK 通知发送端确认应答机制窗口大小字段越大说明网络的吞吐量越高接收端一旦发现自己的缓冲区快满了就会将窗口大小设置成一个更小的值通知给发送端发送端接收到这个窗口之后就会减慢自己的发送速度如果接收端缓冲区满了就会将窗口置为 0这时发送方不再发送数据但是需要定期发送一个窗口探测数据段使接收端把窗口大小告诉发送端。滑动窗口对每⼀个发送的数据段, 都要给⼀个ACK确认应答收到ACK后再发送下一个数据段. 这样做有⼀个比较大的缺点, 就是性能较差尤其是数据往返的时间较长的时候。既然这样⼀发⼀收的方式性能较低那么我们⼀次发送多条数据就可以大大地提高性能(其实是将多个段的等待时间重叠在⼀起了)。窗口大小指的是无需等待确认应答而可以继续发送数据的最大值。上图的窗口大小就是4000个字节四个段。发送前四个段的时候不需要等待任何ACK直接发送收到第一个ACK后滑动窗口向后移动继续发送第五个段的数据依次类推操作系统内核为了维护这个滑动窗口需要开辟发送缓冲区来记录当前还有哪些数据没有应答只有确认应答过的数据才能从缓冲区删掉窗口越大则网络的吞吐率就越高。如何理解这个滑动窗口呢不考虑网络情况滑动窗口大小一般是对方缓冲区剩余空间的大小实际上考虑网络还有阻塞窗口的影响只能向右滑动吗是的可以变大变小吗是的窗口变大接收方空闲缓冲区多 → 告诉发送方你可以多发点窗口变小接收方处理不过来 → 告诉发送方少发点可以为0吗可以证明对方没有接收缓冲区了或者暂时不能再接收任何数据。丢包重传问题那么如果出现了丢包, 如何进行重传?核心结论是在滑动窗口内任何问题最终都会转化为最左侧丢包的问题。最左侧报文丢失如果窗口最左边的报文丢失接收方无法确认任何后续数据1.会一直返回对该丢失报文的重复 ACK触发快重传2.发送方等待 ACK 超时触发超时重传。发送方必须重传该报文及之后所有未被确认的数据。中间报文丢失即使中间的报文丢失前面的报文被正常接收滑动窗口的左侧移动至中间报文丢失的地方这在效果上等同于最左侧报文丢失。最右侧报文丢失如果只是窗口最右侧的报文丢失接收方可以确认到丢失报文之前的所有数据窗口可以正常右滑只需要重传丢失的那一个报文。这在效果上等同于最左侧报文丢失。以左侧报文丢失为例丢失分为两种情况情况一数据包已经到达ACK被丢失了32位确认序号确认序号之前的报文全部收到了TCP 的 ACK 不是 “确认这个包”而是 “确认到这个序号之前的所有字节都已收到”。所以只要后面的 ACK 能顺利到达前面的 ACK 丢了也没关系。发送方看到一个更大的 ACK 序号就知道前面的所有数据都已经被接收了。情况1中间某个 ACK 丢失了如上图比如发送方发了1-1000、1001-2000、2001-3000接收方依次返回ACK1001、ACK2001、ACK3001但 ACK2001 在传输中丢失了结果发送方收到 ACK1001然后直接收到 ACK3001它会立刻明白1~3000 字节都已经被接收了窗口直接滑到 3001完全不需要重传任何数据结论中间的 ACK 丢了无所谓靠后面的 ACK 就能 “带过” 确认。情况2最后一个报文的 ACK 丢失了这里要分两种子情况子情况 2.1发送方已经关闭连接FIN 之后如果这是连接关闭前的最后一个数据报文且它的 ACK 丢失了发送方会超时重传这个 FIN 或数据报文接收方收到后会再次返回 ACK发送方收到 ACK 后才会正式关闭连接子情况 2.2连接还在进行中只是数据流的最后一个包超时重传兜底发送方发出最后一个包后会启动超时重传定时器。如果在超时时间内没有收到 ACK发送方会认为该包未被接收重新发送这个数据包并再次等待 ACK。接收方收到重传的包后会发现它已经处理过于是再次返回 ACK。注捎带 ACK 优化如果接收方在超时之前有新的应用层数据要发送给发送方它会在这个新的数据包中捎带对“最后一个包”的确认 ACK。发送方收到这个捎带了 ACK 的新数据包后就知道“最后一个包”已经被成功接收无需再等待或重传。情况二数据包直接丢了初始状态发送端 A 连续发送了1~1000,1001~2000,2001~3000, …,7001~8000等多个报文段。其中1001~2000这个报文段在传输过程中丢失了图中用 X 标记。接收端 B 的反应接收端 B 首先收到1~1000返回确认ACK1001表示期望下一个字节是 1001。接着接收端 B 收到了2001~3000。但它期望的是 1001所以这是一个失序报文。它会立即返回一个重复确认ACK1001意思是“我还在等 1001 字节的数据”。随后接收端 B 又陆续收到了3001~4000,4001~5000,5001~6000,6001~7000。每收到一个它都会返回一个ACK1001。发送端 A 的反应发送端 A 连续收到了3 个ACK1001。触发快重传机制而不是超时重传机制它判断出1001~2000这个报文段已经丢失。发送端 A 不再等待超时定时器立即重传1001~2000这个报文段。接收端 B 再次反应接收端 B 收到了重传的1001~2000。此时它检查自己的接收缓冲区发现之前已经收到了2001~7000的所有数据只是因为1001~2000缺失所以一直无法确认。现在1001~2000补齐了所有数据从 1 到 7000 都连续了。因此接收端 B 返回一个新的确认ACK7001表示“我已经收到了 1~7000 字节的数据下一个期望是 7001”。发送端 A 连续收到了三个ACK包并且确认序号都是相同的1001直接触发快重传机制而不是一直等待到超时触发超时重传机制。TCP 快重传Fast Retransmit机制详解快重传是 TCP 协议中用于快速恢复丢包的关键机制它通过接收端的重复确认让发送端无需等待超时就能立即重传丢失的报文段从而大幅提升传输效率。核心原理用重复 ACK 代替超时等待在传统的超时重传机制中发送端需要等待一个超时定时器通常几百毫秒才能判断丢包并重传。而快重传则利用了接收端的行为接收端的规则每当收到一个失序的报文段时它会立即发出一个对最后一个连续字节的重复确认Duplicate ACK。发送端的规则当连续收到3 个相同的重复确认时就可以断定对应的报文段已经丢失无需等待超时立即重传。机制触发条件核心作用优势快重传连续 3 次重复 ACK立即重传丢失报文无需等待超时恢复更快超时重传超时定时器到期重传丢失报文最基础的保障几点补充滑动窗口是流量控制的具体实现缓冲区实际上是一个环形链表滑动窗口在上面就不会越界 ——既避免越界又能高效复用内存。阻塞控制虽然TCP有了滑动窗口这个大杀器能够高效可靠地发送大量的数据。但是如果在刚开始阶段就发送大量的数据仍然可能引发问题。因为网络上有很多的计算机可能当前的网络状态就已经比较拥堵。在不清楚当前网络状态下贸然发送大量的数据是很有可能引起雪上加霜的。TCP引入慢启动机制先发少量的数据探探路摸清当前的网络拥堵状态再决定按照多大的速度传输数据。前期发数据慢但是增幅快——有应答后检测到网络不错然后把量增上去保证效率。像上面这样的拥塞窗口增长速度是指数级别的。“慢启动” 只是指初始时慢但是增长速度非常快。注意为了避免增长得过快因此不能让拥塞窗口单纯地加倍此处引入一个叫做慢启动阈值的参数当拥塞窗口超过这个阈值时不再按照指数方式增长而是按照线性方式增长。ssthresh是一个阈值在这个阈值之前拥塞窗口大小是指数增长超过这个阈值就变成线性增长。完整流程TCP 刚启动时慢启动阈值 ssthresh 最大窗口值一般就是对方通告的窗口。发送开始时定义拥塞窗口大小为 1。慢启动阶段cwnd ssthresh「每收到 1 个 ACK拥塞窗口cwnd1」→ 宏观上每轮 RTT 翻倍指数增长拥塞避免阶段cwnd ≥ ssthresh「每轮 RTT 只 1」→ 线性增长出现超时重传ssthresh 当前 cwnd/2cwnd 重置为 1重新慢启动。刚开始通信吞吐量慢慢上升发生拥塞吞吐量会下降。注RTTRound-Trip Time往返时间就是「主机A发一个 TCP 包给主机B」→「主机B收到后回一个 ACK 确认包」→「主机A收到这个 ACK」整个过程的总耗时。每次发送数据包时将拥塞窗口和接收端主机反馈的窗口大小做比较取较小的值作为实际发送的窗口。滑动窗口min(接收方的接收能力,拥塞窗口)滑动窗口 min(接收方的接收能力, 拥塞窗口)滑动窗口min(接收方的接收能力,拥塞窗口)延迟应答延迟应答是 TCP 接收端为了优化传输效率而采用的一种机制核心思想是不立即对每个报文段进行确认而是等待一段时间或累积一定数量的报文段后再发送 ACK。如下图就是每两个报文应答一次为什么需要延迟应答提升窗口大小接收端处理数据速度很快如果立即应答返回的窗口大小会偏小如缓冲区 1M收到 500K 后立即应答窗口仅为 500K。延迟一段时间如 200ms后接收端已处理完数据缓冲区重新空闲返回的窗口可以更大如 1M从而允许发送端发送更多数据提升整体传输效率。触发条件数量 时间限制为了避免 ACK 延迟太久导致发送端超时重传延迟应答有严格的触发规则数量限制每隔 N 个报文段就必须应答一次通常 N2。时间限制如果超过最大延迟时间通常为 200ms无论是否收到足够数量的报文段都必须应答。粘包问题粘包问题中的“包”是应用层数据包而非 TCP 传输层的报文段。粘包的本质原因TCP 是面向字节流的协议而非“面向报文”UDP 是具体表现为TCP 协议头只有“序号”字段保证字节有序没有“报文长度”字段无法标识应用层包边界传输层视角TCP 会把应用层数据拆/拼为报文段传输接收端按序号将字节存到缓冲区保证字节连续且有序应用层视角从缓冲区读取数据时看到的是“一串无边界的连续字节”无法区分“哪段字节属于一个完整的应用层包”——这就是粘包。简单说TCP 只保证字节传对、传全但不帮应用层划分“业务包”的边界边界需要应用层自己定义。粘包问题的典型场景粘包可能是“多包粘成一段”也可能是“一包拆成多段”发送方连续发多个小应用包TCP 为了效率会合并成一个报文段发送 → 应用层读取时多个包粘在一起接收方缓冲区数据未及时读取后续新数据写入后多个包的字节混在一起大应用包被 TCP 拆分成多个报文段传输应用层第一次读取时只拿到部分字节出现“半包”。解决粘包问题的核心思路与具体方案核心原则在应用层明确两个数据包之间的边界具体分 3 类方案覆盖所有场景方案类型核心逻辑适用场景优点注意事项定长包方案约定应用层包的固定字节长度包大小固定的场景如固定结构的指令实现最简单读取逻辑单一若数据不足需补占位符浪费带宽长度字段方案包头预留固定长度的“总长度”字段变长包绝大多数业务场景灵活适配所有变长数据需约定长度字段的字节数如 2/4 字节且长度要包含包头分隔符方案包末尾添加唯一分隔符如\r\n、特殊字符文本类数据如HTTP、自定义协议直观易调试需保证分隔符不与正文冲突需处理“分隔符被拆分”的情况我已经帮你把这段关于TCP异常情况的文本中的异常字符、多余空格和换行进行了清理并保持了原文的核心信息和逻辑结构整理后的内容如下TCP异常情况进程终止进程终止会释放文件描述符仍然可以发送FIN和正常关闭没有什么区别。机器重启和进程终止的情况相同。机器掉电/网线断开接收端认为连接还在一旦接收端有写入操作接收端发现连接已经不在了就会进行reset。即使没有写入操作TCP自己也内置了一个保活定时器会定期询问对方是否还在。如果对方不在也会把连接释放。TCP的保活策略放在TCP报头的选项字段0-40字节里面。
返回列表