主要内容:TCP的快速确认、TCP_QUICKACK选项的实现。

内核版本:3.15.2

我的博客:http://blog.csdn.net/zhangskd

快速确认模式

(1) 进入快速确认模式

设置快速确认模式标志,设置在快速确认模式中可以发送的ACK数量。

static void tcp_enter_quickack_mode (struct sock *sk)
{
struct inet_connection_sock *icsk = inet_csk(sk); tcp_incr_quickack(sk); /* 设置在快速确认模式中可以发送的ACK数量 */
icsk->icsk_ack.pingpong = 0; /* 快速确认模式的标志 */
icsk->icsk_ack.ato = TCP_ATO_MIN; /* ACK超时时间 */
}

在快速确认模式中,可以发送的ACK数量是有限制的,具体额度为icsk->icsk_ack.quick。

所以进入快速确认模式时,需要设置可以快速发送的ACK数量,一般允许快速确认半个接收窗口的数据量,

但最多不能超过16个,最少为2个。

static void tcp_incr_quickack (struct sock *sk)
{
struct inet_connection_sock *icsk = inet_csk(sk); /* 可以快速确认半个接收窗口的数据量 */
unsigned int quickacks = tcp_sk(sk)->rcv_wnd / (2 * icsk->icsk_ack.rcv_mss); if (quickacks == 0)
quckacks = 2; /* 最少为2个 */ if (quickacks > icsk->icsk_ack.quick)
icsk->icsk_ack.quick = min(quickacks, TCP_MAX_QUICKACKS); /* 最多不能超过16个 */
} /* Maximal number of ACKs sent quickly to accelerate slow-start. */
#define TCP_MAX_QUICKACKS 16U

(2) 检查是否处于快速确认模式。

如果设置了快速确认标志,且快速确认模式中可以发送的ACK数量不为0,就判断连接处于快速确认模式中,

允许立即发送ACK。

/* Send ACKs quickly, if "quick" count is not exhausted and the session is not interactive. */
static inline bool tcp_in_quickack_mode (const struct sock *sk)
{
const struct inet_connectionsock *icsk = inet_csk(sk); /* 如果快速确认模式中可以发送的ACK数量不为0,且设置了快速确认标志 */
return icsk->icsk_ack.quick && ! icsk->icsk_ack.pingpong;
}

快速ACK的发送

在tcp_rcv_established()中,如果没有通过TCP的首部预测,就会执行慢速路径来处理接收到的报文。

处理完接收到的报文之后,会调用tcp_data_snd_check()来检查是否需要发送数据,以及是否需要扩大发送缓存。

然后调用tcp_ack_snd_check()来检查是否需要发送ACK,以及是使用快速确认还是延迟确认。

同样的在通过TCP首部预测的快速路径中,也会调用__tcp_ack_snd_check()来发送快速确认或延迟确认。

static inline void tcp_ack_snd_check(struct sock *sk)
{
/* 如果没有ACK需要发送 */
if (! inet_csk_ack_scheduled(sk)) {
/* We sent a data segment already. */
return;
} __tcp_ack_snd_check(sk, 1); /* 决定要发送快速确认还是延迟确认 */
}

如果此时符合以下任一条件,可以立即发送ACK,即进行快速确认:

1. 接收缓冲区中有一个以上的全尺寸数据段仍然是NOT ACKed,并且接收窗口变大了。

所以一般收到了两个数据包后,会发送ACK,而不是对每个数据包都进行确认。

2.  此时处于快速确认模式中。

3. 乱序队列不为空。

/* 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); /* 符合以下任一条件,可以立即发送ACK:
* 1. 接收缓冲区中有一个以上的全尺寸数据段仍然是NOT ACKed,并且接收窗口变大了。
* 2. 此时处于快速确认模式中。
* 3. 乱序队列不为空。
*/
/* 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 && skb_peek(&tp->out_of_order_queue))) { /* Then ack it now. */
tcp_send_ack(sk); /* 立即发送ACK */ } else {
/* Else, send delayed ack. */
tcp_send_delayed_ack(sk); /* 延迟ACK的发送,见下一篇blog:) */
}
}

ACK的发送函数为tcp_send_ack(),如果发送失败会启动ACK延迟定时器。

/* This routine sends an ack and also updates the window. */

