TCP数据发送之发送窗口
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数据发送之发送窗口相关推荐
- 计算机网络课程设计——发送和接收TCP数据包以及发送和捕获ARP数据包
1.课程设计要求: 发送和接收TCP数据包:TCP是一种面向连接的.可靠的传输层协议.TCP协议工作在网络层IP协议的基础上.本课程设计的目的是设计一个发送和接收TCP数据包的程序,其功能是填充一个T ...
- linux C - TCP数据接收和发送示例
[推荐阅读] 浅谈linux 内核网络 sk_buff 之克隆与复制 深入linux内核架构--进程&线程 了解Docker 依赖的linux内核技术 1.client端(读) #includ ...
- python拦截tcp数据包_发送低级原始tcp数据包python
我最近一直在做一个原始数据包的程序.我们最近有一个关于生包的讲座,所以我一直在努力学习和做我的教授告诉我的事情.我的程序有问题,它出现了一个错误,说目标地址是必需的,它是原始的,所以我不想做socke ...
- 计算机网络课程设计:发送TCP数据包
此文章写于2021年6月29日 一.背景概述 TCP(传输控制协议)是一种面向连接的,可靠的传输层协议.TCP协议在网络层IP协议的基础上,向应用层用户进程提供可靠的,全双工的数据流传输. 二.设计内 ...
- 网络编程—使用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 ...
- TCP窗口调整与数据流控制以及病态窗口症状
TCP协议主要依赖不断调整窗口大小来保证数据收发吞吐率.在三次握手时,客户端会告诉服务器自己一次能接收数据量的大小,这就对应客户端的接收窗口以及服务器的发送端口.同理服务器也会告知客户端它一次能接收的 ...
- ZYNQ PL采集AD7606数据PS LWIP发送
一,传输设计: 1,PS 通过 AXI GPIO IP核启动 PL 不间断循环构造64bit 位宽的 0-1023 的数据,通过 AXI DMA IP 核,PS的 Slave AXI GP 接口传输至 ...
- 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 ...
- linux内核协议栈 TCP数据发送之发送窗口
目录 1 发送窗口概述 2 snd_una 和 snd_wnd 的更新 2.1 发送窗口初始化 2.1.1 客户端初始化 2.1.2 服务器端初始化 2.2 本地接收窗口 rcv_wnd 通告 2.2 ...
最新文章
- PS 图像尺寸|点阵格式图像|矢量格式图像|图像格式的选择
- leetcode算法题--出界的路径数★
- 验证 Swarm 数据持久性 - 每天5分钟玩转 Docker 容器技术(104)
- 【小松教你手游开发】【面试必读(编程基础)】堆和栈的区别(转过无数次的文章)...
- 【[SDOI2014]数数】
- 伯克利计算机科学研究生,加州大学伯克利分校
- 网络拓扑结构与静态特征
- IP实时传输协议RTP/RTCP详解
- Popclip的JSON格式化扩展
- 我就问你1MB和1Mb能一样吗?
- 基于matlab算法的可靠度分析,参考基于matlab算法的可靠度分析
- 百度php工程师面试题
- 【矩阵计算】QR分解-基于Householder变换
- 达梦DCA学习笔记202004
- APP设计之启动页和广告页
- java日志,需要知道的几件事(commons-logging,log4j,slf4j,logback)
- 【深度学习】参数量、模型大小、显存
- fread和fwrite
- 孩子立刻就不玩游戏了,因为他找到更好玩的东西!
- 面向开放词汇的目标检测ECCV2022
热门文章
- java.lang.NoClassDefFoundError与aspectjrt、aspectjweaver的联系
- 《白帽子讲Web安全》学习笔记
- 传统Tier1再加码“本土化”,国产供应商如何应对?
- 【转】【引用】春花秋月意阑珊李煜词选
- 常用天线 | 六种不同类型喇叭天线的介绍与仿真(附 HFSS 仿真文件下载)
- Python-菜鸟练习100题
- 嵌入式软件开发经典面试题
- 山东ISO14001需要准备哪些材料
- 如何用html制作明信片,制作书写明信片的动画效果
- 输入3.7V升压5V,3.7V转5V电路图芯片