当前位置: 首页 > news >正文

计算机网络-TCP可靠传输机制

计算机网络-TCP可靠传输机制

  • 3. TCP可靠传输机制
    • 3.1 序列号与确认号机制
      • 3.1.1 序列号与确认号的基本概念
      • 3.1.2 序列号与确认号的工作原理
      • 3.1.3 序列号与确认号在Linux内核中的实现
        • TCP控制块中的序列号和确认号字段
        • 序列号的初始化
        • 发送数据时的序列号处理
        • 接收数据时的确认号处理
      • 3.1.4 序列号与确认号的实际应用
        • 数据的有序交付
        • 检测丢失的数据
        • 去除重复的数据
        • 支持流量控制和拥塞控制
        • 支持选择性确认(SACK)
      • 3.1.5 序列号与确认号的常见问题与优化
        • 序列号回绕问题
        • 初始序列号的安全性
        • 确认延迟
        • 重复确认和快速重传
    • 3.2 超时重传机制
      • 3.2.1 超时重传的基本概念
      • 3.2.2 超时重传的工作原理
      • 3.2.3 RTO的计算
      • 3.2.4 超时重传在Linux内核中的实现
        • RTO的计算
        • 重传定时器的管理
        • 重传数据段
      • 3.2.5 超时重传的实际应用
        • 处理数据包丢失
        • 处理确认丢失
        • 适应网络状况变化
        • 与快速重传的协同
      • 3.2.6 超时重传的常见问题与优化
        • 虚假超时
        • 重传歧义
        • 重传风暴
        • 针对特定网络环境的优化
    • 3.3 滑动窗口机制
      • 3.3.1 滑动窗口的基本概念
      • 3.3.2 滑动窗口的工作原理
        • 发送窗口
        • 接收窗口
        • 窗口滑动过程
      • 3.3.3 滑动窗口在Linux内核中的实现
        • TCP控制块中的窗口字段
        • 发送窗口的管理
        • 接收窗口的管理
        • 窗口更新
      • 3.3.4 滑动窗口的实际应用
        • 流量控制
        • 提高网络利用率
        • 支持批量确认
        • 与拥塞控制的协同
      • 3.3.5 滑动窗口的常见问题与优化
        • 窗口缩放问题
        • 零窗口问题
        • 糊涂窗口综合症
        • 接收窗口自动调整
    • 3.4 差错控制机制
      • 3.4.1 差错控制的基本概念
      • 3.4.2 TCP的差错控制机制
        • 校验和(Checksum)
        • 确认和重传
        • 序列号和确认号
        • 超时机制
      • 3.4.3 差错控制在Linux内核中的实现
        • 校验和的计算和验证
        • 确认和重传的实现
        • 选择性确认(SACK)的实现
      • 3.4.4 差错控制的实际应用
        • 处理网络丢包
        • 处理网络乱序
        • 处理数据损坏
        • 适应网络状况变化
      • 3.4.5 差错控制的常见问题与优化
        • 校验和的局限性
        • 重传歧义问题
        • 选择性确认的开销
        • 差错控制与拥塞控制的协同

3. TCP可靠传输机制

TCP(传输控制协议)的核心特性是提供可靠的数据传输服务,即确保数据能够无错、完整、按序地从发送方传输到接收方。这种可靠性是通过一系列精心设计的机制实现的,包括序列号与确认号机制、超时重传机制、滑动窗口机制和差错控制机制等。本章将深入探讨这些机制的原理和实现。

3.1 序列号与确认号机制

序列号和确认号是TCP可靠传输的基础,它们使得TCP能够跟踪已发送和已接收的数据,检测丢失或重复的数据,并确保数据按序交付。

3.1.1 序列号与确认号的基本概念

序列号(Sequence Number):用于标识从TCP发送端向TCP接收端发送的数据字节流的位置。序列号是32位的无符号整数,当它达到2^32-1时会绕回到0。

确认号(Acknowledgment Number):用于告知发送方接收方已成功接收的数据。确认号表示接收方期望从发送方收到的下一个字节的序列号,即已成功接收的数据的最高序列号加1。

在TCP连接建立时,通过三次握手过程,双方会交换随机生成的初始序列号(ISN),这是为了安全考虑,防止序列号被猜测和连接被劫持。

3.1.2 序列号与确认号的工作原理

TCP将数据视为无结构的字节流,每个字节都有一个序列号。当发送数据时,TCP会为每个数据段分配序列号,接收方则通过确认号告知发送方已成功接收的数据。

以下是序列号与确认号工作的基本流程:

  1. 发送方发送数据

    • 发送方将数据分割成多个段
    • 为每个段分配序列号,序列号等于该段第一个字节在整个数据流中的位置
    • 发送数据段,并启动重传定时器
  2. 接收方接收数据

    • 接收方接收数据段,检查序列号是否在预期范围内
    • 如果数据段有效,接收方将数据放入接收缓冲区
    • 接收方发送确认,确认号等于已成功接收的数据的最高序列号加1
  3. 发送方处理确认

    • 发送方接收确认,检查确认号
    • 如果确认号大于已确认的序列号,发送方更新已确认的序列号
    • 发送方可以释放已确认数据的缓冲区空间

这个过程确保了数据的可靠传输:如果数据段丢失,接收方不会确认该段,发送方会在超时后重传;如果数据段乱序到达,接收方可以根据序列号重新排序;如果数据段重复到达,接收方可以根据序列号识别并丢弃重复的数据。

3.1.3 序列号与确认号在Linux内核中的实现

在Linux内核中,序列号和确认号的处理涉及多个数据结构和函数。以下是一些关键的实现细节:

TCP控制块中的序列号和确认号字段

TCP控制块(TCP Control Block,TCB)是存储TCP连接状态的数据结构,在Linux内核中对应struct tcp_sock。它包含了与序列号和确认号相关的多个字段:

// file: include/linux/tcp.h
struct tcp_sock {// .../* 发送相关 */u32 snd_una;    /* 已发送但未确认的第一个字节的序列号 */u32 snd_nxt;    /* 下一个要发送的字节的序列号 */u32 snd_wl1;    /* 用于窗口更新的序列号 */u32 snd_wl2;    /* 用于窗口更新的确认号 */u32 write_seq;  /* 初始发送序列号 *//* 接收相关 */u32 rcv_nxt;    /* 期望接收的下一个字节的序列号 */u32 rcv_wup;    /* 接收窗口更新点 */u32 copied_seq; /* 已复制到用户空间的最后一个字节的序列号 */// ...
};
序列号的初始化

在TCP连接建立时,需要为连接分配初始序列号(ISN)。Linux内核使用secure_tcp_seq函数生成随机的初始序列号:

// 简化版的secure_tcp_seq函数
__u32 secure_tcp_seq(__be32 saddr, __be32 daddr, __be16 sport, __be16 dport)
{struct tcp_secret secret;u32 hash[MD5_DIGEST_WORDS];u32 seq;// 使用密钥和连接四元组计算哈希值net_secret_init(&secret);seq = secret.secrets[0];seq += ktime_get_real_ns() >> 6;seq += saddr;seq += daddr;seq += (sport << 16) + dport;// 添加随机性seq ^= secure_tcp_sequence_number(hash);return seq;
}

这个函数使用源IP地址、目的IP地址、源端口、目的端口以及一个密钥和时间戳来生成随机的初始序列号,增加了序列号被猜测的难度,提高了安全性。

发送数据时的序列号处理

当发送数据时,TCP需要为数据段分配序列号。这通常在tcp_transmit_skb函数中完成:

// 简化版的tcp_transmit_skb函数
static int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it,gfp_t gfp_mask)
{// ...// 构建TCP头部th = tcp_hdr(skb);th->source = inet->inet_sport;th->dest = inet->inet_dport;// 设置序列号th->seq = htonl(tcb->seq);// 设置确认号(如果需要)if (tcb->tcp_flags & TCPHDR_ACK)th->ack_seq = htonl(tp->rcv_nxt);// ...// 更新发送序列号if (!(tcb->tcp_flags & TCPHDR_SYN))tp->snd_nxt = TCP_SKB_CB(skb)->end_seq;// ...
}
接收数据时的确认号处理