void tcp_send_ack (struct sock *sk)
{
struct sk_buff *buff; /* If we have been reset, we may not send again. */
if (sk->sk_state == TCP_CLOSE)
return; /* We are not putting this on the write queue, so tcp_transmit_skb()
* will set the ownership to this sock.
*/
buff = alloc_skb(MAX_TCP_HEADER, sk_gfp_atomic(sk, GFP_ATOMIC)); if (buff == NULL) { /* 分配skb失败 */
inet_csk_schedule_ack(sk); /* 设置标志位,表示有ACK需要发送 */
inet_csk(sk)->icsk_ack.ato = TCP_ATO_MIN; /* 重置ATO */ /* 设置延迟确认定时器,超时时间为200ms */
inet_csk_reset_xmit_timer(sk, ICSK_TIME_DACK, TCP_DELACK_MAX, TCP_RTO_MAX);
return;
} /* Reserve space for headers and prepare control bits. */
skb_reserve(buff, MAX_TCP_HEADER); /* 设置报文头部的空间 */ /* 初始化不携带数据的skb的一些控制字段 */
tcp_init_nondata_skb(buff, tcp_acceptable_seq(sk), TCPHDR_ACK); /* Send it off, this clears delayed acks for us. */
TCP_SKB_CB(buff)->when = tcp_time_stamp; tcp_transmit_skb(sk, buff, 0, sk_gfp_atomic(sk, GFP_ATOMIC));
} /* 设置标志位,表示有ACK需要发送。*/
static inline void inet_csk_schedule_ack (struct sock *sk)
{
inet_csk(sk)->icsk_ack.pending |= ICSK_ACK_SCHED;
} /* Maximal time to delay before sending an ACK.
* Delayed ACK的最大延迟时间,一般为200ms
*/
#define TCP_DELACK_MAX ((unsigned) (HZ/5)) /* Delayed ACK的最小延迟时间,一般为40ms */
#define TCP_DELACK_MIN ((unsigned) (HZ/25))

TCP_QUICKACK选项

TCP_QUICKACK用于让本端立即发送ACK,而不进行延迟确认。

需要注意的是,这个选项并不是持久的,之后还是有可能进入延迟确认模式的。

所以如果需要一直进行快速确认,要在每次调用接收函数后都进行选项设置。

int quickack = 1; /* 启用快速确认,如果赋值为0表示使用延迟确认 */

setsockopt(fd, SOL_TCP, TCP_QUICKACK, &quickack, sizeof(quickack));

#define TCP_QUICKACK 12 /* Block / reenable quick acks */

static int do_tcp_setsockopt(struct sock *sk, int level, int optname, char __user *optval,
unsigned int optlen)
{
...
case TCP_QUICKACK:
if (! val) {
icsk->icsk_ack.pingpong = 1; /* 禁用快速确认模式 */ } else {
icsk->icsk_ack.pingpong = 0; /* 启用快速确认模式 */ /* 如果当前有ACK需要发送,就立即发送 */
if (1 << sk->sk_state) & (TCPF_ESTABLISHED | TCPF_CLOSE_WAIT) &&
inet_csk_ack_scheduled(sk)) { icsk->icsk_ack.pending |= ICSK_ACK_PUSHED; /* 允许在快速模式中立即发送 */ /* 通常当接收队列中有数据复制到用户空间时,会调用此函数来判断是否需要立即发送ACK。
* 这里的用法比较特殊,由于设置了ICSK_ACK_PUSHED标志,且处于快速确认模式中,
* 必然会立即发送ACK。
*/
tcp_cleanup_rbuf(sk, 1); /* 如果选项的值为偶数,那么立即退出快速确认模式。
* 原来选项值不限于0和1,还分奇偶的:)
*/
if (! (val & 1))
icsk->icsk_ack.pingpong = 1;
}
}
break;
...
}

当接收队列中有数据复制到用户空间时,会调用tcp_cleanup_rbuf()来判断是否要立即发送ACK。

(1) 如果现在有ACK需要发送,满足以下条件之一,就可以立即发送:

1. icsk->icsk_ack.blocked为1,之前有Delayed ACK被用户进程阻塞了。

2. 接收缓冲区中有一个以上的全尺寸数据段仍然是NOT ACKed (所以经常是收到2个全尺寸段后发送ACK)

3. 本次复制到用户空间的数据量大于0,且满足以下条件之一:

3.1 设置了ICSK_ACK_PUSHED2标志

3.2 设置了ICSK_ACK_PUSHED标志,且处于快速确认模式中

(2) 如果原来没有ACK需要发送,但是现在的接收窗口显著增大了,也需要立即发送ACK通知对端。

这里的显著增大是指:新的接收窗口大小不为0,且比原来接收窗口的剩余量增大了一倍。

