TCP的发送过程由滑动窗口控制,而滑动窗口的大小受限于发送窗口和拥塞窗口,拥塞窗口由拥塞控制算法的代表,而发送窗口是流量控制算法的代表,这篇笔记记录了发送窗口相关的内容,包括发送窗口的初始化、更新、以及它是如何影响数据发送过程的。

1. 概述

TCP的发送窗口可以用下图表示:

如图所示,TCB中有三个成员和发送窗口强相关。

struct tcp_sock {...//下一个要发送的序号,即序号等于snd_nxt的数据还没有发送u32   snd_nxt;    /* Next sequence we send        *///已经发送,但是还没有被确认的最小序号,注意序号等于snd_una的数据已经发送,//最想收到的确认号要大于snd_una。但是有一个特殊情况,如果发送的所有数据都//已经被确认,那么snd_una将等于下一个要发送的数据,即snd_una代表的数据还//没有发送,见下面tcp_ack()更新snd_una就可以理解这一点了u32  snd_una;    /* First byte we want an ack for    *///发送窗口大小,以字节为单位,来源于输入段首部的窗口字段,即对端接收缓冲区的剩余大小u32   snd_wnd;    /* The window we expect to receive  *///记录到目前为止对端通告过的窗口的最大值,可以代表对端接收缓冲区的最大值u32   max_window; /* Maximal window ever seen from peer   *///写系统调用一旦成功返回,说明数据一被TCP协议接收,这时就要为每一个数据分配一个序号,//write_seq就是下一个要分配的序号,其初始值由secure_tcp_sequence_number()基于//算法生成。注意等于write_seq的序号还没有被分配u32   write_seq;  /* Tail(+1) of data held in tcp send buffer */
...
};

2. snd_una和snd_wnd的更新

snd_una是发送窗口的左边界,如果该字段更新,即使发送窗口大小snd_wnd没有发生变化,整个发送窗口也会前移,这样从流量控制的角度,就可以发送更多的数据(是否真的可以发送,还要考虑拥塞窗口等其它因素)。

2.1 初始化

可以想的到,snd_una的初始化一定发生在第一个数据段发送过程中,而snd_wnd的初始化应该是发生在第一个输入段处理过程中,所以需要客户端和服务器端分开来看。

2.1.1 客户端初始化

客户端对snd_una的初始化当然是发生在SYN段的发送过程中,相关代码如下:

int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
{...//选择初始发送序号if (!tp->write_seq)tp->write_seq = secure_tcp_sequence_number(inet->saddr,inet->daddr,inet->sport,usin->sin_port);
...
}
static void tcp_connect_init(struct sock *sk)
{...//发送窗口大小要从输入段首部的窗口字段获取,这时还没有任何输入段,先初始化为0tp->snd_wnd = 0;//初始化snd_una为第一个序号,该函数之后write_seq将会分配给SYN段tp->snd_una = tp->write_seq;
...
}

对snd_wnd的初始化发生在收到SYN+ACK段时,相关代码如下:

static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb,struct tcphdr *th, unsigned len)
{...if (th->ack) {...tp->snd_wnd = ntohs(th->window);
...}
}

2.1.2 服务器端初始化

正面理解的话,服务器端对snd_una的初始化应该是发生在发送SYN+ACK段时,但是实际上不是,而是发生在收到第三次握手的ACK段时。如笔记TCP之服务器端收到ACK包所述,三次握手完成后,创建了子套接字,然后在tcp_child_process()中会继续调用tcp_rcv_state_process()处理ACK报文,代码如下:

