
接收端处理数据的速度是有限的.如果发送端发的太快,导致接收端的缓冲区被打满,这个时候如果发送 端继续发送,就会造成丢包,继⽽引起丢包重传等等⼀系列连锁反应.
因此TCP⽀持根据接收端的处理能⼒,来决定发送端的发送速度.这个机制就叫做流量控制(Flow Control);


这些图片是来自《图解TCP/IP协议》
《图解HTTP》
接收端如何把窗口大小告诉发送端呢?回忆我们的TCP⾸部中,有⼀个16位窗⼝字段,就是存放了窗⼝大小信息;
那么问题来了,16位数字最⼤表⽰65535,那么TCP窗⼝最大就是65535字节么?
实际上,TCP⾸部40字节选项中还包含了⼀个窗⼝扩⼤因⼦M,实际窗⼝大小是窗⼝字段的值左移M位
虽然TCP有了滑动窗窗口这个大杀器,能够⾼效可靠的发送⼤量的数据.但是如果在刚开始阶段就发送⼤量 的数据,仍然可能引发问题.
因为网络上有很多的计算机,可能当前的⽹络状态就已经⽐较拥堵.在不清楚当前⽹络状态下,贸然发送 ⼤量的数据,是很有可能引起雪上加霜的.
TCP引⼊慢启动机制,先发少量的数据,探探路,摸清当前的⽹络拥堵状态,再决定按照多⼤的速度传输 数据

这两个都可以控制窗口的大小,这就要用到木桶效应了
如果说中间的设备转发能力有限,也会丢包

当无法精准衡量通信链路中单个设备的性能时,直接将整个通信链路当作一个整体,不拆分单个设备的影响。
把 “窗口大小” 等同于 “传输速度”,用 “面多加水,水多加面” 的思路,通过实际传输的反馈(丢包 / 不丢包)来动态调整速度,而非提前计算。
通过反复的 “提速 - 降速” 调整,让传输速度刚好匹配链路的承载能力 —— 既充分利用链路带宽,又不会因过载导致大量丢包,实现 “速率与链路能力的动态平衡”。

当TCP通信开始后,⽹络吞吐量会逐渐上升;随着⽹络发⽣拥堵,吞吐量会⽴刻下降;
下面的图就表示了拥塞控制的工作过程

如果说丢包了,就会TCP reno 掉到丢包的一把这个点,并将它设为新的ssthresh值,然后就成线性增长的过程
拥塞控制,归根结底是TCP协议想尽可能快的把数据传输给对⽅,但是⼜要避免给⽹络造成太⼤压⼒的折 中⽅案.
TCP拥塞控制这样的过程,就好像热恋的感觉

如果接收数据的主机⽴刻返回ACK应答,这时候返回的窗⼝可能⽐较⼩.
⼀定要记得,窗口越⼤,⽹络吞吐量就越⼤,传输效率就越⾼.我们的⽬标是在保证⽹络不拥塞的情况下 尽量提高传输效率;
• 数量限制:每隔N个包就应答⼀次;
• 时间限制:超过最大延迟时间就应答⼀次; 具体的数量和超时时间,依操作系统不同也有差异;⼀般N取2,超时时间取200ms;
数量限制和时间限制是根据数量的多少,决定使用哪一个限制,
数量限制:数据量密集使用
时间限制:数据量比较少,使用时间限制
注意:但是不一定是百分百提高效率,是否提高效率取决于应用程序处理的缓冲区的数据的速度,如果再等待的过程中,没咋处理那效率就低了,如果处理的多,可以返回的窗口就大了,效率就随之提高了

延时应答,发送ack(内核发送) 延迟发送后,发送应答的时间就长了,等一会再发送ack,有可能就会使ack 就会被响应顺带捎着
在延迟应答的基础上,我们发现,很多情况下,客户端服务器在应用层也是"⼀发⼀收"的.意味着客户端 给服务器说了"Howareyou",服务器也会给客户端回⼀个"Fine,thankyou"; 那么这个时候ACK就可以搭顺风车,和服务器回应的"Fine,thankyou"⼀起回给客户端


创建⼀个TCP的socket,同时在内核中创建⼀个发送缓冲区和⼀个接收缓冲区;
由于缓冲区的存在,TCP程序的读和写不需要⼀⼀匹配,
例如
• 写100个字节数据时,可以调用⼀次write写100个字节,也可以调用100次write,每次写⼀个字节;
• 读100个字节数据时,也完全不需要考虑写的时候是怎么写的,既可以⼀次read100个字节,也可以⼀ 次read⼀个字节,重复100次;


解决办法:
1.约定包与包之间的分隔符(包的结束标志)
比如:\n

2.约定包的长度:
比如:在前面设置一个包开头,就是数据包有多长