/* Clean up the receive buffer for full frames taken by the user,
* then send an ACK if necessary. COPIED is the number of bytes
* tcp_recvmsg has given to the user so far, it speeds up the calculation
* of whether or not we must ACK for the sake of a window update.
*/ void tcp_cleanup_rbuf (struct sock *sk, int copied)
{
struct tcp_sock *tp = tcp_sk(sk);
bool time_to_ack = false; /* 获取接收队列的头一个数据段 */
struct sk_buff *skb = skb_peek(&sk->sk_receive_queue); /* copied_seq: Head of yet unread data,应用程序下次从这里开始复制数据。
* 这里检查在发送队列中,已复制到用户空间的数据段是否被清理了。
*/
WARN(skb && !before(tp->copied_seq, TCP_SKB_CB(skb)->end_seq),
"cleanup rbuf bug: copied %X seq %X rcvnxt %X\n", tp->copied_seq,
TCP_SKB_CB(skb)->end_seq, tp->rcv_nxt); /* 如果现在有ACK需要发送,满足以下条件之一则可以立即发送 */
if (inet_csk_ack_scheduled(sk)) {
const struct inet_connection_sock *icsk = inet_csk(sk); /* 1. Delayed ACKs frequently hit locked sockets during bulk receive.
* 2. Once-per-two-segments ACK was not sent by tcp_input.c.
* 3. copied >0 and ICSK_ACK_PUSHED2 set.
* 4. copied > 0 and ICSK_ACK_PUSHED and in quickack mode.
*/
if (icsk->icsk_ack.blocked || tp->rcv_nxt - tp->rcv_wup > icsk->icsk_ack.rcv_mss ||
(copied > 0 && ((icsk->icsk_ack.pending & ICSK_ACK_PUSHED2) ||
((icsk->icsk_ack.pending & ICSK_ACK_PUSHED) && ! icsk->icsk_ack.pingpong)) &&
! atomic_read(&sk->sk_rmem_alloc)))
time_to_ack = true;
} /* We send an ACK if we can now advertise a non-zero window which has
* been raised "significantly" - at least twice bigger.
* Even if window raised up to infinity, do not send window open ACK in states,
* where we will not receive more. It is useless.
*/
if (copied > 0 && ! time_to_ack && ! (sk->sk_shutdown & RCV_SHUTDOWN)) {
__u32 rcv_window_now = tcp_receive_window(tp); /* 当前接收窗口的剩余量 */ /* 如果当前接收窗口的剩余量小于最大值的一半 */
if (2 * rcv_window_now <= tp->window_clamp) { /* 根据剩余的接收缓存,计算新的接收窗口的大小。
* 因为这次复制,很可能空出不少接收缓存,所以新的接收窗口也会相应增大。
*/
__u32 new_window = __tcp_select_window(sk); /* Send ACK now, if this read freed lots of space in our buffer.
* Certainly, new_window is new window.
* We can advertise it now, if it is not less than current one.
* "Lots" means "at least twice" here.
*/ /* 如果新的接收窗口不为0,且比原来接收窗口的剩余量大了一倍以上,就说接收窗口显著增大了。
* 而当接收窗口显著增大时,也需要立即发送ACK告知对端。
*/
if (new_window && new_window >= 2 * rcv_window_now)
time_to_ack = true;
}
} if (time_to_ack)
tcp_send_ack(sk); /* 发送ACK给对端 */
}
/* 计算当前接收窗口的剩余量 */
/* Compute the actual receive window we are currently advertising.
* Rcv_nxt can be after the window if our peer push more data than
* the offered window.
*/
static inline u32 tcp_receive_window (const struct tcp_sock *tp)
{
s32 win = tp->rcv_wup + tp->rcv_wnd - tp->rcv_nxt; if (win < 0)
win = 0; return (u32) win;
}

