TCP的核心系列 — ACK的处理(二)
本文主要内容:tcp_ack()中的一些细节,如发送窗口的更新、持续定时器等。
内核版本:3.2.12
Author:zhangskd @ csdn
发送窗口的更新
什么时候需要更新发送窗口呢?
(1)确认了新的数据
(2)条件1不成立,ACK段的序号是最新的。
这表示虽然ACK段没有确认了新的数据,但是它携带了新数据。
(3)条件1和2都不成立,通告窗口变大。
ACK既没有确认了新的数据,序号也不是最新的。
虽然如此,但是如果对端的接收窗口变大,我们还是要更新发送窗口。
此时ack_seq必须等于snd_wl1,而不能小于,因为那可能是乱序的。
判断是否要更新发送窗口:
/* Check that window update is acceptable.
* The function assumes that snd_una <= ack <= snd_nxt.
* ack和ack_seq分别表示ACK段的确认序号和序号,不要混淆了:)
*/
static inline int tcp_may_update_window(const struct tcp_sock *tp, const u32 ack,
const u32 ack_seq, const u32 nwin)
{
return after(ack, tp->snd_una) || after(ack_seq, tp->snd_wl1) ||
(ack_seq == tp->snd_wl1 && nwin > tp->snd_wnd);
}
更新函数:
/* Update our send window.
* Window update algorithm.
*/
static int tcp_ack_update_window(struct sock *sk, const struct sk_buff, u32 ack, u32 ack_seq)
{
struct tcp_sock *tp = tcp_sk(sk);
int flag = 0;
u32 nwin = ntohs(tcp_hdr(skb)->window); /* 通告窗口*/ /* 在发送syn包时,通告窗口没有乘窗口扩大因子*/
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_seq); /* 记录更新发送窗口的ACK段序号*/ 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); /* Update maximal window ever seen from peer */
if (nwin > tp->max_window) {
tp->max_window = nwin; /* 更新见过的最大接收窗口*/
tcp_sync_mss(sk, inet_csk(sk)->icsk_pmtu_cookie); /* 更新MSS */
}
}
} tp->snd_una = ack; /* 更新发送窗口左端*/
return flag;
}
持续定时器
持续定时器在对端通告接收窗口为0,阻止TCP继续发送数据时设定。由于允许TCP继续发送数据的窗口更新
有可能丢失,因此,如果TCP有数据要发送,而对端通告窗口为0,则持续定时器启动,超时后向对端发送1
字节的数据,以判断对端接收窗口是否已经打开。
#define ICSK_TIME_PROBE0 3 /* Zero window probe timer */ static void tcp_ack_probe(struct sock *sk)
{
const struct tcp_sock *tp = tcp_sk(sk);
struct inet_connection_sock *icsk = inet_csk(sk); /* Was it a usable window open ?
* 对端是否有足够的接收缓存,即我们能否发送一个包。
*/
if (! after(TCP_SKB_CB(tcp_send_head(sk))->end_seq, tcp_wnd_end(tp))) {
icsk->icsk_backoff = 0; /* 清除退避指数 */
inet_csk_clear_xmit_timer(sk, ICSK_TIME_PROBE0); /* 清除持续定时器*/ /* Socket must be waked up by subsequent tcp_data_snd_check().
* This function is not for random using!
*/ } else { /* 否则根据退避指数重置持续定时器*/
inet_csk_reset_xmit_timer(sk, ICSK_TIME_PROBE0,
min(icsk->icsk_rto << icsk->icsk_backoff, TCP_RTO_MAX), TCP_RTO_MAX);
}
}
返回发送窗口的最后一个字节序号:
/* Returns end sequence number of the receiver's advertised window */
static inline u32 tcp_wnd_end(const struct tcp_sock *tp)
{
return tp->snd_una + tp->snd_wnd;
}
flag标志
#define FLAG_SND_UNA_ADVANCED 0x400 /* Snd_una was changed (!= FLAG_DATA_ACKED)*/
#define FLAG_DATA 0x01 /* Incoming frame contained data. 接收的ACK段有负荷,不是纯ACK段*/
#define FLAG_WIN_UPDATE 0x02 /* Incoming ACK was a window update. 接收的ACK段更新了发送窗口*/ #define FLAG_DATA_ACKED 0x04 /* This ACK acknowledged new data.*/
#define FLAG_SYN_ACKED 0x10 /* This ACK acnowledged SYN. */
#define FLAG_ACKED (FLAG_DATA_ACKED | FLAG_SYN_ACKED) #define FLAG_NOT_DUP (FLAG_DATA | FLAG_WIN_UPDATE | FLAG_ACKED) #define FLAG_DATA_SACKED 0x20 /* New SACK. */
#define FLAG_ECE 0x40 /* ECE in this ACK .*/
#define FLAG_CA_ALERT (FLAG_DATA_SACKED | FLAG_ECE) #define FLAG_SACK_RENEGING 0x2000 /*snd_una advanced to a sacked seq */ #define FLAG_SLOWPATH 0x100 /* Do not skip RFC checks for window update. 表示在慢速路径中*/
sysctl_tcp_abc选项:
“Controls Appropriate Byte Count defined in RFC3465. If set to 0 then does congestion avoid once
per ACK. 1 is conservative value, and 2 is more aggressive. The default value is 1.”
默认值应该是0,即对每个ACK都进行拥塞避免。
拥塞控制
网络中数据包个数的计算:
/* This determines how many packets are "in the network" to the best of our knowledge.
* In many cases it is conservative, but where detailed information is available from the
* receiver (via SACK blocks etc.) we can make more aggressive calculations.
*
* Use this for decisions involving congestion control, use just
* tp->packets_out to determine if the send queue is empty or not.
*
* Read this equation as:
* packets sent once on transmission queue MINUS
* packets left network, but not honestly ACKde yet PLUS
* packets fast retransmitted
*/
static inline unsigned int tcp_packets_in_flight(const struct tcp_sock *tp)
{
return tp->packets_out - tcp_left_out(tp) + tp->retrans_out;
}
判断一个ACK是否可疑:
返回0表示ACK正常,返回1表示可疑
什么算是可疑的呢?
如果已经处于拥塞状态,则会显示可疑;
如果此ACK为拥塞信号,则会显示可疑。
可疑的条件(或):
(1) 不属于以下四种(FLAG_NOT_DUP):
FLAG_DATA:接收的ACK段是负荷数据携带的
FLAG_WIN_UPDATE:接收的ACK段更新了发送窗口
FLAG_DATA_ACKED:接收的ACK确认了新的数据
FLAG_SYN_ACKED:接收的ACK确认了SYN段
(2) 属于以下两种(FLAG_CA_ALERT):
FLAG_DATA_SACKED:是新的SACK
FLAG_ECE:在ACK中存在ECE标志,显示收到拥塞通知
(3) 拥塞状态不为Open
static inline int tcp_ack_is_dubious(const struct sock *sk, const int flag)
{
return !(flag & FLAG_NOT_DUP) || (flag & FLAG_CA_ALERT) ||
inet_csk(sk)->icsk_ca_state != TCP_CA_Open;
}
增加拥塞窗口:
返回1表示可以增加cwnd,返回0便是不能增加。
那么在什么情况下可以增加cwnd呢?
下面几个条件必须同时成立才能增加cwnd:
(1) ACK中没有ECE标志,或者,处于慢启动阶段
(2) 不能处于Recovery、CWR状态
static inline int tcp_may_raise_cwnd(const struct sock *sk, const int flag)
{
const struct tcp_sock *tp = tcp_sk(sk);
return ( !(flag & FLAG_ECE) || tp->snd_cwnd < tp->snd_ssthresh) &&
!((1 << inet_csk(sk)->icsk_ca_state) & (TCPF_CA_Recovery | TCPF_CA_CWR));
}
拥塞窗口的AI接口:
static void tcp_cong_avoid (struct sock *sk, u32 ack, u32 in_flight)
{
const struct inet_connection_sock *icsk = inet_csk(sk);
icsk->icsk_ca_ops->cong_avoid(sk, ack, in_flight);
tcp_sk(sk)->snd_cwnd_stamp = tcp_time_stamp;
}
TCP的核心系列 — ACK的处理(二)的更多相关文章
- TCP的核心系列 — ACK的处理(一)
TCP发送数据包后,会收到对端的ACK.通过处理ACK,TCP可以进行拥塞控制和流控制,所以 ACK的处理是TCP的一个重要内容.tcp_ack()用于处理接收到的ACK. 本文主要内容:TCP接收A ...
- TCP的核心系列 — SACK和DSACK的实现(二)
和18版本相比,37版本的SACK和DSACK的实现做了很多改进,最明显的就是需要遍历的次数少了, 减少了CPU的消耗.37版的性能提升了,代码有大幅度的改动,逻辑也更加复杂了. 本文主要内容:37版 ...
- TCP的核心系列 — 重传队列的更新和时延的采样(二)
在tcp_clean_rtx_queue()中,并非对每个ACK都进行时延采样.是否进行时延采样,跟这个ACK是否为 重复的ACK.这个ACK是否确认了重传包,以及是否使用时间戳选项都有关系. 本文主 ...
- TCP的核心系列 — SACK和DSACK的实现(一)
TCP的实现中,SACK和DSACK是比较重要的一部分. SACK和DSACK的处理部分由Ilpo Järvinen (ilpo.jarvinen@helsinki.fi) 维护. tcp_ack() ...
- TCP的核心系列 — SACK和DSACK的实现(三)
不论是18版,还是37版,一开始都会从TCP的控制块中取出SACK选项的起始地址. SACK选项的起始地址是保存在tcp_skb_cb结构的sacked项中的,那么这是在什么时候做的呢? SACK块并 ...
- TCP的核心系列 — SACK和DSACK的实现(七)
我们发送重传包时,重传包也可能丢失,如果没有检查重传包是否丢失的机制,那么只能依靠超时来恢复了. 37版本把检查重传包是否丢失的部分独立出来,这就是tcp_mark_lost_retrans(). 在 ...
- TCP的核心系列 — SACK和DSACK的实现(六)
上篇文章中我们主要说明如何skip到一个SACK块对应的开始段,如何walk这个SACK块包含的段,而没有涉及到 如何标志一个段的记分牌.37版本把给一个段打标志的内容独立出来,这就是tcp_sack ...
- TCP的核心系列 — SACK和DSACK的实现(四)
和18版本不同,37版本把DSACK的检测部分独立出来,可读性更好. 37版本在DSACK的处理中也做了一些优化,对DSACK的两种情况分别进行处理. 本文主要内容:DSACK的检测.DSACK的处理 ...
- TCP的核心系列 — 重传队列的更新和时延的采样(一)
重传队列实际上就是发送队列(sk->sk_write_queue),保存着发送且未确认的数据段. 当有新的数据段被确认时,需要把这些段从重传队列中删除,同时更新一些变量,包括 packets_o ...
随机推荐
- 如何编写入门级redis客户端
概述 Redis是开源的.基于内存的数据结构存储系统,可用作数据库.缓存以及消息代理方面.Redis支持许多种数据结构,并内置了丰富的诸如冗余.脚本.事务.持久化等功能,深受业界喜爱,被各种业务系统广 ...
- popupwindow中EditText获取焦点后自动弹出软键盘
关于popupwindow中EditText获取焦点后自动弹出软键盘的问题,玩过手机qq或空间的童鞋应该知道,再点击评论时会弹出一个编辑框,并且伴随软键盘一起弹出是不是很方便啊,下面我们就来讲一下实现 ...
- Struts1基础、使用Struts实现登录、使用Struts HTML标签简化开发
Struts 1基础 为什么重拾Struts 1 曾经是最主流的MVC框架 市场份额依然很大 很多遗留系统中依旧使用 维护和升级都需要熟悉Struts 1 与Struts 2相比 编码.配置繁琐 侵入 ...
- 【伯乐在线】最值得阅读学习的 10 个 C 语言开源项目代码
原文出处: 平凡之路的博客 欢迎分享原创到伯乐头条 伯乐在线注:『阅读优秀代码是提高开发人员修为的一种捷径』http://t.cn/S4RGEz .之前@伯乐头条 曾发过一条微博:『C 语言进阶有 ...
- 1.2、Android Studio为新设备创建一个模块
模块为你的应用的源码.资源文件和app level设置(比如AndroidManifest.xml)提供了一个容器.每个模块可以独立的构建.测试和调试. 通过使用模块,Android Studio可以 ...
- UNIX环境高级编程——初始化一个守护进程
#include <stdio.h> #include <stdlib.h> #include <signal.h> #include <unistd.h&g ...
- EBS系统克隆
术语 克隆是对已有的Oracle应用系统创建一份拷贝的过程.克隆一个Oracle应用系统有几种不同的情况,包括: l 标准克隆 – 复制一个已有的Oracle应用系统生成一份拷贝,例如对生产 ...
- Xcode7.2中如何添加一个Empty Application模板
大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) Xcode 6.0正式版之后已经没有所谓的Empty Appl ...
- Day 21:Docker 入门教程
几个月以前,红帽(Red Hat)宣布了在 Docker 技术上和 dotCloud 建立合作关系.在那时候,我并没有时间去学习关于 Docker 的知识,所以在今天,趁着这个 30 天的挑战,我决定 ...
- 文章标题 Oracle数据库中dual表使用
一. 业务场景 业务流程需要进行写入和更新的比较,所以有原表和历史表. 要求表中的ID唯一性,以及两张表的ID关联,另外后续可能数据库会进行迁移 二.方案选择 方案一:id设置为int型自增长. 这种 ...