当接收数据时,TCP需要处理序列号和生成确认号。这通常在tcp_rcv_established函数中完成:

// 简化版的tcp_rcv_established函数
void tcp_rcv_established(struct sock *sk, struct sk_buff *skb,const struct tcphdr *th, unsigned int len)
{// ...// 检查序列号是否在接收窗口内if (!tcp_sequence(tp, TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq)) {// 序列号不在接收窗口内,丢弃数据段// ...goto discard;}// 处理数据tcp_data_queue(sk, skb);// 更新接收序列号tp->rcv_nxt = TCP_SKB_CB(skb)->end_seq;// 发送确认tcp_send_ack(sk);// ...
}

tcp_send_ack函数中,会构建并发送ACK包,确认号设置为tp->rcv_nxt

// 简化版的tcp_send_ack函数
void tcp_send_ack(struct sock *sk)
{// ...// 构建ACK包buff = alloc_skb(MAX_TCP_HEADER, sk_gfp_atomic(sk, GFP_ATOMIC));if (!buff)return;// 设置TCP头部skb_reserve(buff, MAX_TCP_HEADER);tcp_init_nondata_skb(buff, tp->snd_una, TCPHDR_ACK);// 设置确认号th = tcp_hdr(buff);th->ack_seq = htonl(tp->rcv_nxt);// 发送ACK包tcp_transmit_skb(sk, buff, 0, sk_gfp_atomic(sk, GFP_ATOMIC));// ...
}

3.1.4 序列号与确认号的实际应用

序列号和确认号机制在TCP的实际应用中发挥着关键作用,它们支持了多种重要功能:

数据的有序交付

序列号使得接收方能够按照发送顺序重组数据,即使数据段乱序到达。接收方会根据序列号将数据段放入正确的位置,确保应用程序收到的数据与发送时的顺序相同。

检测丢失的数据

通过确认号,发送方可以知道哪些数据已经被接收方成功接收。如果在一定时间内没有收到某个数据段的确认,发送方会认为该数据段丢失,并重新发送。

去除重复的数据

如果接收方收到了重复的数据段(可能是由于网络延迟或重传导致的),它可以根据序列号识别出这是重复的数据,并丢弃它,避免将重复的数据交付给应用程序。

支持流量控制和拥塞控制

序列号和确认号是TCP流量控制和拥塞控制的基础。通过跟踪已发送和已确认的数据量,TCP可以调整发送速率,避免接收方缓冲区溢出和网络拥塞。

支持选择性确认(SACK)

在标准的TCP中,确认号只能确认连续的数据。选择性确认(SACK)扩展了这一机制,允许接收方确认非连续的数据块,提高了重传效率。SACK通过TCP选项实现,使用序列号范围来指示已接收的数据块。

3.1.5 序列号与确认号的常见问题与优化

序列号回绕问题

序列号是32位的无符号整数,当发送的数据量超过4GB时,序列号会回绕到0。这可能导致新旧数据无法区分,特别是在高速网络中。

TCP通过时间戳选项解决了这个问题。时间戳选项为每个数据段添加一个时间戳,即使序列号回绕,也可以通过时间戳区分新旧数据。这个机制被称为PAWS(Protection Against Wrapped Sequence numbers)。

初始序列号的安全性

如果初始序列号是可预测的,攻击者可能会猜测序列号并伪造TCP段,导致连接劫持或数据注入。为了提高安全性,现代TCP实现使用加密算法生成随机的初始序列号,如前面介绍的secure_tcp_seq函数。

确认延迟

为了减少网络开销,TCP通常不会立即确认每个接收到的数据段,而是延迟一段时间(通常是200ms),希望在这段时间内有数据要发送,可以将确认捎带在数据段中。这种延迟确认机制可能会增加重传超时的可能性。

优化方法包括:

  • 调整延迟确认的超时时间
  • 在特定场景下禁用延迟确认
  • 实现更智能的确认策略,如根据网络状况动态调整确认行为
重复确认和快速重传

如果接收方收到了乱序的数据段,它会发送重复确认(duplicate ACK),确认号仍然是期望接收的下一个字节的序列号。当发送方收到多个重复确认(通常是3个)时,会触发快速重传机制,立即重传可能丢失的数据段,而不等待重传超时。

这个机制在Linux内核中的实现如下:

// 简化版的tcp_fastretrans_alert函数
void tcp_fastretrans_alert(struct sock *sk, int pkts_acked, int flag)
{// ...// 检查是否收到了3个重复确认if (tp->dup_ack >= tp->reordering) {// 触发快速重传tcp_retransmit_skb(sk, tcp_write_queue_head(sk));// 进入快速恢复状态tp->snd_cwnd = tp->snd_ssthresh + tp->reordering;// ...}// ...
}

序列号和确认号机制是TCP可靠传输的基础,它们通过精心设计的交互过程,确保了数据的可靠、有序传输。理解序列号和确认号的原理和实现,对于深入理解TCP协议和优化网络应用都有重要意义。

3.2 超时重传机制

超时重传机制是TCP可靠传输的关键组成部分,它确保了在数据包丢失的情况下,发送方能够检测到丢失并重新发送数据,从而保证数据的可靠传输。

3.2.1 超时重传的基本概念

**超时重传(Timeout Retransmission)**是指当发送方在一定时间内没有收到已发送数据的确认时,假定数据已丢失,并重新发送该数据的机制。

超时重传机制基于以下假设:

  • 如果数据在网络中丢失,接收方将无法确认该数据
  • 如果确认在网络中丢失,发送方将无法知道数据已被接收
  • 在这两种情况下,发送方都需要重新发送数据

超时重传的关键是确定合适的超时时间,即重传超时时间(Retransmission Timeout,RTO)。如果RTO设置得太短,可能会导致不必要的重传,增加网络负担;如果RTO设置得太长,则会延长数据恢复的时间,降低传输效率。

3.2.2 超时重传的工作原理

TCP超时重传的基本工作流程如下:

  1. 发送数据

    • 发送方发送数据段
    • 启动重传定时器,超时时间为当前的RTO值
  2. 等待确认

    • 如果在RTO时间内收到确认,取消重传定时器
    • 如果在RTO时间内没有收到确认,触发超时重传
  3. 超时重传

    • 重新发送未被确认的最小序列号的数据段
    • 更新RTO值(通常是将当前RTO值加倍,这被称为指数退避)
    • 重新启动重传定时器
  4. 重传限制

    • 如果重传次数超过最大限制,TCP会放弃重传,关闭连接或通知上层应用

TCP的超时重传机制不仅仅是简单地重新发送数据,还包括了RTO的计算和调整,以适应网络状况的变化。

3.2.3 RTO的计算

RTO的计算是TCP超时重传机制的核心部分。TCP使用往返时间(Round-Trip Time,RTT)的测量值来计算RTO。RFC 6298定义了标准的RTO计算算法,称为Jacobson/Karels算法:

  1. 初始化

    • 初始RTO设置为1秒(或3秒,取决于实现)
    • 初始SRTT(平滑RTT)和RTTVAR(RTT变化量)未定义
  2. 首次RTT测量

    • 当收到第一个RTT测量值R时
    • SRTT = R
    • RTTVAR = R/2
    • RTO = SRTT + 4 * RTTVAR
  3. 后续RTT测量

    • 当收到新的RTT测量值R时
    • RTTVAR = (1 - beta) * RTTVAR + beta * |SRTT - R|,其中beta通常为0.25
    • SRTT = (1 - alpha) * SRTT + alpha * R,其中alpha通常为0.125
    • RTO = SRTT + 4 * RTTVAR
  4. RTO限制

    • RTO不应小于1秒(某些实现可能使用更小的值,如200ms)
    • 没有明确的上限,但通常会设置一个最大值(如60秒)
  5. 指数退避

    • 当发生超时重传时,RTO加倍
    • 连续重传时,RTO会呈指数增长(1s, 2s, 4s, 8s, …)
    • 当收到新的确认时,RTO重新计算

