@bit::Shadow
✧(≖ ◡ ≖✿
目录
[connect() accept()](#connect() accept())
[read() recv()](#read() recv())
C端(既然已经收到第一个ACK了)close后为何能读取FIN的信息?
[扩大因子 M](#扩大因子 M)
超时重传
理解丢包问题
丢包分作两种情况
1.未递达丢包。
2.递达后应答丢包。

丢包的确认:未收到应答+等待超时。(等待时间受网络、环境变化而动态决定)
动态超时情景
- Linux(Unix 和 Win也是),超时以500ms为一个单位进行控制,每次判断超时重发时间都是500ms的整数倍。
- 如果重发一次仍然得不到应答,等待2*500ms后再进行重发。(4*500ms---8*500ms指数级增长)。
- 累计一定重传次数,认为网络/对端主机出现异常,强制关闭链接。
应用层再理解
connect() accept()
A段发送执行connect()发送链接请求,状态码设置为STN_SEND。对端收到后状态码设置为SYN_RCVD并应答。++A端收到后connect正式返回++ ,状态码设置为ESTABLISHED表明已经建立链接(与对端确认链接间存在不可避免的时间差)。对端接收到A端链接建立的应答后,同样设置ESTABLISHED状态码,++accept()返回++。

可知:connect()发起了三次握手之后OS自动进行三次握手,而accept()没有参与三次握手!!!
read() recv()
read write的对象是缓冲区(UDP是直接构造数据报,发送到网络)而非网络相关的。具体什么时候传输由OS决策。
这充分体现出用户(应用层)与系统间的忙闲不均形成的高效解耦架构。
三次握手

为什么要进行三次握手??
1.以最小成本确定全双工属性正常。(网络正常)
2.已最小成本确认双方100%愿意。
三次握手真的是三次吗?
即C、S端的第二次握手(SYN+ACK)可能以拆解格式(先SYN后ACK等)应答。
注:就像四次挥手也可以被压缩为三次挥手一样。(但四次挥手难以压缩)
四次挥手难以压缩为三次的原因?
关键是:发送端发送缓冲区极大概率仍然存在数据没有被发送完成。(这背后是序号对数据完整性的掌控)
四次挥手

C端(既然已经收到第一个ACK了)close后为何能读取FIN的信息?
man 2 shutdown 接口 { 单向地关闭fd的读/写端链接 } 在FIN信号中被使用。
总而言之:close()后,系统层均做了对应的保护机制,极大限度地避免了数据丢失。
验证实验
.mp4版
客户端 . . .

服务端启动后主动断开 . . .
显示TIME_WAIT状态

TIME_WAIT状态
最先请求结束连接的一方,接受到ACK后即进入TIME_WAIT状态。
MSL时间段
当一端进入TIME_WAIT状态后吗,最多需等待2*MSL的时间,才会正常关闭释放资源。
MSL:TCP报文在网络内存活存的最大时间。通常是30s
因此TIME_WAIT存在2*MSL的延迟就实现了,无论是发送过来待接受的数据/发送的数据都可以保证接受/发送。
(这也是服务端bind error的原因:服务端绑定一个端口号待服务端终止(结束程序)必须再等待2*MSL的时间)
解决无法立即重启的问题
man setsockopt(待完善理解)
cpp
int opt = 1;
setsockfdopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
导致的问题:导致数据丢失的概率增加。但因为有序号的作用下,大大减少了可能性。
滑动窗口
位置:发送端的发送缓冲区和接收端的接收缓冲区都存在。
以下仅探讨发送缓冲区

滑动窗口的分界
- 左侧:已发送、已应答数据区。
- 内部:待一次性发送数据群。(受网络、硬件条件反馈而动态变化窗口大小)
- 右侧:待发送区域。
32位序号与32位确认序号的响应机制
确认序号反馈的代表接受小于序号标记的数据已送达。

S端发送2001代表报文序号小于2001的数据S端均已接受到。
解决序号应答问题步骤
1.确认丢包是在C--->S丢弃,还是C<---S丢弃。
2.C<---S应答的是S端已送达的数据序号。
快重传与超时重传
快重传


快重传:发送端收到了3次相同的财重复的应答!---------快重传(立即补发)
超时重传(超时+无应答)

分析三次握手中哪个阶段可以携带数据?
答:前两次不能携带数据,第三次可以携带。
建立连接未完全建立时,双方缓冲区状态未知。禁止发送数据。但地三次握手时就可以携带数据了。
流量控制
响应场景:适时控制发送数据流大小,避免溢出、丢失问题。
1.窗口探测
发送数据端常常执行一不携带数据的报文(报头),向接收端探测其接受缓冲区剩余大小。
2.窗口更新通知
接受端发送对自己窗口大小剩余的描述。(可能丢包,发送端常时不时发送探测窗口包)
扩大因子 M
受16位窗口指针字段限制而导致TCP最大65535字节吗?
答:实际上TCP首部40字节选项中还包含了窗口扩大因子M,实际窗口的大小为窗口值左移M位。
实际窗口大小 = TCP头中的窗口值 << M
拥塞控制------拥塞窗口算法
拥塞窗口
一种使用慢启动算法与接收方窗口共同限制滑动窗口大小的算法窗口。
在该值以内网络大概率不拥塞,值以上网络可能拥塞。
滑动窗口的大小 = min (对方窗口大小,拥塞窗口大小)
cpp
[滑动窗口大小] = min(cwnd, rwnd);
慢启动算法
第一阶段:指数级增长。
第二阶段:线性探测增长。
第三阶段:网络/硬件限制或者网络拥塞重新循环。

当TCP开始启动的时候, 慢启动阈值等于窗口最大值;
• 在每次超时重发的时候, 慢启动阈值会变成原来的一半, 同时拥塞窗口置回1;
• 少量的丢包, 我们仅仅是触发超时重传; 大量的丢包, 我们就认为网络拥塞;
当TCP通信开始后, 网络吞吐量会逐渐上升; 随着网络发生拥堵, 吞吐量会立刻下降;
拥塞控制, 归根结底是TCP协议想尽可能快的把数据传输给对方, 但是又要避免给网络造成太大压力的折中方案.
延迟应答
如果接收数据的主机立刻返回ACK应答,这时候返回的窗口可能比较小。
- 假设接收端缓冲区为1M。一次收到了500K的数据;如果立刻应答,返回的窗口就是500K;
- 但实际上可能处理端处理的速度很快10ms内就把500K数据从缓冲区消费掉了;
- 这种情况下,接收端处理掉还远没有达到自己的极限,即使窗口再放大一些依然处理得过来。
- 如果接收端稍微等一会儿再应答,比如等200ms再应答,那么这个时候的窗口大小就是1M;
一定记得,窗口越大,网络吞吐量越大,传输效率就比较高。我们的目标是在保证网络吧拥堵的情况下尽量提高传输效率;
那么所有的包都可以延迟应答吗?肯定也不是;
- 数量限制:每隔N个包就应答一次;
- 时间限制:超过最大延迟时间就应答一次;
- 具体的数量和超时时间,依操作系统不同也有差异;一般N取2,超时时间取200ms。
捎带应答
在延迟应答的基础上,我们发现,很多情况下,客户端服务器在应用层也是"一发一收"的。意味着客户端对服务器说了"How are you",服务器也会给客户端回一个"Fine";
那么这个时候ACK就可以搭顺风车,和服务器回应的"Fine"一起回给客户端
TCP小结
为什么TCP这么复杂? 因为要保证可靠性, 同时又尽可能的提高性能.
可靠性:
• 校验和
• 序列号(按序到达)
• 确认应答
• 超时重发
• 连接管理
• 流量控制
• 拥塞控制
提高性能:
• 滑动窗口
• 快速重传
• 延迟应答
• 捎带应答
其他:
• 定时器(超时重传定时器, 保活定时器, TIME_WAIT定时器等)
TCP与UDP的选择问题
1.对于丢包问题容忍度较高,对信息完整性要求高。使用UDP(像:聊天信息)
2.对于丢包问题容忍度极低就必须使用TCP。(像:登录、注册)
感谢支持,长期连载
欢迎关注
