1.理解TCP报头
TCP报头的本质:结构体
1.TCP报头和有效载荷分离???
(不包括选项这里先不说)报头的长度是固定的
2.分用问题
存在目的端口号
为什么TCP有报头长度,但是没有有效载荷长度???
对于TCP叫做可靠性协议的正确认识
可靠性核心定义:TCP 要保证我方发出的数据,能够得到对方应答确认;通过确认应答机制,保证历史发送的数据100% 交付到对端的内核缓冲区。
客户端要怎么知道服务器端有没有收到报文
只有当客户端收到服务器给他的报文的时候才确定上面那条报文收到了
当然要是没有收到报文也会有下面两种情况
- 我方发送出去的报文在路上丢失:数据包损毁、网络丢包,压根没有抵达服务端。
- 服务端返回的 ACK 确认报文在路上丢失:服务端明明收到数据了,但是回执报文网络丢包,回不到客户端。
对客户端来说,这两种现象表现完全一样:收不到 ACK,客户端区分不开到底是数据丢了,还是 ACK 丢了。
超时重传机制
客户端发送报文时,会启动超时计时器,超过deadline就会触发重传。
- 如果计时器到期之前,收到了对应的 ACK 确认:计时器清零,万事大吉。
- 如果超时时间耗尽,依旧没有收到 ACK:触发超时重传,把原来的数据报文重新发送一遍。
总结
可靠性
确认应答 = 保证数据一定送达对方。这种说法不准确。
因为网络本身是不可靠的,数据包可能在传输过程中丢失、损坏或延迟。无论协议怎么设计,都无法做到 “数据一定能到达对方”。
真正能做到的是:
- 如果数据到达了,发送方能收到确认;
- 如果数据长时间没有得到确认,发送方可以判定本次发送异常,并采取重传等措施。
所以,确认应答更准确的意义是:
让发送方获得一次明确的反馈,而不是一直处于未知状态
当前没有收到报文
- 要不要继续等待?
- 要不要重新发送?
确认应答机制就是为了解决这个问题。
它让发送方在限定时间内得到一个明确状态:
有以下几种情况
情况一:对方收到数据
接收方收到报文后,回复 ACK。
发送方收到 ACK 后,可以确定:
本次数据已经被对方接收。
于是发送方可以继续发送下一段数据,或释放本次发送缓冲区。
情况二:对方没有收到数据
如果报文在网络中丢失,接收方自然不会回复 ACK。
发送方超时后仍未收到 ACK,就会知道:
本次发送结果不确定,需要重传。
此时发送方不是盲目认为 “对方一定没收到”,而是根据超时机制做出一个合理判断,并触发重传。
情况三:对方收到了,但 ACK 丢失
这时,发送方仍然收不到 ACK,会触发超时重传。
从结果上看,发送方并不知道 “对方其实已经收到了”,但这并不影响确认应答机制的价值。因为对于发送方来说,它得到的仍然是一个确定结果:
超时未收到 ACK,执行重传。
而接收方收到重复数据后,可以通过序列号识别并丢弃重复报文,再回复 ACK。
所以,确认应答机制关注的不是 “每次都必须成功送达”,而是:
无论网络情况如何,发送方都能按照统一规则得到一个确定处理结果。
按序投递
TCP 通信:协议层面 A、B 两端地位完全对等、双向对称,两端都既能发也能收。
在实际中TCP发送报文大多是以下这种情况一下子发很多,然后接收很多,不会是一条发一条收,这样的效率实在是太低了
但是像这样发送可能会存在乱序问题---:IP 网络报文走不同路由,接收方收到报文顺序和发送顺序不一致。
序列号 + 接收缓冲区
- 乱序到达的报文不会直接交给应用,先存缓冲区;
- 根据序列号对数据重新整理排序;
- 只有拿到连续完整字节流,才按正确顺序交付上层应用。
- 去重:识别重复报文,丢弃重复数据
- 排序:处理网络乱序,保证上交应用的数据有序
如果发回来的应答丢了,3001丢了,那么对方报文到底有没有全部收到呢????
答案就是全部都收到了,因为确认序号4001,也就是表明前面的报文全部都是收到了
确认序号: 自己收到的序号 + 1,确认序号! 含义:该序号之前的所有数据,我收到了 seq: 1000 ack seq: 1001,1001 序号之前的所有数据,我收到了
为什么既要有序号,又要又确认序号
因为有捎带应答的出现,提高效率
捎带应答(piggybacking)
如果没有两套字段:
收到数据 → 单独发一个纯 ACK 报文应答;要发数据时,再单独发数据报文。 来回多一次报文,开销大,效率低。
TCP 设计:应答信息不单独发报文,直接 “捎带” 在自己要发送的数据报文中。
- 报文里用
seq放自己要发数据的编号; - 报文里用
ack放对对方数据的确认回执。
👉 同一个 TCP 报文,同时携带 “我方的数据” 和 “对对方的应答”,不用额外发送纯 ACK 包,减少报文数量,提升网络传输效率。
16位窗口大小
我们知道 TCP 在内核中维护两套缓冲区:发送缓冲区、接收缓冲区。
接收缓冲区是接收方内核开辟的一块内存,应用程序调用read()才会把数据从内核缓冲区拷贝到用户空间,缓冲区的剩余容量是动态变化的。
假设场景:接收缓冲区总大小 4KB,上层应用读取慢,当前已经占用 3KB,仅剩 1KB 空闲空间。 此时发送方还在持续发送大量数据。接收方缓冲区已满之后,新来的数据无法存入内核缓冲区,就会被直接丢弃。 数据包跨越网络传输之后被丢弃,网络带宽白白消耗,重传又会进一步拉低整体传输效率。
但是如果要是接收方给发送方,告知它有剩余的缓存空间,那么也就是说会更好的控制发送报文,数据的时间
TCP 引入流量控制机制:接收方会在 ACK 报文中,携带自己接收缓冲区的剩余可用大小,这个值就是接收窗口 rwnd。 发送方拿到这个窗口大小,就知道:最多只能发送不超过窗口大小的数据,不能一股脑疯狂发包。
接收方,如何衡量自己的接收能力??
看接收缓冲区中剩余空间的大小
发顺丰如何得知对方的接收能力??
将自己的接收能力,填写到应答报文的16位窗口大小中
流量控制-->可靠性+效率
标志位
有这么多种报文,当然要对他来进行管理
标志位是用来区分报文类型的
ACK:确认序号是否有效。绝大多数 TCP 报文 ACK=1;只有连接建立的第一个 SYN 报文,ACK 才为 0。ACK 有效时,确认序号字段才具备意义,用来回执已经收到的数据。
SYN:
主机 A(客户端) ↔ 主机 B(服务端)
- 第一次握手:SYN 报文主机 A 发送
SYN=1报文:向 B 发起连接请求,携带自己的初始序列号 seq。
比喻:A:“你可以做我女朋友吗?(我想和你建立连接)”
- 第二次握手:SYN+ACK 报文主机 B 收到 SYN,回复
SYN=1,ACK=1报文。 一方面回复同意建立连接 (SYN),另一方面对 A 的请求做确认应答 (ACK)。
比喻:B:“可以,什么时候开始?(我收到你的请求,我也同意建立连接)”
- 第三次握手:ACK 报文主机 A 收到 SYN+ACK,回复纯
ACK=1确认报文,确认收到 B 的同步请求。报文发送完成,连接正式建立,可以传输业务数据。
比喻:A:“就现在!(我收到你的答复,连接正式就绪)”
双方OS都会存在大量的连接,OS要管理这些连接--->先描述在组织,存在链接结构体,这个结构体相对于UDP的更加复杂,建立链接时有成本的,时间+空间
通信之前,要先保证网络是通畅的--->验证全双工
FIN
TCP 是全双工,A、B 双方各自都要关闭自己的发送通道,所以需要 4 次报文交互。 主机 A:主动关闭方,主机 B:被动关闭方
- 第一次挥手:FIN 报文主机 A 发送
FIN=1,告诉 B:A 这边不再发送新数据,准备关闭自己方向的通道。
比喻 A:“我这边说完了,我不发消息啦。”
- 第二次挥手:ACK 报文主机 B 收到 FIN,回复
ACK=1确认。此时 A→B 方向关闭,但 B 还可以继续给 A 发送剩余的数据。
比喻 B:“收到,我知道你不发消息了,不过我这边还有话要说。”
- 第三次挥手:FIN 报文等 B 把本机剩余数据全部发送完毕后,B 发送
FIN=1,通知 A:B 也不再发送数据。
比喻 B:“我的消息也发送完了,我也要结束对话。”
- 第四次挥手:ACK 报文主机 A 收到 B 的 FIN,回复
ACK=1确认。
- B 收到 ACK,立刻关闭连接。
- A 会等待
TIME‑WAIT时间,再释放本地资源,防止最后这个 ACK 报文丢失,保证 B 可以收到确认。
比喻 A:“好的,到此结束。”
基于上面的了解
为什么是三次握手,四次挥手
也可以是三次握手啊???
这里其实是用到了捎带应答,-->也就是说作为服务器端,别人向你发起连接,你在很多时候都是答应的,因为你是服务器,捎带应答可以提高效率
而四次挥手是因为可能有一方要继续传输数据,不能中断,不能立刻发出 FIN 关闭报文。