这个算法使得RTO能够适应网络状况的变化:当网络稳定时,RTO接近RTT;当网络不稳定时,RTO会增大,减少不必要的重传。

3.2.4 超时重传在Linux内核中的实现

在Linux内核中,超时重传机制的实现涉及多个函数和数据结构。以下是一些关键的实现细节:

RTO的计算

Linux内核使用类似于RFC 6298的算法计算RTO,但有一些优化和调整:

// 简化版的tcp_rtt_estimator函数
void tcp_rtt_estimator(struct sock *sk, long mrtt_us)
{struct tcp_sock *tp = tcp_sk(sk);long m = mrtt_us;u32 srtt = tp->srtt_us;// 更新SRTT和RTTVARif (srtt != 0) {// 已有SRTT,使用EWMA算法更新m -= (srtt >> 3);  // m = rtt - srtt/8srtt += m;         // srtt += m/8if (m < 0) {m = -m;m -= (tp->mdev_us >> 2);if (m > 0)m >>= 3;} else {m -= (tp->mdev_us >> 2);if (m > 0)m >>= 2;}tp->mdev_us += m;  // mdev += m/4// 更新最大MDEVif (tp->mdev_us > tp->mdev_max_us) {tp->mdev_max_us = tp->mdev_us;if (tp->mdev_max_us > tp->rttvar_us)tp->rttvar_us = tp->mdev_max_us;}// 每隔一段时间,更新RTTVARif (after(tp->snd_una, tp->rtt_seq)) {tp->rtt_seq = tp->snd_nxt;if (tp->mdev_max_us < tp->rttvar_us)tp->rttvar_us -= (tp->rttvar_us - tp->mdev_max_us) >> 2;tp->mdev_max_us = tcp_rto_min_us(sk);}} else {// 首次测量RTTsrtt = m << 3;tp->mdev_us = m;tp->mdev_max_us = m;tp->rttvar_us = m << 1;}tp->srtt_us = srtt;// 计算RTOtp->rto = __tcp_set_rto(tp);// 限制RTO的范围tp->rto = min(tp->rto, TCP_RTO_MAX);tp->rto = max(tp->rto, TCP_RTO_MIN);
}
重传定时器的管理

TCP使用定时器来触发超时重传。在Linux内核中,这通过tcp_write_timer函数实现:

// 简化版的tcp_write_timer函数
void tcp_write_timer(struct timer_list *t)
{struct inet_connection_sock *icsk = from_timer(icsk, t, icsk_retransmit_timer);struct sock *sk = &icsk->icsk_inet.sk;// 检查是否需要重传if (!sock_owned_by_user(sk)) {tcp_retransmit_timer(sk);} else {// 如果套接字正在被用户使用,延迟重传inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,icsk->icsk_rto, TCP_RTO_MAX);}sock_put(sk);
}

当重传定时器超时时,会调用tcp_retransmit_timer函数处理:

// 简化版的tcp_retransmit_timer函数
void tcp_retransmit_timer(struct sock *sk)
{struct tcp_sock *tp = tcp_sk(sk);// 检查是否有未确认的数据if (!tp->packets_out)goto out;// 检查是否超过最大重传次数if (tcp_write_timeout(sk))goto out;// 执行超时重传tcp_retransmit_skb(sk, tcp_write_queue_head(sk));// 更新RTO(指数退避)icsk->icsk_rto = min(icsk->icsk_rto << 1, TCP_RTO_MAX);// 重新启动重传定时器inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,icsk->icsk_rto, TCP_RTO_MAX);// ...out:// ...
}
重传数据段

当需要重传数据段时,会调用tcp_retransmit_skb函数:

// 简化版的tcp_retransmit_skb函数
int tcp_retransmit_skb(struct sock *sk, struct sk_buff *skb)
{// ...// 准备重传TCP_SKB_CB(skb)->seq = tp->snd_una;// 更新重传计数TCP_SKB_CB(skb)->tcp_flags |= TCPHDR_SYN;tp->retrans_stamp = tcp_time_stamp(tp);tp->retrans_out += tcp_skb_pcount(skb);// 发送重传的数据段err = tcp_transmit_skb(sk, skb, 1, GFP_ATOMIC);// ...return err;
}

3.2.5 超时重传的实际应用

超时重传机制在TCP的实际应用中发挥着关键作用,它确保了数据的可靠传输,特别是在网络状况不佳的情况下。以下是一些实际应用场景:

处理数据包丢失

在网络拥塞或链路质量差的情况下,数据包可能会丢失。超时重传机制能够检测到这种丢失,并重新发送数据,确保数据的完整性。

处理确认丢失

即使数据包成功到达接收方,确认包也可能在返回途中丢失。超时重传机制同样能够处理这种情况,通过重新发送数据来触发新的确认。

适应网络状况变化

通过动态调整RTO,TCP能够适应网络状况的变化。当网络延迟增加时,RTO会相应增加,减少不必要的重传;当网络状况改善时,RTO会逐渐减小,提高传输效率。

与快速重传的协同

超时重传机制与快速重传机制协同工作,共同提高TCP的可靠性和效率。快速重传通过重复确认检测丢失,能够更快地响应;而超时重传作为最后的保障,确保即使在没有足够重复确认的情况下,丢失的数据也能被恢复。

3.2.6 超时重传的常见问题与优化

虚假超时

如果RTO设置得不合理,或者网络延迟突然增加,可能会导致虚假超时,即数据实际上没有丢失,但由于确认没有在RTO时间内到达,触发了不必要的重传。

优化方法包括:

  • 使用更准确的RTT测量方法
  • 实现更智能的RTO计算算法,如考虑RTT的历史变化趋势
  • 使用时间戳选项,提高RTT测量的准确性
重传歧义

当发生重传后,如果收到确认,无法确定这个确认是针对原始传输还是重传。这被称为重传歧义问题,它会影响RTT的测量和RTO的计算。

解决方法包括:

  • 使用Karn算法:忽略重传数据段的RTT测量
  • 使用时间戳选项:通过时间戳可以区分原始传输和重传的确认
重传风暴

在网络严重拥塞的情况下,大量的数据包可能会丢失,导致大规模的重传。如果所有连接同时进行重传,可能会加剧网络拥塞,形成重传风暴。

解决方法包括:

  • 实现随机化的重传退避
  • 与拥塞控制机制协同,在检测到严重拥塞时减少发送速率
  • 使用ECN(显式拥塞通知)等机制,提前感知网络拥塞
针对特定网络环境的优化

不同的网络环境(如有线网络、无线网络、卫星链路等)有不同的特性,标准的超时重传机制可能不是最优的。针对特定网络环境的优化包括:

  • 无线网络:区分拥塞丢包和无线链路丢包,避免不必要的拥塞控制
  • 高延迟网络:使用更大的初始窗口和更激进的窗口增长策略
  • 高带宽延迟积网络:使用更大的窗口和更精确的RTT测量

超时重传机制是TCP可靠传输的重要组成部分,它通过检测和恢复丢失的数据,确保了数据的完整传输。理解超时重传的原理和实现,对于优化网络应用和解决网络问题都有重要意义。

3.3 滑动窗口机制

滑动窗口机制是TCP流量控制和可靠传输的核心机制,它允许发送方在收到确认前发送多个数据段,提高了网络利用率,同时防止发送方发送过多数据导致接收方缓冲区溢出。

3.3.1 滑动窗口的基本概念

**滑动窗口(Sliding Window)**是一种流量控制机制,它定义了在任意时刻,发送方可以发送的数据量。窗口的大小是动态调整的,取决于接收方的处理能力和网络状况。

TCP使用两种窗口:

  • 发送窗口(Send Window):发送方维护的窗口,表示可以发送的数据量
  • 接收窗口(Receive Window):接收方维护的窗口,表示可以接收的数据量

滑动窗口机制的核心思想是:发送方可以在收到确认前发送多个数据段,但发送的数据量不能超过当前的窗口大小。随着数据的发送和确认,窗口会沿着数据流"滑动",这就是"滑动窗口"名称的由来。

3.3.2 滑动窗口的工作原理

发送窗口

