【重学计网】TCP如何保证可靠传输?怎么保证可靠性?可靠传输的原理?

👨‍💻程序员三明治 :个人主页
🔥 个人专栏 : 《设计模式精解》 《重学数据结构》

🤞先做到 再看见!


目录

序列号(SYN)

TCP将每个字节的数据都进行了编号,这就是序列号。序列号的具体作用如下:能够保证有序性,既能防止数据丢失,又能避免数据重复

TCP的核心目标是在不可靠的IP网络之上,建立一个可靠的"数据管道",使得发送方发送的字节流能够原封不动、顺序正确地被接收方接收。

TCP协议的格式如下图:

关键字段解释:

  • 源/目的端口号:用于标识发送方和接收方的应用程序进程。与IP地址共同构成套接字,用于唯一确定一条连接。
  • 序列号:表示本报文段中第一个字节在整个数据流中的编号,用于实现数据传输的可靠性和顺序性保障
  • 确认号:表示接收方期望收到的下一个报文段的第一个字节的序列号,ACK=N表示接收方已正确接收所有序列号≤N-1的数据。
  • 数据偏移:以4字节为单位指出TCP首部的长度,主要用于解析可变长的选项字段。
  • 控制位(标志位):
    • URG:紧急指针有效,用于标记紧急数据
    • ACK:确认号有效,通常在连接建立后始终置1
    • PSH:推送功能,提示接收方立即交付数据
    • RST:重置连接,用于在出现严重错误时强制断开
    • SYN:同步序列号,用于建立连接
    • FIN:结束标志,用于释放连接
  • 窗口大小:用于流量控制,表示接收方当前可接收的最大字节数,即接收缓冲区的剩余空间。
  • 校验和:覆盖首部和数据部分,用于检测报文段在传输过程中是否发生错误。
  • 紧急指针:仅在URG=1时有效,用于指示本报文段中紧急数据的末尾位置。

确认应答(ACK)

接收方接收数据之后,会回传ACK报文,来告知是否接收到数据了。若发送端仍未收到确认应答,就会启动超时重传。

序列号与确认号的详细规则

python 复制代码
# 伪代码示例:TCP序列号计算
class TCPSegment:
    def __init__(self, seq_num, data):
        self.seq = seq_num          # 本段第一个字节的序列号
        self.data = data
        self.length = len(data)

    def expected_ack(self):
        # 接收方期待的ACK = 本次SEQ + 数据长度
        return self.seq + self.length

# 示例传输过程
segment1 = TCPSegment(seq_num=1000, data="Hello")  # 1000-1004
# 接收方收到后返回ACK=1005
segment2 = TCPSegment(seq_num=1005, data="World")  # 1005-1009

三次握手&四次挥手

TCP通过三次握手建立连接和四次挥手关闭连接,确保通信的可靠性和数据的可靠传输。

三次握手

建立一个 TCP 连接需要"三次握手",缺一不可:

  • 一次握手 :客户端发送带有 SYN(SEQ=x) 标志的数据包 -> 服务端,然后客户端进入 SYN_SEND 状态,等待服务器的确认;
  • 二次握手 :服务端发送带有 SYN+ACK(SEQ=y,ACK=x+1) 标志的数据包 --> 客户端,然后服务端进入 SYN_RECV 状态
  • 三次握手 :客户端发送带有 ACK(ACK=y+1) 标志的数据包 --> 服务端,然后客户端和服务器端都进入ESTABLISHED 状态,完成 TCP 三次握手

为什么一定是三次握手呢?不能是四次五次六次?

三次握手最主要的目的就是双方确认自己与对方的发送与接收是正常的。

  1. 第一次握手:Client 什么都不能确认;Server 确认了对方发送正常,自己接收正常
  2. 第二次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:对方发送正常,自己接收正常
  3. 第三次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己发送、接收正常,对方发送、接收正常

断开连接-TCP 四次挥手

断开一个 TCP 连接则需要"四次挥手",缺一不可:

  1. 第一次挥手 :客户端发送一个 FIN(SEQ=x) 标志的数据包->服务端,用来关闭客户端到服务器的数据传送。然后客户端进入 FIN-WAIT-1 状态。
  2. 第二次挥手 :服务器收到这个 FIN(SEQ=X) 标志的数据包,它发送一个 ACK (ACK=x+1)标志的数据包->客户端 。然后服务端进入 CLOSE-WAIT 状态,客户端进入 FIN-WAIT-2 状态。
  3. 第三次挥手 :服务端发送一个 FIN (SEQ=y)标志的数据包->客户端,请求关闭连接,然后服务端进入 LAST-ACK 状态。
  4. 第四次挥手 :客户端发送 ACK (ACK=y+1)标志的数据包->服务端,然后客户端进入TIME-WAIT 状态,服务端在收到 ACK (ACK=y+1)标志的数据包后进入** CLOSE 状态**。此时如果客户端等待 2MSL 后依然没有收到回复,就证明服务端已正常关闭,随后客户端也可以关闭连接了。