TCP的ACK确认系列 — 快速确认的更多相关文章

  1. TCP的ACK确认系列 — 延迟确认

    主要内容:TCP的延迟确认.延迟确认定时器的实现. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd 延迟确认模式 发送方在发送数据包时,如果发送的数据包有 ...

  2. TCP的ACK原理和延迟确认机制

    某天晚上睡觉前突然想到 tcp的ACK确认是单独发的还是和报文一起发的,下面看一下别人的解答 一.ACK定义TCP协议中,接收方成功接收到数据后,会回复一个ACK数据包,表示已经确认接收到ACK确认号 ...

  3. tcpack--3快速确认模式- ack状态发送&清除

    ACK发送状态的转换图 ACK的发送状态清除 当成功发送ACK时,会删除延迟确认定时器,同时清零ACK的发送状态标志icsk->icsk_ack.pending ACK发送事件主要做了:更新快速 ...

  4. tcpack--3快速确认模式

    接收到数据报后,会调用tcp_event_data_recv(),不管是在慢速路径的tcp_data_queue中调用还是 在快速路径中处理接收数据后直接调用,注意(如果len <= tcp_h ...

  5. TCP的ACK确认系列 — 发送状态转换机

    主要内容:TCP的ACK发送方式,以及ACK发送状态转换机的实现. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd 概述 TCP采用两种方式来发送ACK: ...

  6. TCP/IP网络编程系列之四(初级)

    TCP/IP网络编程系列之四-基于TCP的服务端/客户端 理解TCP和UDP 根据数据传输方式的不同,基于网络协议的套接字一般分为TCP和UDP套接字.因为TCP套接字是面向连接的,因此又称为基于流的 ...

  7. linux内核参数sysctl.conf,TCP握手ack,洪水攻击syn,超时关闭wait

    题记:优化Linux内核sysctl.conf参数来提高服务器并发处理能力 PS:在服务器硬件资源额定有限的情况下,最大的压榨服务器的性能,提高服务器的并发处理能力,是很多运维技术人员思考的问题.要提 ...

  8. linux内核参数sysctl.conf,TCP握手ack,洪水攻击syn,超时关闭wait(转)

    http://www.xshell.net/linux/Linux_sysctl_conf.html 优化Linux内核sysctl.conf参数来提高服务器并发处理能力 Posted by 破冰 o ...

  9. RabbitMQ (十一) 消息确认机制 - 消费者确认

    由于生产者和消费者不直接通信,生产者只负责把消息发送到队列,消费者只负责从队列获取消息(不管是push还是pull). 消息被"消费"后,是需要从队列中删除的.那怎么确认消息被&q ...

随机推荐

  1. Day 1 Python简单程序

    一.高级语言和低级语言   最初的计算机程序都是用0和1的序列表示的,程序员直接使用的是机器指令,无需翻译,从纸带打孔输入即可执行得到结果.后来为了方便记忆,就将用0.1序列表示的机器指令都用符号助记 ...

  2. (一)python基础知识

    Python:解释型语言(一边翻译一边运行)注释:单行注释(#).多行注释(ctrl+/):''' '''和""" """ (python2 ...

  3. ReactNative 4Android源码分析二: 《JNI智能指针之实现篇》

    文/Tamic http://blog.csdn.net/sk719887916/article/details/53462268 回顾 上一篇介绍了<ReactNative4Android源码 ...

  4. 事务的特性(ACID)

    一.事务 定义:所谓事务,它是一个操作序列,这些操作要么都执行,要么都不执行,它是一个不可分割的工作单位. 准备工作:为了说明事务的ACID原理,我们使用银行账户及资金管理的案例进行分析. // 创建 ...

  5. 衣带渐宽终不悔,为伊消得人憔悴--DbHelper增强版

    核心理念 如何使用 测试实例 数据库内详细数据信息 测试代码 数据库连接池测试 测试集 延伸 相关下载链接 前几日,写了一篇关于一个 轻量级数据持久化的框架的博客(点击浏览: http://blog. ...

  6. Android属性动画完全解析(下),Interpolator和ViewPropertyAnimator的用法

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/44171115 大家好,欢迎继续回到Android属性动画完全解析.在上一篇文章当中 ...

  7. python将nan, inf转为特定的数字

    最近,处理两个矩阵的点除,得到结果后,再作其他的计算,发现有些内置的函数不work:查看得到的数据,发现有很多nan和inf,导致python的基本函数运行不了,这是因为在除的过程中分母出现0的缘故. ...

  8. Android TV开发总结(一)构建一个TV app前要知道的事儿

    转载请把头部出处链接和尾部二维码一起转载,本文出自逆流的鱼yuiop:http://blog.csdn.net/hejjunlin/article/details/52792562 前言:近年来,智能 ...

  9. 全废话SQL Server统计信息(1)——统计信息简介

    当心空无一物,它便无边无涯.树在.山在.大地在.岁月在.我在.你还要怎样更好的世界?--张晓风<我在> 为什么要写这个内容? 随着工作经历的积累,越来越感觉到,大量的关系型数据库的性能问题 ...

  10. Dynamics CRM2016 Web API之Use custom FetchXML

    CRM2016中新增的web api支持fetch xml了,之前使用FetchXML的场景是在后天代码中通过组织服务的retrieve multiple方法,但实际的应用效果有多大,还需要在实际的项 ...