发送窗口可以分为四个部分:

  1. 已发送且已确认:窗口左边界左侧的数据,这些数据已经成功传输,发送方可以释放相应的缓冲区
  2. 已发送但未确认:窗口内的左侧部分,这些数据已经发送,但还没有收到确认
  3. 未发送但允许发送:窗口内的右侧部分,这些数据可以立即发送
  4. 未发送且不允许发送:窗口右边界右侧的数据,这些数据只有在窗口滑动后才能发送

发送窗口的大小取决于接收方通告的接收窗口大小和发送方的拥塞窗口大小,取两者的较小值。

接收窗口

接收窗口也可以分为三个部分:

  1. 已接收且已确认:窗口左边界左侧的数据,这些数据已经成功接收并确认
  2. 未接收但允许接收:窗口内的数据,接收方准备好接收这些数据
  3. 未接收且不允许接收:窗口右边界右侧的数据,接收方暂时不能接收这些数据

接收窗口的大小取决于接收方的缓冲区大小和当前已使用的缓冲区空间。

窗口滑动过程

滑动窗口的工作过程如下:

  1. 初始状态

    • 发送方和接收方都有一个初始窗口大小
    • 发送方的窗口左边界是已确认的最高序列号加1(snd_una)
    • 发送方的窗口右边界是左边界加窗口大小(snd_una + snd_wnd)
  2. 发送数据

    • 发送方可以发送窗口内的所有数据
    • 每发送一个数据段,窗口内的可用空间减少
    • 发送方不能发送窗口外的数据
  3. 接收确认

    • 当发送方收到确认时,窗口左边界向右滑动
    • 滑动的距离等于新确认的数据量
    • 窗口右边界也相应向右滑动,允许发送更多数据
  4. 接收窗口更新

    • 接收方在发送确认时,会通告自己的接收窗口大小
    • 发送方根据接收方通告的窗口大小调整自己的发送窗口
    • 如果接收方的窗口大小为0,发送方会暂停发送数据,进入持续状态

这个过程确保了发送方不会发送超过接收方能够处理的数据量,实现了流量控制。

3.3.3 滑动窗口在Linux内核中的实现

在Linux内核中,滑动窗口机制的实现涉及多个数据结构和函数。以下是一些关键的实现细节:

TCP控制块中的窗口字段

TCP控制块(struct tcp_sock)包含了与滑动窗口相关的多个字段:

// file: include/linux/tcp.h
struct tcp_sock {// .../* 发送窗口相关 */u32 snd_una;    /* 已发送但未确认的第一个字节的序列号 */u32 snd_nxt;    /* 下一个要发送的字节的序列号 */u32 snd_wnd;    /* 发送窗口大小 */u32 snd_wl1;    /* 用于窗口更新的序列号 */u32 snd_wl2;    /* 用于窗口更新的确认号 */u32 write_seq;  /* 初始发送序列号 *//* 接收窗口相关 */u32 rcv_nxt;    /* 期望接收的下一个字节的序列号 */u32 rcv_wnd;    /* 接收窗口大小 */u32 rcv_wup;    /* 接收窗口更新点 */// ...
};
发送窗口的管理

发送窗口的大小由接收方通告的窗口大小和拥塞窗口大小共同决定:

// 简化版的tcp_current_mss函数
u32 tcp_wnd_end(const struct tcp_sock *tp)
{return tp->snd_una + min(tp->snd_wnd, tp->snd_cwnd);
}

当发送数据时,TCP会检查数据是否在发送窗口内:

// 简化版的tcp_write_xmit函数
static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle,int push_one, gfp_t gfp)
{// ...while ((skb = tcp_send_head(sk))) {// 检查是否在发送窗口内if (tcp_snd_wnd_test(tp, skb, mss_now))break;// 发送数据段tcp_transmit_skb(sk, skb, 1, gfp);// 更新发送序列号tp->snd_nxt = TCP_SKB_CB(skb)->end_seq;// ...}// ...
}
接收窗口的管理

接收窗口的大小取决于接收缓冲区的大小和当前已使用的空间:

// 简化版的tcp_select_window函数
u16 tcp_select_window(struct sock *sk)
{struct tcp_sock *tp = tcp_sk(sk);u32 cur_win = tcp_receive_window(tp);u32 new_win = __tcp_select_window(sk);// 确保窗口不会收缩if (new_win < cur_win) {new_win = cur_win;}// 限制窗口大小if (new_win > 65535)new_win = 65535;return new_win;
}

当接收数据时,TCP会检查数据是否在接收窗口内:

// 简化版的tcp_rcv_established函数
void tcp_rcv_established(struct sock *sk, struct sk_buff *skb,const struct tcphdr *th, unsigned int len)
{// ...// 检查序列号是否在接收窗口内if (!tcp_sequence(tp, TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq)) {// 序列号不在接收窗口内,丢弃数据段// ...goto discard;}// 处理数据tcp_data_queue(sk, skb);// 更新接收序列号tp->rcv_nxt = TCP_SKB_CB(skb)->end_seq;// 发送确认和窗口更新tcp_send_ack(sk);// ...
}
窗口更新

当接收方的窗口大小发生变化时,需要通知发送方。这通常通过发送ACK包实现:

// 简化版的tcp_send_ack函数
void tcp_send_ack(struct sock *sk)
{// ...// 构建ACK包buff = alloc_skb(MAX_TCP_HEADER, sk_gfp_atomic(sk, GFP_ATOMIC));if (!buff)return;// 设置TCP头部skb_reserve(buff, MAX_TCP_HEADER);tcp_init_nondata_skb(buff, tp->snd_una, TCPHDR_ACK);// 设置确认号th = tcp_hdr(buff);th->ack_seq = htonl(tp->rcv_nxt);// 设置窗口大小th->window = htons(tcp_select_window(sk));// 发送ACK包tcp_transmit_skb(sk, buff, 0, sk_gfp_atomic(sk, GFP_ATOMIC));// ...
}

当发送方收到窗口更新时,会更新自己的发送窗口:

// 简化版的tcp_ack函数
static bool tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag)
{// ...// 检查是否需要更新窗口if (tcp_may_update_window(tp, ack, ack_seq, nwin)) {// 更新发送窗口tcp_update_wl(tp, ack_seq, ack);tp->snd_wnd = nwin;// 检查是否可以发送更多数据tcp_fast_path_check(sk);}// ...
}

3.3.4 滑动窗口的实际应用

滑动窗口机制在TCP的实际应用中发挥着关键作用,它支持了多种重要功能:

流量控制

滑动窗口的主要作用是实现流量控制,防止发送方发送过多数据导致接收方缓冲区溢出。通过动态调整窗口大小,接收方可以控制数据的接收速率,适应自己的处理能力。

提高网络利用率

传统的停等协议(发送一个数据包,等待确认后再发送下一个)在高延迟网络中效率很低。滑动窗口允许发送方在收到确认前发送多个数据段,提高了网络利用率,特别是在高延迟网络中。

支持批量确认

滑动窗口机制允许接收方使用累积确认,即一个确认可以确认多个数据段。这减少了确认的数量,降低了网络开销。

与拥塞控制的协同

滑动窗口机制与拥塞控制机制协同工作,共同决定发送方的发送速率。拥塞窗口限制了发送方向网络中注入的数据量,防止网络拥塞;而接收窗口限制了发送方向接收方发送的数据量,防止接收方缓冲区溢出。

3.3.5 滑动窗口的常见问题与优化

窗口缩放问题

TCP头部中的窗口大小字段是16位的,最大值为65535字节。这在高带宽延迟积网络中可能不够用。为了解决这个问题,TCP引入了窗口缩放选项,允许将窗口大小乘以一个缩放因子(最大为14),从而支持最大约1GB的窗口。

在Linux内核中,窗口缩放的实现如下:

// 简化版的tcp_select_window函数
u16 tcp_select_window(struct sock *sk)
{// ...// 应用窗口缩放if (tp->rx_opt.rcv_wscale) {new_win = new_win >> tp->rx_opt.rcv_wscale;}// ...
}
零窗口问题

