3.10内核,反向合入4.9的bbr。

最近分析bbr的时候,收集了线上的一些报文,其中有一个疑问一直在我脑海里面,如下:

本身处于delay_ack状态的客户端,大概40ms回复一个delay_ack,当收到一个490字节的小包之后,立刻回复了ack。且不止出现,是有规律的出现:

我是如何确定这个ack一定是打破了delay_ack的呢,除了在时间上和发包的时间相隔很短,我还特意确认了一下,之后报文的ack是否立刻回复的,结果确定

都是立刻回复的,也就是进入了quick_ack的模式,回复快速ack的数量也刚好是16,因为 tcp_init_nondata_skb(buff, tcp_acceptable_seq(sk), TCPHDR_ACK);

中,对shinfo->gso_segs = 1;这样在减少quick阈值的时候,每次 tcp_event_ack_sent 只是将quick减去1,这样就是说,一旦打破delay_ack,那么至少两个,

至多16个quick ack,也就是符合代码:

icsk->icsk_ack.quick = min(quickacks, TCP_MAX_QUICKACKS);

根据接收窗口和mss,以及

/* Maximal number of ACKs sent quickly to accelerate slow-start. */
#define TCP_MAX_QUICKACKS   16U
我铁定确定了这个是打破了原本的delay_ack.它既不属于乱序报文,又没有out of window,且收包窗口也没有变化,也不是收到一个已经被ack过的报文,
且乱序队列中并没有数据,为啥它就能打破delay_ack呢?你说它到底满足哪一条呢?
/*
* Check if sending an ack is needed.
*/
static void __tcp_ack_snd_check(struct sock *sk, int ofo_possible)
{
struct tcp_sock *tp = tcp_sk(sk); /* More than one full frame received... */
if (((tp->rcv_nxt - tp->rcv_wup) > inet_csk(sk)->icsk_ack.rcv_mss &&
/* ... and right edge of window advances far enough.
* (tcp_recvmsg() will send ACK otherwise). Or...
*/
__tcp_select_window(sk) >= tp->rcv_wnd) ||
/* We ACK each frame or... */
tcp_in_quickack_mode(sk) ||
/* We have out of order data. */
(ofo_possible && !RB_EMPTY_ROOT(&tp->out_of_order_queue))) {
/* Then ack it now */
tcp_send_ack(sk);
} else {
/* Else, send delayed ack. */
tcp_send_delayed_ack(sk);
}
}

然后我搜索代码,看什么时候调用 tcp_enter_quickack_mode,发现没有收获,这个包不满足条件。

这个包的神奇之处在哪?走查了delay_ack打破的条件,没法理解这个代码逻辑,关于delay_ack的出现场景,在另一篇博客中有描述《https://www.cnblogs.com/10087622blog/p/10315410.html》

我点击这个报文详细分析:

发现它和其他报文的区别是,它带了push标志,带了走查了代码,也没看出来,为啥push标志的报文会在delay_ack的情况下,能立刻发送ack。

最后,再回到报文,看到一点, 那就是这个小包与上一个ack之间的间隔为230ms左右,直觉感觉这个时间偏大,然后走查收包的代码:

/* There is something which you must keep in mind when you analyze the
* behavior of the tp->ato delayed ack timeout interval. When a
* connection starts up, we want to ack as quickly as possible. The
* problem is that "good" TCP's do slow start at the beginning of data
* transmission. The means that until we send the first few ACK's the
* sender will sit on his end and only queue most of his data, because
* he can only send snd_cwnd unacked packets at any given time. For
* each ACK we send, he increments snd_cwnd and transmits more of his
* queue. -DaveM
*/
static void tcp_event_data_recv(struct sock *sk, struct sk_buff *skb)
{
struct tcp_sock *tp = tcp_sk(sk);
struct inet_connection_sock *icsk = inet_csk(sk);
u32 now; inet_csk_schedule_ack(sk); tcp_measure_rcv_mss(sk, skb); tcp_rcv_rtt_measure(tp); now = tcp_time_stamp; if (!icsk->icsk_ack.ato) {
/* The _first_ data packet received, initialize
* delayed ACK engine.
*/
tcp_incr_quickack(sk);
icsk->icsk_ack.ato = TCP_ATO_MIN;
} else {
int m = now - icsk->icsk_ack.lrcvtime; if (m <= TCP_ATO_MIN / ) {
/* The fastest case is the first. */
icsk->icsk_ack.ato = (icsk->icsk_ack.ato >> ) + TCP_ATO_MIN / ;
} else if (m < icsk->icsk_ack.ato) {
icsk->icsk_ack.ato = (icsk->icsk_ack.ato >> ) + m;
if (icsk->icsk_ack.ato > icsk->icsk_rto)
icsk->icsk_ack.ato = icsk->icsk_rto;
} else if (m > icsk->icsk_rto) {---------------------------进入这个流程
/* Too long gap. Apparently sender failed to
* restart window, so that we send ACKs quickly.
*/
tcp_incr_quickack(sk);----------------------------------这个修改了quickack的发包数量
sk_mem_reclaim(sk);
}
}
icsk->icsk_ack.lrcvtime = now; tcp_ecn_check_ce(tp, skb); if (skb->len >= )
tcp_grow_window(sk, skb);
}

正是因为发包的间隔大于了 icsk->icsk_rto,所以接收端觉得很长时间没有收到包了,那么尽快给对方回复ack。icsk->icsk_ack.quick 已经大于0了。