流程图如下:

流量控制

TCP 连接的每一方都有固定大小的缓冲空间,TCP的接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。TCP 使用的流量控制协议是可变大小的滑动窗口协议。 (TCP 利用滑动窗口实现流量控制)

假如这个是我们的TCP的发送缓冲区,那么它可以被划分成三部分:

其中,我们把可以直接发送的部分叫做滑动窗口。

滑动窗口实现细节

python 复制代码
# 简化的滑动窗口实现
class SlidingWindow:
    def __init__(self, window_size):
        self.window_size = window_size
        self.send_base = 0      # 已发送待确认的最小序号
        self.next_seq_num = 0   # 下一个要发送的序号
        self.buffer = {}        # 已发送未确认的数据包
    
    def send_packet(self, data):
        if self.next_seq_num < self.send_base + self.window_size:
            # 窗口内,可以发送
            packet = create_packet(self.next_seq_num, data)
            self.buffer[self.next_seq_num] = packet
            self.next_seq_num += len(data)
            send(packet)
    
    def receive_ack(self, ack_num):
        # 累积确认:确认所有小于ack_num的数据
        for seq in list(self.buffer.keys()):
            if seq < ack_num:
                del self.buffer[seq]
        if ack_num > self.send_base:
            self.send_base = ack_num  # 滑动窗口

拥塞控制

当网络拥塞时,发送端会减少发送速率,以避免进一步加重网络拥塞。常用的拥塞控制算法包括慢启动、拥塞避免、拥塞发生快速恢复等。

具体详解可看这篇文章:面试必问高频题:什么是TCP拥塞控制?拥塞控制四大算法详解,小白易懂,慢开始、拥塞避免、拥塞发生、快速重传;_拥塞控制算法-CSDN博客

超时重传

当 TCP 发送端发出一个段后,它启动一个定时器,等待接收端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。

导致重传的情况

首先,发送方没有接收到响应的ACK报文原因可能有两点:

  • 数据在传输过程中由于网络原因等直接全体丢包,接收方根本没有接收到。
  • 接收方接收到了响应的数据,但是发送的ACK报文响应却由于网络原因丢包了。

那么发生上述情况,我们的发送方在发送完数据后等待一个时间,时间到达没有接收到ACK报文,那么对刚才发送的数据进行重新发送。

  • 如果是刚才第一个原因,接收方收到二次重发的数据后,便进行ACK应答。
  • 如果是第二个原因,接收方发现接收的数据已存在(判断存在的根据就是序列号,所以上面说序列号还有去除重复数据的作用),那么直接丢弃,仍旧发送ACK应答。

我们知道,一来一回的时间总是差不多的,都会有一个类似于平均值的概念。比如发送一个包到接收端收到这个包一共是0.5s,然后接收端回发一个确认包给发送端也要0.5s,这样的两个时间就是RTT(往返时间)。然后可能由于网络原因的问题,时间会有偏差,称为抖动(方差)。

所以,超时重传的时间大概是比往返时间+抖动值还要稍大的时间。

注意:在重发的过程中,假如一个包经过多次的重发也没有收到对端的确认包,那么就会认为接收端异常,强制关闭连接。并且通知应用通信异常强行终止。

TCP使用自适应重传算法:

  • RTT测量:记录往返时间
  • RTO计算 :<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">RTO = SRTT + 4 × RTTVAR</font>
  • Karn算法:重传时不更新RTT,避免二义性

如果我的内容对你有帮助,请辛苦动动您的手指为我点赞,评论,收藏。感谢大家!!

相关推荐
Elastic 中国社区官方博客13 分钟前
我们如何通过 70 多个拉取请求,将 OpenTelemetry Java agent 迁移到 invokedynamic
java·大数据·开发语言·elasticsearch·搜索引擎·全文检索·es
tyn18825 分钟前
记录本地安装nacos服务的方法及异常
java·开发语言
步行cgn1 小时前
Spring MVC 视图机制的核心接口与类
java·开发语言·后端·spring·mvc
y1su1 小时前
Leetcode 字符串模板
java·数据结构·算法·leetcode·职场和发展·动态规划
g3voip2 小时前
工业防水电话到底怎么选?隧道、管廊与码头的需求差异
服务器·网络·信息与通信·ip
码事漫谈2 小时前
AI 读你的提示词,其实很没耐心
后端
珠海西格电力2 小时前
零碳园区管理系统的性能评估体系的行业标准
大数据·网络·人工智能·物联网·机器学习·架构·能源
用户8356290780512 小时前
如何使用 Python 格式化 Excel 单元格中的长文本
后端·python
我命由我123452 小时前
Python 开发 - Python 的 pathlib
java·开发语言·后端·python·学习·java-ee·学习方法
.Bryce.3 小时前
从强化学习到Agentic RL:让Agent在“干活“中自己变聪明
服务器·网络·agent·rag·agentic rl