TCP 滑动窗口核心源码分析(Linux)

TCP 滑动窗口机制是 Linux 内核网络协议栈的核心组成部分,主要涉及以下几个关键文件和数据结构:

  1. 核心数据结构:struct tcp_sock
  2. 窗口管理函数:tcp_rcv_space_adjust()
  3. 窗口更新逻辑:tcp_event_data_sent()、tcp_ack()
  4. 拥塞控制实现:tcp_congestion_control()

下面我们通过分析 Linux 内核源码(基于 5.15 版本)来理解滑动窗口的工作原理:

1. 关键数据结构定义
struct tcp_sock {
    /* 拥塞控制相关参数 */
    u32 snd_cwnd; /* 发送窗口大小 */
    u32 snd_ssthresh; /* 慢启动阈值 */
    u32 snd_wnd; /* 接收方通告的窗口大小 */
    u32 snd_wl1; /* 最近一次窗口更新时的序列号 */
    u32 snd_wl2; /* 最近一次窗口更新时的确认号 */
    /* 序列号管理 */
    u32 snd_una; /* 未确认的最早序列号 */
    u32 snd_nxt; /* 下一个要发送的序列号 */
    u32 snd_up; /* 紧急指针位置 */
    u32 snd_wnd_clamp; /* 窗口大小上限 */
    /* 接收窗口管理 */
    u32 rcv_wnd; /* 当前接收窗口大小 */
    u32 rcv_nxt; /* 期望接收的下一个序列号 */
    u32 rcv_wup; /* 窗口更新时的确认号 */
};
2. 接收窗口调整逻辑

当接收到数据时,内核会调用tcp_rcv_space_adjust()函数调整接收窗口:

 
static void tcp_rcv_space_adjust(struct sock *sk)
{
    struct tcp_sock *tp = tcp_sk(sk);
    int window;
    /* 计算可用接收缓冲区大小 */
    window = sk->sk_rcvbuf - sk_wmem_queued(sk);
    /* 应用窗口缩放因子 */
    window = min_t(int, window, TCP_MAX_WINDOW);
    window >>= tp->rx_opt.wscale;
    /* 更新接收窗口大小 */
    if (window != tp->rcv_wnd) {
        tp->rcv_wnd = window;
        /* 设置窗口更新标志 */
        tcp_mark_window_update(sk);
    }
}
3. 发送窗口计算逻辑

在发送数据前,内核会计算实际可用的发送窗口:

static inline u32 tcp_current_snd_window(const struct tcp_sock *tp)
{
	/* 实际发送窗口 = min(拥塞窗口, 接收方通告窗口) */
	return min(tp->snd_cwnd, tp->snd_wnd);
}
/* 计算可发送的字节数 */
static inline u32 tcp_send_headroom(const struct tcp_sock *tp)
{
	u32 headroom;
	/* 窗口右边界 - 下一个要发送的序列号 */
	headroom = tcp_wnd_end(tp) - tp->snd_nxt;
	/* 如果已经超过窗口大小,返回0 */
	if (headroom > tp->snd_wnd)
		headroom = 0;
	return headroom;
}
4. 窗口滑动逻辑

当收到 ACK 确认时,内核会调用tcp_ack()函数处理确认并滑动窗口:

static int tcp_ack(struct sock *sk, struct sk_buff *skb, int flag)
{
	struct tcp_sock *tp = tcp_sk(sk);
	u32 ack = TCP_SKB_CB(skb)->ack_seq;
	u32 old_una = tp->snd_una;
	/* 处理ACK确认 */
	if (after(ack, tp->snd_una)) {
		/* 滑动窗口左边界到最新确认位置 */
		tp->snd_una = ack;
		/* 更新拥塞窗口(慢启动或拥塞避免) */
		tcp_cong_ack(sk, ack, FLAG_SLOWPATH);
		/* 检查是否有新的窗口更新 */
		if (TCP_SKB_CB(skb)->rx_opt.wscale_ok &&
		TCP_SKB_CB(skb)->window_clamp) {
			tp->snd_wnd_clamp = TCP_SKB_CB(skb)->window_clamp;
		}
		/* 更新接收方通告的窗口大小 */
		if (tcp_update_windows(tp, skb)) {
			/* 窗口已更新,触发发送更多数据 */
			tcp_event_data_sent(sk);
		}
	}
	/* 其他ACK处理逻辑... */
	return 0;
}
5. 窗口更新触发数据发送

当窗口更新或有新的确认到达时,内核会调用tcp_event_data_sent()尝试发送更多数据:

static void tcp_event_data_sent(struct sock *sk)
{
    struct tcp_sock *tp = tcp_sk(sk);
    /* 如果有数据等待发送且窗口允许 */
    if (tcp_write_queue_empty(sk) == 0 &&
        tcp_send_headroom(tp) > 0) {
            /* 尝试发送数据 */
            tcp_send_active(sk);
    }
}
6. 糊涂窗口综合征避免

Linux 内核通过以下逻辑避免糊涂窗口综合征:

static bool tcp_snd_wnd_test(struct sock *sk)
{
    struct tcp_sock *tp = tcp_sk(sk);
    int mss = tcp_current_mss(sk);
    /* 窗口太小且缓冲区数据不足一个MSS时,暂不发送 */
    if (tp->snd_wnd < mss && sk_wmem_queued(sk) < mss) {
        /* 延迟发送,等待更多数据积累 */
        return false;
    }
    return true;
}

滑动窗口核心机制总结

通过分析上述源码,可以总结出 TCP 滑动窗口的核心工作机制:

  1. 窗口大小动态调整
    1. 接收窗口根据可用缓冲区空间动态调整
    2. 发送窗口取拥塞窗口和接收方通告窗口的最小值
  2. 窗口滑动触发条件
    1. 收到新的 ACK 确认时,窗口左边界滑动到最新确认位置
    2. 接收方通告窗口更新时,窗口右边界相应调整
  3. 流量控制实现
    1. 接收方通过窗口大小字段告知发送方自身处理能力
    2. 发送方根据接收方反馈调整发送速率
  4. 效率优化措施
    1. 窗口扩大选项突破 65535 字节限制
    2. 糊涂窗口综合征避免机制
    3. 延迟确认和捎带确认减少 ACK 数量

通过这些机制的协同工作,TCP 滑动窗口实现了高效、可靠的网络数据传输。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值