如果接收方的缓冲区已满,它会通告一个零窗口,告诉发送方暂停发送数据。当接收方处理了一些数据后,它需要通知发送方恢复发送。但如果这个窗口更新包丢失了,可能会导致死锁:发送方一直等待窗口更新,而接收方认为发送方已经知道窗口已经打开。

为了解决这个问题,TCP引入了零窗口探测机制:当发送方收到零窗口通告时,会启动一个持续定时器,定期发送一个字节的数据(称为窗口探测包),强制接收方重新通告窗口大小。

在Linux内核中,零窗口探测的实现如下:

// 简化版的tcp_send_probe0函数
void tcp_send_probe0(struct sock *sk)
{// ...// 构建探测包buff = alloc_skb(MAX_TCP_HEADER, sk_gfp_atomic(sk, GFP_ATOMIC));if (!buff)return;// 设置TCP头部skb_reserve(buff, MAX_TCP_HEADER);tcp_init_nondata_skb(buff, tp->snd_una - 1, TCPHDR_ACK);// 发送探测包tcp_transmit_skb(sk, buff, 0, sk_gfp_atomic(sk, GFP_ATOMIC));// 重新启动持续定时器inet_csk_reset_xmit_timer(sk, ICSK_TIME_PROBE0,min(icsk->icsk_rto << icsk->icsk_backoff, TCP_RTO_MAX),TCP_RTO_MAX);// ...
}
糊涂窗口综合症

糊涂窗口综合症(Silly Window Syndrome)是指接收方只处理了少量数据就急于通告窗口更新,或发送方收到小窗口更新就急于发送小数据段,导致网络中充斥着小数据段,降低了传输效率。

解决方法包括:

  • 接收方延迟窗口更新:只有当窗口大小达到一定阈值(如MSS或接收缓冲区的一半)时,才通告窗口更新
  • 发送方延迟发送:只有当可发送的数据量达到一定阈值(如MSS)时,才发送数据
  • Nagle算法:将多个小数据段合并成一个大数据段发送,减少网络中的小数据段数量

在Linux内核中,这些优化的实现如下:

// 简化版的tcp_nagle_test函数
static bool tcp_nagle_test(const struct tcp_sock *tp, const struct sk_buff *skb,unsigned int mss_now, int nonagle)
{// 检查是否启用Nagle算法if (nonagle & TCP_NAGLE_OFF)return true;// 如果有未确认的数据且数据大小小于MSS,延迟发送if ((nonagle & TCP_NAGLE_CORK) && tp->packets_out)return false;// 标准Nagle算法return (skb->len >= mss_now ||!tcp_packets_in_flight(tp) ||(TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN));
}
接收窗口自动调整

为了适应不同的网络环境和应用需求,现代TCP实现通常会自动调整接收窗口的大小。Linux内核实现了接收窗口自动调整机制,根据应用程序的读取速率和网络状况动态调整接收窗口的大小。

// 简化版的tcp_rcv_space_adjust函数
void tcp_rcv_space_adjust(struct sock *sk)
{// ...// 计算新的接收窗口大小if (tp->rcv_ssthresh < tp->window_clamp &&(tp->rcv_ssthresh <= tp->rcv_wnd ||(tp->rcv_wnd < tp->window_clamp &&tp->rcv_wnd < tp->rcv_ssthresh + 2 * tp->advmss))) {// 增加接收窗口tp->rcv_ssthresh = min(tp->rcv_ssthresh + tp->advmss, tp->window_clamp);tp->rcv_wnd = min(tp->rcv_wnd + tp->advmss, tp->window_clamp);}// ...
}

滑动窗口机制是TCP流量控制和可靠传输的核心机制,它通过动态调整窗口大小,实现了发送方和接收方之间的流量平衡。理解滑动窗口的原理和实现,对于优化网络应用和解决网络问题都有重要意义。

3.4 差错控制机制

差错控制是TCP可靠传输的重要组成部分,它确保了数据在传输过程中的完整性和正确性。TCP通过多种机制检测和处理传输过程中可能出现的错误,包括校验和、确认和重传、序列号和确认号以及超时机制等。

3.4.1 差错控制的基本概念

**差错控制(Error Control)**是指检测和纠正传输过程中可能出现的错误的机制。在网络通信中,数据可能会因为多种原因而损坏或丢失,如信号衰减、电磁干扰、设备故障等。差错控制机制的目标是确保接收方能够正确地接收发送方发送的数据,或者至少能够检测到错误并请求重传。

TCP的差错控制主要解决以下几类问题:

  • 位错误:数据在传输过程中的某些位被错误地改变
  • 丢包:数据包在传输过程中完全丢失
  • 重复:同一个数据包被接收多次
  • 乱序:数据包的到达顺序与发送顺序不同

3.4.2 TCP的差错控制机制

TCP使用多种机制来实现差错控制,主要包括:

校验和(Checksum)

TCP使用校验和来检测数据在传输过程中是否被损坏。校验和是对TCP头部、数据以及一个伪头部(包含源IP地址、目的IP地址、协议号和TCP长度)进行计算得出的。

校验和的计算方法是:将所有16位字进行反码求和,然后取反码。接收方使用相同的算法计算校验和,如果结果为0,则认为数据完整;否则,认为数据已损坏,将丢弃该段。

校验和能够检测出大多数的位错误,但它不是一个强校验机制,某些特定模式的错误可能无法被检测出。为了提高可靠性,TCP通常与底层的错误检测机制(如以太网的CRC校验)结合使用。

确认和重传

TCP使用确认和重传机制来处理丢包问题。当发送方发送数据后,会等待接收方的确认;如果在一定时间内没有收到确认,发送方会假定数据已丢失,并重新发送。

TCP使用累积确认机制,即确认号表示接收方已成功接收的所有数据的最高序列号加1。这意味着一个确认可以确认多个数据段,减少了确认的数量,但也可能导致不必要的重传。

为了提高效率,TCP还支持选择性确认(SACK)机制,允许接收方确认非连续的数据块,减少不必要的重传。

序列号和确认号

TCP使用序列号和确认号来检测和处理重复和乱序的数据包。每个数据段都有一个唯一的序列号,接收方可以根据序列号识别重复的数据段并丢弃它们,也可以根据序列号重新排序乱序到达的数据段。

序列号和确认号机制确保了数据的有序交付,即使底层网络可能导致数据包乱序到达。

超时机制

TCP使用超时机制来处理确认丢失的情况。当发送方在一定时间内没有收到确认时,会假定确认已丢失,并重新发送数据。

超时时间(RTO)是根据往返时间(RTT)的测量值动态计算的,以适应网络状况的变化。如果网络延迟增加,RTO会相应增加,减少不必要的重传;如果网络状况改善,RTO会逐渐减小,提高传输效率。

3.4.3 差错控制在Linux内核中的实现

在Linux内核中,TCP的差错控制机制的实现涉及多个函数和数据结构。以下是一些关键的实现细节:

校验和的计算和验证

TCP校验和的计算在tcp_v4_send_check函数中实现:

// 简化版的tcp_v4_send_check函数
void tcp_v4_send_check(struct sock *sk, struct sk_buff *skb)
{struct inet_sock *inet = inet_sk(sk);struct tcphdr *th = tcp_hdr(skb);// 计算校验和th->check = 0;th->check = tcp_v4_check(skb->len, inet->inet_saddr, inet->inet_daddr,csum_partial(th, skb->len, 0));
}

校验和的验证在tcp_v4_do_rcv函数中实现:

// 简化版的tcp_v4_do_rcv函数
int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
{// ...// 验证校验和if (!pskb_may_pull(skb, sizeof(struct tcphdr)) ||(skb_checksum_complete(skb))) {goto discard_it;}// ...
}
确认和重传的实现

TCP的确认机制在tcp_send_ack函数中实现:

// 简化版的tcp_send_ack函数
void tcp_send_ack(struct sock *sk)
{// ...// 构建ACK包buff = alloc_skb(MAX_TCP_HEADER, sk_gfp_atomic(sk, GFP_ATOMIC));if (!buff)return;// 设置TCP头部skb_reserve(buff, MAX_TCP_HEADER);tcp_init_nondata_skb(buff, tp->snd_una, TCPHDR_ACK);// 设置确认号th = tcp_hdr(buff);th->ack_seq = htonl(tp->rcv_nxt);// 发送ACK包tcp_transmit_skb(sk, buff, 0, sk_gfp_atomic(sk, GFP_ATOMIC));// ...
}