思考:对于UDP协议来说,是否也存在"粘包问题"呢?
• 对于UDP,如果还没有上层交付数据,UDP的报文长度仍然在.同时,UDP是⼀个⼀个把数据交付给应 ⽤层.就有很明确的数据边界.
• 站在应用层的站在应用层的⻆度,使⽤UDP的时候,要么收到完整的UDP报⽂,要么不收.不会出 现"半个"的情况
• 进程终止:进程终⽌会释放文件描述符,仍然可以发送FIN.和正常关闭没有什么区别.
进程崩溃和 “主动正常退出” 在资源回收层面无本质区别:当进程终止(无论崩溃 / 主动退出),操作系统会自动回收该进程持有的所有资源(包括文件描述符)—— 而 Socket 本身属于 “文件描述符” 的一种,因此系统会自动调用该 Socket 的close操作。
close操作触发 TCP 四次挥手Socket 的close操作会让 TCP 协议栈发送FIN 报文,这是 TCP 四次挥手的 “关闭请求” 信号。因此,即使进程已经崩溃,只要系统完成了 Socket 的close调用,TCP 的四次挥手流程会正常启动,最终完成连接关闭。
TCP 的连接状态(如连接的端口、状态、缓冲区数据等)是由操作系统内核维护的,而非进程本身。所以:进程崩溃后,进程虽然消失,但内核中该 TCP 连接的信息仍存在,因此四次挥手可以依赖内核正常执行 —— 只是连接释放的时机,会比进程主动调用close稍晚(因系统需要先完成进程资源的回收流程)。
• 机器重启:和进程终止的情况相同.
当 A 完成关机(断电 / 系统停止运行),A 的所有内存数据(包括内核中保存的 “与 B 的 TCP 连接信息”)会被直接清除:
B 内核中保存的 “与 A 的 TCP 连接信息”,存储在TCP 连接控制块(TCB,Transmission Control Block) 中(包含 A 的 IP、端口、序号、窗口大小等核心数据)。当 B 多次重传 FIN 仍收不到 A 的 ACK 后,TCP 协议栈会判定 “对端(A)连接异常”,此时会:
最终,A 的 “B 的信息” 随关机丢失,B 的 “ A 的信息” 被主动清理,双方关于这次连接的信息都会彻底消失。
• 机器掉电/网线断开:接收端认为连接还在,⼀旦接收端有写入操作,接收端发现连接已经不在了,就 会进行reset.即使没有写入操作,TCP自己也内置了⼀个保活定时器,会定期询问对⽅是否还在.如果 对方不在,也会把连接释放.


A 突然掉电离线后,B 后续发送的数据无法收到 A 的 ACK(确认)响应,此时 B 会触发TCP 超时重传机制:重复发送未被确认的数据,但因 A 已离线,重传无法解决问题。
当重传次数达到系统设定的阈值时,B 会触发 “重置 TCP 连接” 逻辑,主动发送RST 报文(TCP 头部的 RST 标志位被置位的报文):
由于 A 已掉电,B 发送的 RST 报文同样无法得到 ACK 响应,此时 B 会单方面释放该连接,清除内核中保存的该连接的所有信息(如端口、状态、缓冲区数据等),完成资源回收。
RST 是 TCP 的 “强制中断标志”,区别于正常四次挥手的 “优雅关闭”,仅用于异常场景(如对端无响应、连接状态异常),可快速释放无效连接资源。
TCP 接收方会在 ACK 报文中携带 “接收窗口大小(rwnd)”,发送方以此控制发送速率。若接收方缓冲区已满,会返回rwnd=0(窗口关闭),此时发送方会暂停数据发送。
当发送方因rwnd=0暂停发送后,无法主动获知接收方缓冲区何时空闲(窗口重新打开),此时会发送窗口试探包:
rwnd;rwnd>0,发送方恢复数据传输;若仍为0,则继续等待并周期性试探。窗口试探包≠心跳包:心跳包是探测对端是否在线,而窗口试探包是探测对端的接收能力(窗口状态),仅用于流量控制场景。

当发送方(A)突然掉电,接收方(B)会失去 A 的消息,但无法直接区分 “A 是故障离线” 还是 “暂时无业务数据发送”。
这一逻辑对应 TCP 的 “保活(keep-alive)机制”:系统默认会配置保活超时时间(如 2 小时)、心跳包发送周期(如 75 秒)、重试次数(如 9 次),当达到重试阈值仍无响应,就会终止连接,避免无效连接长期占用资源。
网线断了:

消耗更长的时间,双方分别删除这个TCP信息
另外,应⽤层的某些协议,也有⼀些这样的检测机制.例如HTTP⻓连接中,也会定期检测对⽅的状态.例 如QQ,在QQ断线之后,也会定期尝试重新连接.

核心思路是在应用层模仿 TCP 的可靠传输机制(即 “往 TCP 上套”)