static bool tcp_in_quickack_mode(struct sock *sk)
{
const struct inet_connection_sock *icsk = inet_csk(sk);
const struct dst_entry *dst = __sk_dst_get(sk); return (dst && dst_metric(dst, RTAX_QUICKACK)) ||
(icsk->icsk_ack.quick && !icsk->icsk_ack.pingpong);
}

那么还需要一个条件就是,icsk->icsk_ack.pingpong 要为0,才行,否则单独增加 icsk->icsk_ack.quick 的值并不能保证立刻回复ack。

而我们目前这个流,明显是一个单向的发包流,并不是pingpong模式,所以这个值肯定为0,那么我们就满足了 tcp_in_quickack_mode 的条件,

打破了本端的delay_ack模式。

总结:

我只是搜索了tcp_enter_quickack_mode 的代码流程,没有注意到 tcp_incr_quickack 的调用,导致这个问题查了小半天。业务不精。

如果连续两个小包,加起来超过mss了,则可能会触发对端在delay_ack模式下立即回复ack,但是如果一个小包就打破了对端的delay_ack,则需要关注这个

小包的发包间隔了。

那么问题来了,为什么会相隔这么长时间发送小包?后面会继续探讨。

tcp的发送端一个小包就能打破对端的delay_ack么?的更多相关文章

  1. C# TCP socket发送大数据包时,接收端和发送端数据不一致 服务端接收Receive不完全

    简单的c# TCP通讯(TcpListener) C# 的TCP Socket (同步方式) C# 的TCP Socket (异步方式) C# 的tcp Socket设置自定义超时时间 C# TCP ...

  2. socket小程序写一个客户端,实现给服务端发送hello World字符串,将客户端发送的数据变成大写后返回

    写一个客户端,实现给服务端发送hello World字符串,将客户端发送的数据变成大写后返回 本机id是192.168.xx.xy 服务端 import socket soc = socket.soc ...

  3. Java基础知识强化之网络编程笔记06:TCP之TCP协议发送数据 和 接收数据

    1. TCP协议发送数据 和 接收数据 TCP协议接收数据:• 创建接收端的Socket对象• 监听客户端连接.返回一个对应的Socket对象• 获取输入流,读取数据显示在控制台• 释放资源 TCP协 ...

  4. 高效的TCP消息发送组件

    目前的.net 架构下缺乏高效的TCP消息发送组件,而这种组件是构建高性能分布式应用所必需的.为此我结合多年的底层开发经验开发了一个.net 下的高效TCP消息发送组件.这个组件在异步发送时可以达到每 ...

  5. socket(TCP)发送文件

    一:由于在上一个随笔的基础之上拓展的所以直接上代码,客户端: using System; using System.Collections.Generic; using System.Componen ...

  6. NTCPMSG 开源高性能TCP消息发送组件

    https://www.cnblogs.com/eaglet/archive/2013/01/07/2849010.html 目前的.net 架构下缺乏高效的TCP消息发送组件,而这种组件是构建高性能 ...

  7. TCP的发送缓冲区和接收缓冲区

    TCP协议是作用是用来进行端对端数据传送的,那么就会有发送端和接收端,在操作系统有两个空间即user space和kernal space. 每个Tcp socket连接在内核中都有一个发送缓冲区和接 ...

  8. TCP的发送系列 — 发送缓存的管理(二)

    主要内容:从TCP层面判断发送缓存的申请是否合法,进程因缺少发送缓存而进行睡眠等待. 因为有发送缓存可写事件而被唤醒. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zh ...

  9. TCP的发送系列 — 发送缓存的管理(一)

    主要内容:TCP发送缓存的初始化.动态调整.申请和释放. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd 数据结构 TCP对发送缓存的管理是在两个层面上进 ...

随机推荐

  1. ubuntu 安装 c语言的库函数man手册

    安装 1.C语言库函数基本的帮助文档 sudo apt-get install manpages sudo apt-get install manpages-de sudo apt-get insta ...

  2. Chromium Settings页面修改

    /********************************************************************** * Chromium Settings页面修改 * 说明 ...

  3. SQLI DUMB SERIES-12

    (1)检测闭合方式:在username上输入" admin" " 说明输入的username后还有双引号和括号 方法一: (2)通过其他途径知道用户名即可.如 输入&qu ...

  4. Arch Linux pacman 与其他发行版操作比较

    原文:https://wiki.archlinux.org/index.php/Pacman/Rosettahttps://old-en.opensuse.org/Software_Managemen ...

  5. HTML文本元素标签

    <b></b>表示关键字和产品名称如:<b>查看效果</b> 效果:加粗 查看效果 <strong></strong>表示重要的 ...

  6. paramiko模块(01-04节)、SSH

    1.  SSH(安全外壳协议) SSH 为 Secure Shell 的缩写,由 IETF 的网络小组(Network Working Group)所制定:SSH 为建立在应用层基础上的安全协议.SS ...

  7. pass

    空语句 do nothing 保证格式完整 保证语义完整 以if语句为例,在c或c++/java中: if(true) ; //do nothing else { //do something } 1 ...

  8. 内置---排序(sorted)

    # li = [1,23,4,5,6,6,7]# res = sorted(li,reverse=True) #反转后,从小到大 默认从大到小 #res = sorted(li) # print(re ...

  9. 双心一键获取winsxs的写入权限,解决VC运行库安装error1935错误

    @Echo offtitle 双心一键获取winsxs的写入权限,解决VC运行库安装error1935等错误set path=%path%;%~dp0setlocal EnableDelayedExp ...

  10. Oracle中存储图片的类型为BLOB类型,Java中如何将其读取并转为字符串?

    一,读取图片转为String类型: 需要使用Sun公司提供的Base64工具 String str = ((Map) list1.get(0)).get("EINVOICEFILE" ...