重传机制在tcp_retransmit_timer函数中实现:

// 简化版的tcp_retransmit_timer函数
void tcp_retransmit_timer(struct sock *sk)
{// ...// 检查是否有未确认的数据if (!tp->packets_out)goto out;// 检查是否超过最大重传次数if (tcp_write_timeout(sk))goto out;// 执行超时重传tcp_retransmit_skb(sk, tcp_write_queue_head(sk));// 更新RTO(指数退避)icsk->icsk_rto = min(icsk->icsk_rto << 1, TCP_RTO_MAX);// 重新启动重传定时器inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,icsk->icsk_rto, TCP_RTO_MAX);// ...out:// ...
}
选择性确认(SACK)的实现

TCP的选择性确认机制在tcp_sacktag_write_queue函数中实现:

// 简化版的tcp_sacktag_write_queue函数
int tcp_sacktag_write_queue(struct sock *sk, const struct sk_buff *ack_skb,u32 prior_snd_una)
{// ...// 解析SACK选项if (sack = TCP_SKB_CB(ack_skb)->sacked) {// 处理SACK块for (i = 0; i < num_sacks; i++) {start_seq = sack[i].start_seq;end_seq = sack[i].end_seq;// 标记已被选择性确认的数据段tcp_sacktag_one(sk, start_seq, end_seq, ...);}}// ...
}

3.4.4 差错控制的实际应用

TCP的差错控制机制在实际应用中发挥着关键作用,确保了数据的可靠传输,特别是在网络状况不佳的情况下。以下是一些实际应用场景:

处理网络丢包

在网络拥塞或链路质量差的情况下,数据包可能会丢失。TCP的确认和重传机制能够检测到这种丢失,并重新发送数据,确保数据的完整性。

处理网络乱序

在复杂的网络环境中,数据包可能会走不同的路径,导致乱序到达。TCP的序列号机制能够重新排序这些数据包,确保应用程序收到的数据与发送时的顺序相同。

处理数据损坏

数据在传输过程中可能会因为各种原因而损坏。TCP的校验和机制能够检测到这种损坏,并丢弃损坏的数据包,触发重传。

适应网络状况变化

通过动态调整RTO,TCP能够适应网络状况的变化。当网络延迟增加时,RTO会相应增加,减少不必要的重传;当网络状况改善时,RTO会逐渐减小,提高传输效率。

3.4.5 差错控制的常见问题与优化

校验和的局限性

TCP校验和是一个相对简单的错误检测机制,它可能无法检测出某些特定模式的错误。此外,校验和的计算也会增加CPU负担。

优化方法包括:

  • 使用硬件校验和卸载,减轻CPU负担
  • 结合底层的错误检测机制,如以太网的CRC校验
  • 在应用层实现更强的校验机制,如MD5或SHA-1校验
重传歧义问题

当发生重传后,如果收到确认,无法确定这个确认是针对原始传输还是重传。这被称为重传歧义问题,它会影响RTT的测量和RTO的计算。

解决方法包括:

  • 使用Karn算法:忽略重传数据段的RTT测量
  • 使用时间戳选项:通过时间戳可以区分原始传输和重传的确认
选择性确认的开销

选择性确认(SACK)机制可以提高重传效率,但它也增加了TCP头部的大小和处理复杂性。在某些资源受限的环境中,这可能是一个问题。

优化方法包括:

  • 根据网络状况动态启用或禁用SACK
  • 优化SACK的实现,减少处理开销
  • 在特定场景下使用其他机制,如前向纠错(FEC)
差错控制与拥塞控制的协同

TCP的差错控制机制与拥塞控制机制密切相关,它们共同决定了TCP的行为。在某些情况下,这两种机制可能会相互影响,导致性能下降。

优化方法包括:

  • 区分拥塞丢包和非拥塞丢包,采取不同的策略
  • 实现更智能的重传策略,如早期重传(Early Retransmit)
  • 使用显式拥塞通知(ECN)等机制,提前感知网络拥塞

TCP的差错控制机制是其可靠传输的重要组成部分,它通过多种机制检测和处理传输过程中可能出现的错误,确保了数据的完整性和正确性。理解差错控制的原理和实现,对于优化网络应用和解决网络问题都有重要意义。

相关文章:

计算机网络-TCP可靠传输机制

计算机网络-TCP可靠传输机制 3. TCP可靠传输机制3.1 序列号与确认号机制3.1.1 序列号与确认号的基本概念3.1.2 序列号与确认号的工作原理3.1.3 序列号与确认号在Linux内核中的实现TCP控制块中的序列号和确认号字段序列号的初始化发送数据时的序列号处理接收数据时的确认号处理 …...

计算机网络- 传输层安全性

传输层安全性 7. 传输层安全性7.1 传输层安全基础7.1.1 安全需求机密性&#xff08;Confidentiality&#xff09;完整性&#xff08;Integrity&#xff09;真实性&#xff08;Authenticity&#xff09;不可否认性&#xff08;Non-repudiation&#xff09; 7.1.2 常见安全威胁窃…...

【C++取经之路】lambda和bind

目录 引言 lambda语法 lambda捕获列表解析 1&#xff09;值捕获 2&#xff09;引用捕获 3&#xff09;隐式捕获 lambda的工作原理 lambda进阶用法 泛型lambda 立即调用 lambda 与 function bind语法 bind的调用逻辑 bind核心用途 绑定参数 调整参数顺序 bind的…...

AF3 ProteinDataset类的初始化方法解读