int tcp_child_process(struct sock *parent, struct sock *child,struct sk_buff *skb)
{int ret = 0;int state = child->sk_state;//如果用户进程没有锁住child,则让child重新处理该ACK报文,这可以让child//套接字由TCP_SYN_RECV迁移到TCP_ESTABLISH状态if (!sock_owned_by_user(child)) {//见下文ret = tcp_rcv_state_process(child, skb, tcp_hdr(skb),skb->len);/* Wakeup parent, send SIGIO *///child套接字状态发生了迁移,唤醒监听套接字上的进程,可能由于调用accept()而blockif (state == TCP_SYN_RECV && child->sk_state != state)parent->sk_data_ready(parent, 0);} else {/* Alas, it is possible again, because we do lookup* in main socket hash table and lock on listening* socket does not protect us more.*///缓存该skb后续处理sk_add_backlog(child, skb);}bh_unlock_sock(child);sock_put(child);return ret;
}int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,struct tcphdr *th, unsigned len)
{.../* step 5: check the ACK field */if (th->ack) {int acceptable = tcp_ack(sk, skb, FLAG_SLOWPATH);switch (sk->sk_state) {case TCP_SYN_RECV:if (acceptable) {...tcp_set_state(sk, TCP_ESTABLISHED);//用ACK段中的确认号初始化本端的snd_unatp->snd_una = TCP_SKB_CB(skb)->ack_seq;//用输入报文的窗口字段初始化发送窗口大小tp->snd_wnd = ntohs(th->window) <<tp->rx_opt.snd_wscale;
...}break;
...}//end of switch()} elsegoto discard;
...return 0;
}

2.2 传输过程中更新

显然,数据传输过程中,应该在收到ACK后更新snd_una和snd_wnd。如果输入段中携带了ACK,最终都会有tcp_ack()处理确认相关的内容,相关的代码如下:

static int tcp_ack(struct sock *sk, struct sk_buff *skb, int flag)
{...u32 prior_snd_una = tp->snd_una;u32 ack = TCP_SKB_CB(skb)->ack_seq;
...if (!(flag & FLAG_SLOWPATH) && after(ack, prior_snd_una)) {...//快速路径情况,用ack更新snd_una,由于快速路径,所以通告的窗口大小一定//没有发生变化,所以不需要更新snd_wndtp->snd_una = ack;flag |= FLAG_WIN_UPDATE;
...} else {...//慢速路径下,调用函数更新窗口flag |= tcp_ack_update_window(sk, skb, ack, ack_seq);
...}
...
}/* Update our send window.** Window update algorithm, described in RFC793/RFC1122 (used in linux-2.2* and in FreeBSD. NetBSD's one is even worse.) is wrong.*/
static int tcp_ack_update_window(struct sock *sk, struct sk_buff *skb, u32 ack,u32 ack_seq)
{struct tcp_sock *tp = tcp_sk(sk);int flag = 0;u32 nwin = ntohs(tcp_hdr(skb)->window);if (likely(!tcp_hdr(skb)->syn))nwin <<= tp->rx_opt.snd_wscale;if (tcp_may_update_window(tp, ack, ack_seq, nwin)) {flag |= FLAG_WIN_UPDATE;tcp_update_wl(tp, ack, ack_seq);if (tp->snd_wnd != nwin) {//更新发送窗口大小tp->snd_wnd = nwin;/* Note, it is the only place, where* fast path is recovered for sending TCP.*/tp->pred_flags = 0;tcp_fast_path_check(sk);//如果通告的最大接收窗口发生变化,更新max_windowif (nwin > tp->max_window) {tp->max_window = nwin;tcp_sync_mss(sk, inet_csk(sk)->icsk_pmtu_cookie);}}}//用ack更新snd_unatp->snd_una = ack;return flag;
}

3. 发送窗口对发送过程的影响

这里要明白的是,发送窗口是实现流量控制的关键,它影响的只有新数据的发送过程,与重传无关,因为重传的数据一定是在对端接收能力之内。

从TCP之数据发送(二)中有看到新数据发送的两个关键函数tcp_write_xmit()和tcp_push_one(),而且二者非常相似,参考之前的笔记中分析的tcp_snd_wnd_test()和tcp_mss_split_point()就可以明白发送窗口是如何影响发送过程的。

TCP数据发送之发送窗口相关推荐

  1. 计算机网络课程设计——发送和接收TCP数据包以及发送和捕获ARP数据包

    1.课程设计要求: 发送和接收TCP数据包:TCP是一种面向连接的.可靠的传输层协议.TCP协议工作在网络层IP协议的基础上.本课程设计的目的是设计一个发送和接收TCP数据包的程序,其功能是填充一个T ...

  2. linux C - TCP数据接收和发送示例

    [推荐阅读] 浅谈linux 内核网络 sk_buff 之克隆与复制 深入linux内核架构--进程&线程 了解Docker 依赖的linux内核技术 1.client端(读) #includ ...

  3. python拦截tcp数据包_发送低级原始tcp数据包python

    我最近一直在做一个原始数据包的程序.我们最近有一个关于生包的讲座,所以我一直在努力学习和做我的教授告诉我的事情.我的程序有问题,它出现了一个错误,说目标地址是必需的,它是原始的,所以我不想做socke ...

  4. 计算机网络课程设计:发送TCP数据包

    此文章写于2021年6月29日 一.背景概述 TCP(传输控制协议)是一种面向连接的,可靠的传输层协议.TCP协议在网络层IP协议的基础上,向应用层用户进程提供可靠的,全双工的数据流传输. 二.设计内 ...

  5. 网络编程—使用C语言实现发送TCP数据包,以命令行形式运行:SendTCP source_ip source_port dest_ip dest_port;(原理和常见错误分析)

    任务要求: 1.以命令行形式运行:SendTCP source_ip source_port dest_ip dest_port: 2.头部参数自行设定,数据字段为"This is my h ...

  6. TCP窗口调整与数据流控制以及病态窗口症状

    TCP协议主要依赖不断调整窗口大小来保证数据收发吞吐率.在三次握手时,客户端会告诉服务器自己一次能接收数据量的大小,这就对应客户端的接收窗口以及服务器的发送端口.同理服务器也会告知客户端它一次能接收的 ...

  7. ZYNQ PL采集AD7606数据PS LWIP发送

    一,传输设计: 1,PS 通过 AXI GPIO IP核启动 PL 不间断循环构造64bit 位宽的 0-1023 的数据,通过 AXI DMA IP 核,PS的 Slave AXI GP 接口传输至 ...

  8. c语言sgoto 标志位,如何在Go中设置TCP数据包的“不分段”标志位?(How to set “don't fragment” flag bit for TCP packet in Go?)...

    如何在Go中设置TCP数据包的"不分段"标志位?(How to set "don't fragment" flag bit for TCP packet in ...

  9. linux内核协议栈 TCP数据发送之发送窗口

    目录 1 发送窗口概述 2 snd_una 和 snd_wnd 的更新 2.1 发送窗口初始化 2.1.1 客户端初始化 2.1.2 服务器端初始化 2.2 本地接收窗口 rcv_wnd 通告 2.2 ...

最新文章

  1. PS 图像尺寸|点阵格式图像|矢量格式图像|图像格式的选择
  2. leetcode算法题--出界的路径数★
  3. 验证 Swarm 数据持久性 - 每天5分钟玩转 Docker 容器技术(104)
  4. 【小松教你手游开发】【面试必读(编程基础)】堆和栈的区别(转过无数次的文章)...
  5. 【[SDOI2014]数数】
  6. 伯克利计算机科学研究生,加州大学伯克利分校
  7. 网络拓扑结构与静态特征
  8. IP实时传输协议RTP/RTCP详解
  9. Popclip的JSON格式化扩展
  10. 我就问你1MB和1Mb能一样吗?
  11. 基于matlab算法的可靠度分析,参考基于matlab算法的可靠度分析
  12. 百度php工程师面试题
  13. 【矩阵计算】QR分解-基于Householder变换
  14. 达梦DCA学习笔记202004
  15. APP设计之启动页和广告页
  16. java日志,需要知道的几件事(commons-logging,log4j,slf4j,logback)
  17. 【深度学习】参数量、模型大小、显存
  18. fread和fwrite
  19. 孩子立刻就不玩游戏了,因为他找到更好玩的东西!
  20. 面向开放词汇的目标检测ECCV2022

热门文章

  1. java.lang.NoClassDefFoundError与aspectjrt、aspectjweaver的联系
  2. 《白帽子讲Web安全》学习笔记
  3. 传统Tier1再加码“本土化”,国产供应商如何应对?
  4. 【转】【引用】春花秋月意阑珊李煜词选
  5. 常用天线 | 六种不同类型喇叭天线的介绍与仿真(附 HFSS 仿真文件下载)
  6. Python-菜鸟练习100题
  7. 嵌入式软件开发经典面试题
  8. 山东ISO14001需要准备哪些材料
  9. 如何用html制作明信片,制作书写明信片的动画效果
  10. 输入3.7V升压5V,3.7V转5V电路图芯片