AlphaFold3 protein_dataset模块 ProteinDataset 类主要负责从结构化的蛋白质数据中构建一个可供模型训练/推理使用的数据集,ProteinDataset 类的 __init__ 方法用于初始化一个蛋白质数据集对象。 源代码: def __init__(self,dataset_folder,features_folder="./data/t…...

博客园账户注册全流程指南(附常见问题)

博客园账户注册全流程指南&#xff08;附常见问题&#xff09; 引言 博客园作为国内老牌技术社区&#xff0c;是程序员们分享知识、交流技术的圣地。本文将手把手教你完成从注册到开通博客的全流程&#xff0c;附常见问题解答&#xff0c;助你轻松开启技术博客之旅。 一、注…...

算法复习笔记

算法复习 最大公约数枚举abc反序数 模拟xxx定律打印数字菱形今年的第几天&#xff1f;vector完数VS盈数剩下的树 排序和查找顺序查找二分查找找位置 字符串统计单词浮点数加法 线性数据结构队列约瑟夫问题&#xff08;队列&#xff09;计算表达式&#xff08;栈&#xff09; 递…...

spring boot 引入fastjson,com.alibaba.fastjson不存在(Springboot-测试项目)

spring boot 引入fastjson&#xff0c;com.alibaba.fastjson不存在&#xff08;Springboot-测试项目&#xff09; 先解决最初的的包不找到问题&#xff0c;适用所有包找不到跟进。 <mirrors><!-- mirror| Specifies a repository mirror site to use instead of a g…...

新闻推荐系统(springboot+vue+mysql)含万字文档+运行说明文档

新闻推荐系统(springbootvuemysql)含万字文档运行说明文档 该系统是一个新闻推荐系统&#xff0c;分为管理员和用户两个角色。管理员模块包括个人中心、用户管理、排行榜管理、新闻管理、我的收藏管理和系统管理等功能。管理员可以通过这些功能进行用户信息管理、查看和编辑用…...

UE4 踩坑记录

1、Using git status to determine working set for adaptive non-unity build 我删除了一个没用的资源&#xff0c;结果就报这个错&#xff0c;原因就是这条命令导致的&#xff0c; 如果这个项目是git项目&#xff0c; ue编译时会优先通过 git status检查哪些文件被修改&#…...

【解决方案】vscode 不小心打开了列选择模式,选择时只能选中同一列的数据。

vscode 不小心打开了列选择模式&#xff0c;选择时只能选中同一列的数据。 解决方案&#xff1a; 1.通过命令面板关闭&#xff1a; 按下 Ctrl Shift P&#xff08;Windows/Linux&#xff09;或 Cmd Shift P&#xff08;macOS&#xff09;&#xff0c;输入 切换列选择模式…...

国标GB28181视频平台EasyCVR如何搭建汽车修理厂远程视频网络监控方案

一、背景分析 近年我国汽车保有量持续攀升&#xff0c;与之相伴的汽车保养维修需求也逐渐提高。随着社会经济的发展&#xff0c;消费者对汽车维修服务质量的要求越来越高&#xff0c;这使得汽车维修店的安全防范与人员管理问题面临着巨大挑战。 多数汽车维修店分布分散&#…...

【Go】windows下的Go安装与配置,并运行第一个Go程序

【Go】windows下的Go安装与配置&#xff0c;并运行第一个Go程序 安装环境&#xff1a;windows10 64位 安装版本&#xff1a;go1.16 windows/amd64 一、安装配置步骤 1.到官方网址下载安装包 https://golang.google.cn/dl/ 默认情况下 .msi 文件会安装在 c:\Go 目录下。可自行配…...

Linux 线程:从零构建多线程应用:系统化解析线程API与底层设计逻辑

线程 线程的概述 在之前&#xff0c;我们常把进程定义为 程序执行的实例&#xff0c;实际不然&#xff0c;进程实际上只是维护应用程序的各种资源&#xff0c;并不执行什么。真正执行具体任务的是线程。 那为什么之前直接执行a.out的时候&#xff0c;没有这种感受呢&#xf…...

榕壹云无人共享系统:基于SpringBoot+MySQL+UniApp的物联网共享解决方案

无人共享经济下的技术革新 随着无人值守经济模式的快速发展,传统共享设备面临管理成本高、效率低下等问题。榕壹云无人共享系统依托SpringBoot+MySQL+UniApp技术栈,结合物联网与移动互联网技术,为商家提供低成本、高可用的无人化运营解决方案。本文将详细解析该系统的技术架…...

技术书籍推荐(002):电子书免费下载

20. 利用Python进行数据分析 免费 电子书 PDF 下载 书籍简介&#xff1a; 本书聚焦于使用Python进行数据处理和分析。详细介绍了Python中用于数据分析的重要库&#xff0c;如NumPy&#xff08;提供高效的数值计算功能&#xff0c;包括数组操作、数学函数等&#xff09;、panda…...

安全序列(DP)

#include <bits/stdc.h> using namespace std; const int MOD1e97; const int N1e65; int f[N]; int main() {int n,k;cin>>n>>k;f[0]1;for(int i1;i<n;i){f[i]f[i-1]; // 不放桶&#xff1a;延续前一位的所有方案if(i-k-1>0){f[i](f[i]f[i-k…...

数据可视化 —— 堆形图应用(大全)

一、案例一&#xff1a;温度堆积图 # 导入 matplotlib 库中的 pyplot 模块&#xff0c;这个模块提供了类似于 MATLAB 的绘图接口&#xff0c; # 方便我们创建各种类型的可视化图表&#xff0c;比如折线图、柱状图、散点图等 import matplotlib.pyplot as plt # 导入 numpy 库&…...

利用 pyecharts 实现地图的数据可视化——第七次人口普查数据的2d、3d展示(关键词:2d 、3d 、map、 geo、涟漪点)

参考文档&#xff1a;链接: link_pyecharts 官方文档 1、map() 传入省份全称&#xff0c;date_pair 是列表套列表 [ [ ],[ ] … ] 2、geo() 传入省份简称&#xff0c;date_pair 是列表套元组 [ ( ),( ) … ] 1、准备数据 population_data&#xff1a;简称经纬度 population_da…...

字节跳动开源 LangManus:不止是 Manus 平替,更是下一代 AI 自动化引擎

当 “AI 自动化” 成为科技领域最炙手可热的关键词&#xff0c;我们仿佛置身于一场激动人心的变革前夜。各行各业都在翘首以盼&#xff0c;期待 AI 技术能够真正解放生产力&#xff0c;将人类从繁琐重复的工作中解脱出来。在这个充满无限可能的时代&#xff0c;字节跳动悄然发布…...

第十四届蓝桥杯大赛软件赛省赛C/C++ 大学 A 组真题

文章目录 1 幸运数题目描述&#xff1a;答案&#xff1a;4430091 代码&#xff1a; 2 有奖问答题目描述&#xff1a;重点&#xff1a;答案&#xff1a;8335366 代码&#xff1a; 3 平方差题目描述&#xff1a;思路&#xff1a;数学找规律代码&#xff1a; 4 更小的数题目描述&a…...

springboot+tabula解析pdf中的表格数据

场景 在日常业务需求中&#xff0c;往往会遇到解析pdf数据获取文本的需求&#xff0c;常见的做法是使用 pdfbox 来做&#xff0c;但是它只适合做一些简单的段落文本解析&#xff0c;无法处理表格这种复杂类型&#xff0c;因为单元格中的文本有换行的情况&#xff0c;无法对应到…...

静态链接part1

比较多这一部分&#xff0c;包含了编译和链接&#xff0c;书还没看完就先记录一下其中编译的一部分 编译 gcc编译分为预处理、编译、汇编、链接四个步骤 预处理 也称预编译&#xff0c;主要处理的是源代码文件中以“#”开始的预编译指令&#xff0c;这里简单讲一下规则&…...

golang通过STMP协议发送邮件功能详细操作

一.简介 在 Go 语言中接入 IMAP 和 SMTP 服务来进行邮件的发送和接收操作,可以通过使用一些现有的第三方库来简化操作,常见的库有 go-imap 和 gomail&#xff0c;它们可以帮助我们连接和操作 IMAP 邮箱&#xff08;读取邮件&#xff09;以及通过 SMTP 发送邮件 二.实现 1. IMA…...

分布式锁在秒杀场景中的Python实现与CAP权衡

目录 一、分布式锁的前世今生 二、秒杀系统的 “硬核” 挑战 三、Python 实现分布式锁的 “实战演练” Redis 实现:快准狠 ZooKeeper 实现:稳如老狗 数据库实现:老实本分 四、CAP 理论的 “三角恋” 五、性能优化的 “锦囊妙计” 锁粒度控制:粗细有道 超时机制:别…...

数据驱动的温暖守护:智慧康养平台如何实现 “千人千面” 的精准照护?

在当今数字化时代&#xff0c;七彩喜智慧康养平台借助数据的力量&#xff0c;正逐步打破传统养老服务模式的局限&#xff0c;实现 “千人千面” 的精准照护。 通过收集、分析和利用大量与老年人相关的数据&#xff0c;这些平台能够深入了解每位老人的独特需求&#xff0c;并据…...

基于SSM的校园美食交流系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…...

多线程进阶

进阶的内容&#xff0c;就关于线程的面试题为主了&#xff0c;涉及到的内容在工作中使用较少&#xff0c;但面试会考&#xff01;&#xff01;&#xff01; 锁的策略 加锁的过程中&#xff0c;在处理冲突的过程中&#xff0c;涉及到的一些不同的处理方法&#xff0c;此处的锁…...

聊一聊接口测试时遇到第三方服务时怎么办

目录 一、使用 Mock 或 Stub 模拟第三方服务 二、利用第三方服务的沙箱&#xff08;Sandbox&#xff09;环境 三、测试隔离与数据清理 四、处理异步回调 五、容错与异常测试 六、契约测试 在我们进行接口测试时&#xff0c;有的时候会遇到要调用第三方服务即外部的API&am…...

《Python星球日记》第22天:NumPy 基础

名人说&#xff1a;路漫漫其修远兮&#xff0c;吾将上下而求索。—— 屈原《离骚》 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 一、NumPy 简介1. 什么是 NumPy&#xff1f;为什么使用 NumPy&#xff1f;2. 安…...

Spring Boot 中 Bean 的生命周期详解

Spring Boot 中 Bean 的生命周期详解 一、引言 在 Spring Boot 应用中&#xff0c;Bean 是构成应用程序的基础组件。理解 Bean 的生命周期对于开发高效、稳定的 Spring Boot 应用至关重要。本文将深入探讨 Spring Boot 中 Bean 的完整生命周期过程。 二、Bean 生命周期的基本…...

结构化需求分析:功能、数据与行为的全景建模

目录 前言1 功能模型&#xff1a;数据流图&#xff08;DFD&#xff09;的结构与应用1.1 数据流图的基本构成要素1.2 数据流图的层次化设计1.3 数据流图的建模价值 2 数据模型&#xff1a;ER图揭示数据结构与关系2.1 ER图的基本组成2.2 建模过程与注意事项2.3 数据模型的价值体现…...

OpenCompass模型评估

OpenCompass面向大模型的开源方和使用者&#xff0c; 提供开源、高效、全面的大模型评测开放平台。 一、OpenCompass文档 1.基础安装 使用Conda准备 OpenCompass 运行环境&#xff1a; conda create --name opencompass python3.10 -y conda activate opencompass2. 安装 Op…...

基于51单片机语音实时采集系统

基于51单片机语音实时采集 &#xff08;程序&#xff0b;原理图&#xff0b;PCB&#xff0b;设计报告&#xff09; 功能介绍 具体功能&#xff1a; 系统由STC89C52单片机ISD4004录音芯片LM386功放模块小喇叭LCD1602按键指示灯电源构成 1.可通过按键随时选择相应的录音进行播…...

NeuroImage:膝关节炎如何影响大脑?静态与动态功能网络变化全解析

膝骨关节炎&#xff08;KOA&#xff09;是导致老年人活动受限和残疾的主要原因之一。这种疾病不仅引起关节疼痛&#xff0c;还会显著影响患者的生活质量。然而&#xff0c;目前对于KOA患者大脑功能网络的异常变化及其与临床症状之间的关系尚不清楚。 2024年4月10日&#xff0c;…...

高级java每日一道面试题-2025年4月01日-微服务篇[Nacos篇]-Nacos集群的数据一致性是如何保证的?

如果有遗漏,评论区告诉我进行补充 面试官: Nacos集群的数据一致性是如何保证的&#xff1f; 我回答: Nacos 集群数据一致性保障机制详解 在 Java 高级面试中&#xff0c;Nacos 集群的数据一致性保障是考察分布式系统核心能力的关键点。以下是 Nacos 通过多种机制和技术确保…...

阿里云 OSS 在 ZKmall开源商城的应用实践

ZKmall开源商城通过深度整合阿里云OSS&#xff08;对象存储服务&#xff09;&#xff0c;构建了高效、安全的文件存储与管理体系&#xff0c;支撑商品图片、用户上传内容等非结构化数据的存储与分发。结合阿里云OSS的技术特性与ZKmall的微服务架构&#xff0c;其实践方案可总结…...

【Linux】线程池与封装线程

目录 一、线程池&#xff1a; 1、池化技术&#xff1a; 2、线程池优点&#xff1a; 3、线程池应用场景&#xff1a; 4、线程池实现&#xff1a; 二、封装线程&#xff1a; 三、单例模式&#xff1a; 四、其他锁&#xff1a; 五、读者写者问题 一、线程池&#xff1a; …...

protobuf的应用

1.版本和引用 syntax "proto3"; // proto2 package tutorial; // package类似C命名空间 // 可以引用本地的&#xff0c;也可以引用include里面的 import "google/protobuf/timestamp.proto"; // 已经写好的proto文件是可以引用 我们版本选择pr…...

linux shell编程之条件语句(二)

目录 一. 条件测试操作 1. 文件测试 2. 整数值比较 3. 字符串比较 4. 逻辑测试 二. if 条件语句 1. if 语句的结构 (1) 单分支 if 语句 (2) 双分支 if 语句 (3) 多分支 if 语句 2. if 语句应用示例 (1) 单分支 if 语句应用 (2) 双分支 if 语句应用 (3) 多分支 …...

图论整理复习

回溯&#xff1a; 模板&#xff1a; void backtracking(参数) {if (终止条件) {存放结果;return;}for (选择&#xff1a;本层集合中元素&#xff08;树中节点孩子的数量就是集合的大小&#xff09;) {处理节点;backtracking(路径&#xff0c;选择列表); // 递归回溯&#xff…...

企业指标设计方法指南

该文档聚焦企业指标设计方法,适用于企业中负责战略规划、业务运营、数据分析、指标管理等相关工作的人员,如企业高管、部门经理、数据分析师等。 主要内容围绕指标设计展开:首先指出指标设计面临的困境,包括权责不清、口径不统一、缺乏标准规范、报表体系混乱、指标…...

AIP-217 不可达资源

编号217原文链接AIP-217: Unreachable resources状态批准创建日期2019-08-26更新日期2019-08-26 有时&#xff0c;用户可能会请求一系列资源&#xff0c;而其中某些资源暂时不可用。最典型的场景是跨集合读。例如用户可能请求返回多个上级位置的资源&#xff0c;但其中某个位置…...

SAP系统控制检验批

问题&#xff1a;同一批物料多检验批问题 现象&#xff1a;同一物料多采购订单同一天到货时&#xff0c;对其采购订单分别收货&#xff0c;导致系统产生多个检验批&#xff0c;需分别请检单、检验报告等&#xff0c;使质量部工作复杂化。 原因&#xff1a;物料主数据质量试图设…...

JavaScript 代码混淆与反混淆技术详解

一、代码混淆&#xff1a;让别人看不懂你的代码 混淆技术就是一种“代码伪装术”&#xff0c;目的是让别人很难看懂你的代码逻辑&#xff0c;从而保护你的核心算法或敏感信息。 1. 变量名压缩 原理&#xff1a;把变量名改成乱码&#xff0c;比如把calculatePrice改成a&#…...

Android studio | From Zero To One ——手机弹幕

===================================================== github:https://github.com/MichaelBeechan CSDN:https://blog.csdn.net/u011344545 ===================================================== 滚动显示 代码activity_main.xmlactivity_fullscreen.xmlAndroidManife…...

面向对象的需求分析与UML构造块详解

目录 前言1 面向对象的需求分析概述2 UML构造块概述3 UML事物详解3.1 结构事物&#xff08;Structural Things&#xff09;3.2 行为事物&#xff08;Behavioral Things&#xff09;3.3 分组事物&#xff08;Grouping Things&#xff09;3.4 解释事物&#xff08;Annotational T…...

LeetCode 2843.统计对称整数的数目:字符串数字转换

【LetMeFly】2843.统计对称整数的数目&#xff1a;字符串数字转换 力扣题目链接&#xff1a;https://leetcode.cn/problems/count-symmetric-integers/ 给你两个正整数 low 和 high 。 对于一个由 2 * n 位数字组成的整数 x &#xff0c;如果其前 n 位数字之和与后 n 位数字…...

RocketMQ深度百科全书式解析

​一、核心架构与设计哲学​ ​1. 设计目标​ ​海量消息堆积​&#xff1a;单机支持百万级消息堆积&#xff0c;适合大数据场景&#xff08;如日志采集&#xff09;。​严格顺序性​&#xff1a;通过队列分区&#xff08;Queue&#xff09;和消费锁机制保证局部顺序。​事务…...

A2A与MCP Server:AI智能体协作与工具交互的核心协议对比

A2A与MCP Server&#xff1a;AI智能体协作与工具交互的核心协议对比 摘要 在AI智能体技术爆发式增长的今天&#xff0c;谷歌的A2A协议与Anthropic的MCP协议正在重塑AI系统架构。本文通过协议栈分层模型、企业级架构设计案例及开发者实践指南三大维度&#xff0c;揭示二者在AI生…...

如何将网页保存为pdf

要将网页保存为PDF&#xff0c;可以按照以下几种方法操作&#xff1a; 1. 使用浏览器的打印功能 大多数现代浏览器&#xff08;如Chrome、Firefox、Edge等&#xff09;都支持将网页保存为PDF文件。步骤如下&#xff1a; 在 Google Chrome 中&#xff1a; 打开你想保存为PDF…...