Nagle Algorithm

article/2025/7/23 4:12:25

转: http://bbs.chinaunix.NET/thread-3767363-1-1.html

在网络拥塞控制领域,我们知道有一个非常有名的算法叫做Nagle算法(Nagle algorithm),这是使用它的发明人John Nagle的名字来命名的,John Nagle在1984年首次用这个算法来尝试解决福特汽车公司的网络拥塞问题(RFC 896),该问题的具体描述是:如果我们的应用程序一次产生1个字节的数据,而这个1个字节数据又以网络数据包的形式发送到远端服务器,那么就很容易导致网络由于太多的数据包而过载。比如,当用户使用Telnet连接到远程服务器时,每一次击键操作就会产生1个字节数据,进而发送出去一个数据包,所以,在典型情况下,传送一个只拥有1个字节有效数据的数据包,却要发费40个字节长包头(即ip头20字节+tcp头20字节)的额外开销,这种有效载荷(payload)利用率极其低下的情况被统称之为愚蠢窗口症候群(Silly Window Syndrome)。可以看到,这种情况对于轻负载的网络来说,可能还可以接受,但是对于重负载的网络而言,就极有可能承载不了而轻易的发生拥塞瘫痪。
针对上面提到的这个状况,Nagle算法的改进在于:如果发送端欲多次发送包含少量字符的数据包(一般情况下,后面统一称长度小于MSS的数据包为小包,与此相对,称长度等于MSS的数据包为大包,为了某些对比说明,还有中包,即长度比小包长,但又不足一个MSS的包),则发送端会先将第一个小包发送出去,而将后面到达的少量字符数据都缓存起来而不立即发送,直到收到接收端对前一个数据包报文段的ACK确认、或当前字符属于紧急数据,或者积攒到了一定数量的数据(比如缓存的字符数据已经达到数据包报文段的最大长度)等多种情况才将其组成一个较大的数据包发送出去,具体有哪些情况,我们来看看内核实现:
1383:        Filename : \linux-3.4.4\net\ipv4\tcp_output.c
1384:        /* Return 0, if packet can be sent now without violation Nagle's rules:
1385:         * 1. It is full sized.
1386:         * 2. Or it contains FIN. (already checked by caller)
1387:         * 3. Or TCP_CORK is not set, and TCP_NODELAY is set.
1388:         * 4. Or TCP_CORK is not set, and all sent packets are ACKed.
1389:         *    With Minshall's modification: all sent small packets are ACKed.
1390:         */
1391:        static inline int tcp_nagle_check(const struct tcp_sock *tp,
1392:                                          const struct sk_buff *skb,
1393:                                          unsigned mss_now, int nonagle)
1394:        {
1395:                return skb->len < mss_now &&
1396:                        ((nonagle & TCP_NAGLE_CORK) ||
1397:                         (!nonagle && tp->packets_out && tcp_minshall_check(tp)));
1398:        }
1399:        
1400:        /* Return non-zero if the Nagle test allows this packet to be
1401:         * sent now.
1402:         */
1403:        static inline int tcp_nagle_test(const struct tcp_sock *tp, const struct sk_buff *skb,
1404:                                         unsigned int cur_mss, int nonagle)
1405:        {
1406:                /* Nagle rule does not apply to frames, which sit in the middle of the
1407:                 * write_queue (they have no chances to get new data).
1408:                 *
1409:                 * This is implemented in the callers, where they modify the 'nonagle'
1410:                 * argument based upon the location of SKB in the send queue.
1411:                 */
1412:                if (nonagle & TCP_NAGLE_PUSH)
1413:                        return 1;
1414:        
1415:                /* Don't use the nagle rule for urgent data (or for the final FIN).
1416:                 * Nagle can be ignored during F-RTO too (see RFC413.
1417:                 */
1418:                if (tcp_urg_mode(tp) || (tp->frto_counter == 2) ||
1419:                    (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN))
1420:                        return 1;
1421:        
1422:                if (!tcp_nagle_check(tp, skb, cur_mss, nonagle))
1423:                        return 1;
1424:        
1425:                return 0;
1426:        }
这一段Linux内核代码非常容易看,因为注释代码足够的多。从函数tcp_nagle_test()看起,第1412行是直接进行参数判断,如果在外部(也就是调用者)主动设置了TCP_NAGLE_PUSH旗标,比如主动禁止Nagle算法或主动拔走塞子(下一节TCP_CORK内容)或明确是连接最后一个包(比如连接close()前发出的数据包),此时当然是返回1从而把数据包立即发送出去;第1418-1420行代码处理的是特殊包,也就是紧急数据包、带FIN旗标的结束包以及带F-RTO旗标的包;第1422行进入到tcp_nagle_check()函数进行判断,该函数的头注释有点混乱而不太清楚,我再逐句代码解释一下,首先要看明白如果该函数返回1,则表示该数据包不立即发送;再看具体实现就是:skb->len < mss_now为真表示如果包数据长度小于当前MSS;nonagle & TCP_NAGLE_CORK为真表示当前已主动加塞或明确标识立即还会有数据过来(内核表示为MSG_MORE);!nonagle为真表示启用Nagle算法;tp->packets_out为真表示存在有发出去的数据包没有被ACK确认;tcp_minshall_check(tp)是Nagle算法的改进,先直接认为它与前一个判断相同,具体后续再讲。把这些条件按与或组合起来就是:如果包数据长度小于当前MSS &&((加塞、有数据过来)||(启用Nagle算法 && 存在有发出去的数据包没有被ACK确认)),那么缓存数据而不立即发送。
 
上左图(台式主机图样为发送端,又叫客户端,服务器主机图样为接收端,又叫服务器)是未开启Nagle算法的情况,此时客户端应用层下传的数据包被立即发送到网络上(暂不考虑发送窗口与接收窗口这些固有限制,下同),而不管该数据包的大小如何,因此在网络里就有可能同时存在该连接的多个小包;而如上右图所示上,在未收到服务器对第一个包的ACK确认之前,客户端应用层下传的数据包被缓存了起来,当收到ACK确认之后(图中给的情况是这种,当然还有其他情况,前面已经详细描述过)才发送出去,这样不仅总包数由原来的3个变为2个,网络负载降低,与此同时,客户端和服务器都只需处理两个包,消耗的CPU等资源也减少了。
Nagle算法在一些场景下的确能提高网络利用率、降低包处理(客户端或服务器)主机资源消耗并且工作得很好,但是在某些场景下却又弊大于利,要说清楚这个问题需要引入另一个概念,即延迟确认(Delayed ACK)。延迟确认是提高网络利用率的另一种优化,但它针对的是ACK确认包。我们知道,对于TCP协议而言,正常情况下,接收端会对它收到的每一个数据包向发送端发出一个ACK确认包(如前面图示那样);而一种相对的优化就是把ACK延后处理,即ACK与数据包或窗口更新通知包等一起发送(文档RFC 1122),当然这些数据包都是由接收端发送给发送端(接收端和发送端只是一个相对概念)的:

 
上左图是一般情况,上右图(这里只画出了ACK延迟确认机制中的两种情况:通过反向数据携带ACK和超时发送ACK)中,数据包A的ACK是通过接收端发回给发送端的数据包a携带一起过来的,而对应的数据包a的ACK是在等待超时之后再发送的。另外,虽然RFC 1122标准文档上,超时时间最大值是500毫秒,但在实际实现中最大超时时间一般为200毫秒(并不是指每一次超时都要等待200毫秒,因为在收到数据时,定时器可能已经经历一些时间了,在最坏情况的最大值也就是200毫秒,平均等待超时值为100毫秒),比如在linux3.4.4有个TCP_DELACK_MAX的宏标识该超时最大值:
115:        Filename : \linux-3.4.4\include\net\tcp.h
116:        #define TCP_DELACK_MAX        ((unsigned)(HZ/5))        /* maximal time to delay before sending an ACK */
回过头来看Nagle算法与ACK延迟确认的相互作用,仍然举个例子来讲,如果发送端暂有一段数据要发送给接收端,这段数据的长度不到最大两个包,也就是说,根据Nagle算法,发送端发出去第一个数据包后,剩下的数据不足以组成一个可立即发送的数据包(即剩余数据长度没有大于等于MSS),因此发送端就会等待,直到收到接收端对第一个数据包的ACK确认或者应用层传下更多需要发送的数据等(这里暂只考虑第一个条件,即收到ACK);而在接收端,由于ACK延迟确认机制的作用,它不会立即发送ACK,而是等待,直到(具体情况请参考内核函数tcp_send_delayed_ack(),由于涉及到情况太过复杂,并且与当前内容关系不大,所以略过,我们仅根据RFC 1122来看):1,收到发送端的第二个大数据包;2,等待超时(比如,200毫秒)。当然,如果本身有反向数据包要发送,那么可以携带ACK,但是在最糟的情况下,最终的结果就是发送端的第二个数据包需要等待200毫秒才能被发送到网络上。而在像HTTP这样的应用里,某一时刻的数据基本是单向的,所以出现最糟情况的概率非常的大,而且第二个数据包往往用于标识这一个请求或响应的成功结束,如果请求和响应都要超时等待的话,那么时延就得增大400毫秒。
针对在上面这种场景下Nagle算法缺点改进的详细情况描述在文档: http://tools.ietf.org/id/draft-minshall-nagle-01.txt 里,在linux内核里也已经应用了这种改进,也就是前面未曾详细讲解的函数tcp_minshall_check():
1376:        Filename : \linux-3.4.4\net\ipv4\tcp_output.c
1377:        /* Minshall's variant of the Nagle send check. */
1378:        static inline int tcp_minshall_check(const struct tcp_sock *tp)
1379:        {
1380:                return after(tp->snd_sml, tp->snd_una) &&
1381:                        !after(tp->snd_sml, tp->snd_nxt);
1382:        }
函数名是按改进提出者的姓名来命名的,这个函数的实现很简单,但要理解它必须先知道这些字段的含义(RFC 793、RFC 1122):tp->snd_nxt,下一个待发送的字节(序号,后同);tp->snd_una,下一个待确认的字节,如果它的值等于tp->snd_nxt,则表示所有已发数据都已经得到了确认;tp->snd_sml,已经发出去的最近的一个小包的最后一个字节(注意,不一定是已确认)。具体图示如下:
 
总结前面所有介绍的内容,Minshall对Nagle算法所做的改进简而言之就是一句话:在判断当前包是否可发送时,只需检查最近的一个小包是否已经确认(其它需要判断的条件,比如包长度是否大于MSS等这些没变,这里假定判断到最后,由此处决定是否发送),如果是,即前面提到的tcp_minshall_check(tp)函数返回值为假,从而函数tcp_nagle_check()返回0,那么表示可以发送(前面图示里的上图),否则延迟等待(前面图示里的下图)。基于的原理很简单,既然发送的小包都已经确认了,也就是说网络上没有当前连接的小包了,所以发送一个即便是比较小的数据包也无关大碍,同时更重要的是,这样做的话,缩短了延迟,提高了带宽利用率。
那么对于前面那个例子,由于第一个数据包是大包,所以不管它所对应的ACK是否已经收到都不影响对是否发送第二个数据包所做的检查与判断,此时因为所有的小包都已经确认(其实是因为本身就没有发送过小包),所以第二个包可以直接发送而无需等待。
传统Nagle算法可以看出是一种包-停-等协议,它在未收到前一个包的确认前不会发送第二个包,除非是“逼不得已”,而改进的Nagle算法是一种折中处理,如果未确认的不是小包,那么第二个包可以发送出去,但是它能保证在同一个RTT内,网络上只有一个当前连接的小包(因为如果前一个小包未被确认,不会发出第二个小包);但是,改进的Nagle算法在某些特殊情况下反而会出现不利,比如下面这种情况(3个数据块相继到达,后面暂时也没有其他数据到达),传统Nagle算法只有一个小包,而改进的Nagle算法会产生2个小包(第二个小包是延迟等待超时产生),但这并没有特别大的影响(所以说是它一种折中处理):
 
TCP中的Nagle算法默认是启用的,但是它并不是适合任何情况,对于telnet或rlogin这样的远程登录应用的确比较适合(原本就是为此而设计),但是在某些应用场景下我们却又需要关闭它。在链接: http://www.isi.edu/lsam/publicat ... ractions/node2.html 里提到Apache对HTTP持久连接(Keep-Alive,Prsistent-Connection)处理时凸现的奇数包&结束小包问题(The Odd/Short-Final-Segment Problem),这是一个并的关系,即问题是由于已有奇数个包发出,并且还有一个结束小包(在这里,结束小包并不是指带FIN旗标的包,而是指一个HTTP请求或响应的结束包)等待发出而导致的。我们来看看具体的问题详情,以3个包+1个结束小包为例,下图是一种可能发生的发包情况:
 
最后一个小包包含了整个响应数据的最后一些数据,所以它是结束小包,如果当前HTTP是非持久连接,那么在连接关闭时,最后这个小包会立即发送出去,这不会出现问题;但是,如果当前HTTP是持久连接(非pipelining处理,pipelining仅HTTP 1.1支持,并且目前有相当一部分陈旧但仍在广泛使用中的浏览器版本尚不支持,nginx目前对pipelining的支持很弱,它必须是前一个请求完全处理完后才能处理后一个请求),即进行连续的Request/Response、Request/Response、…,处理,那么由于最后这个小包受到Nagle算法影响无法及时的发送出去(具体是由于客户端在未结束上一个请求前不会发出新的request数据,导致无法携带ACK而延迟确认,进而导致服务器没收到客户端对上一个小包的的确认导致最后一个小包无法发送出来),导致第n次请求/响应未能结束,从而客户端第n+1次的Request请求数据无法发出。
 
正是由于会有这个问题,所以遇到这种情况,nginx就会主动关闭Nagle算法,我们来看nginx代码:
2436:        Filename : \linux-3.4.4\net\ipv4\tcp_output.c
2437:        static void
2438:        ngx_http_set_keepalive(ngx_http_request_t *r)
2439:        {
2440:        …
2623:            if (tcp_nodelay
2624:                && clcf->tcp_nodelay
2625:                && c->tcp_nodelay == NGX_TCP_NODELAY_UNSET)
2626:            {
2627:                ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "tcp_nodelay" ;
2628:        
2629:                if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY,
2630:                               (const void *) &tcp_nodelay, sizeof(int))
2631:                    == -1)
2632:                {
2633:        …
2646:                c->tcp_nodelay = NGX_TCP_NODELAY_SET;
2647:            }
Nginx执行到这个函数内部,就说明当前连接是持久连接。第2623行的局部变量tcp_nodelay是用于标记TCP_CORK选项的,由配置指令tcp_nopush指定,默认情况下为off,在linux下,nginx把TCP_NODELAY和TCP_CORK这两个选项完全互斥使用(事实上它们可以一起使用,下一节详细描述),禁用TCP_CORK选项时,局部变量tcp_nodelay值为1(从该变量可以看到,nginx对这两个选项的使用,TCP_CORK优先级别高于TCP_NODELAY);clcf->tcp_nodelay对应TCP_NODELAY选项的配置指令tcp_nodelay的配置值,默认情况下为1;c->tcp_nodelay用于标记当前是否已经对该套接口设置了TCP_NODELAY选项,第一次执行到这里时,值一般情况下也就是NGX_TCP_NODELAY_UNSET(除非不是IP协议等),因为只有此处一个地方设置TCP_NODELAY选项。所以,整体来看,如果此判断为真,于是第2629行对套接口设置TCP_NODELAY禁止Nagle算法(字段c->tcp_nodelay被赋值为NGX_TCP_NODELAY_SET,表示当前已经对该套接口设置了TCP_NODELAY选项),最后的响应数据会被立即发送出去,从而解决了前面提到的可能问题。



http://chatgpt.dhexx.cn/article/AuJ7NIxW.shtml

相关文章

TCP中的Nagle算法

TCP中的Nagle算法 一. Nagel算法 TCP/IP协议中,无论发送多少数据,总是要在数据前面加上协议头,同时,对方接收到数据,也需要发送ACK表示确认.为了尽可能的利用网络带宽,TCP总是希望尽可能的发送足够大的数据.(在一个连接中会设置MSS参数,因此,TCP/IP希望每次都能够以MSS尺寸的数…

确认应答、Nagle算法和延时确认应答

目录 确认应答 延时确认应答 Nagle算法 确认应答 TCP在传输数据的时候&#xff0c;每次接受方收到来自发送方的数据包后&#xff0c;接受方对都会发送一个确认应答(ACK)报文作为回应&#xff0c;发送方收到来自接受方的确认应答(ACK)报文&#xff0c;就表明发送的数据已经被…

TCP/IP卷一:80---TCP数据流与窗口管理之(延时确认(延迟ACK)、Nagle算法)

一、延迟确认&#xff08;延迟ACK&#xff09; 在许多情况下&#xff0c;TCP并不对每个到来的数据包都返回ACK&#xff0c;利用TCP的累积ACK字段&#xff08;参见TCP报文格式https://blog.csdn.net/qq_41453285/article/details/104016416&#xff09;就能实现该功能累积确认可…

禁用 Nagle 算法

有没有发现一个很奇怪的组合&#xff0c;即 Nagle 算法和延时 ACK 的组合。这个组合为什么奇怪呢&#xff1f; 我举一个例子你来体会一下。比如&#xff0c;客户端分两次将一个请求发送出去&#xff0c;由于请求的第一部分的报文未被确认&#xff0c;Nagle 算法开始起作用&…

TCP Nagle算法及示例

TCP nagle算法是说&#xff0c;一个TCP连接只允许有一个未被确认的小数据包&#xff0c;如果有小数据包未被确认&#xff0c;其他要发送的小数据包先被缓存起来&#xff0c;等收到确认后&#xff0c; 把这些数据包再一块发送出去。注意算法中说的是小数据包&#xff0c;也就是n…

TCP-IP详解:Nagle算法

参考书籍&#xff1a;TCP/IP详解&#xff0c;卷1&#xff1a;协议 Small Packet Problem 在使用一些协议通讯的时候&#xff0c;比如Telnet&#xff0c;会有一个字节字节的发送的情景&#xff0c;每次发送一个字节的有用数据&#xff0c;就会产生41个字节长的分组&#xff0c…

TCP-Nagle:代码版本重新解释Nagle算法

开年来的第一份工作&#xff0c;就是在最新的内核上打补丁。 可没想到Nagle算法也被我冲进了去年的垃圾桶里。 在网上找了一些资料&#xff0c;理论很快被消化&#xff0c;但看了看内核的实现&#xff0c;久久没能动弹。坐了一天&#xff0c;才摸索出来点什么&#xff0c;觉得…

Nagle算法原理与实现详解

文章目录 背景Nagle算法详解算法实现实现开启与关闭Nagle算法 Nagle算法与延迟ACK参考 背景 TCP的数据流大致可以被分成两类&#xff1a; 交互式数据流 TCP交互数据流指的是&#xff1a;TCP连接中传输的所有数据的总和&#xff0c;包括控制命令&#xff08;用于管理网络中连接…

TCP详解 (三)Nagle算法和延迟确认

文章目录 延迟确认Nagle算法Nagle算法遇上延迟确认关闭Nagle算法 一些有关TCP通信量的研究如[Caceresetal.1991]发现&#xff0c;如果按照分组数量计算&#xff0c;约有一半的TCP报文段包含成块数据&#xff08;如 FTP、电子邮件和 Usenet新闻&#xff09;&#xff0c;另一半则…

复指数信号正交性的简单证明

复指数信号为&#xff1a; . 写成矢量形式&#xff1a; 复指数信号两两正交&#xff0c;也就是两个复指数信号的内积有如下表示&#xff1a; 将上式内积形式展开&#xff1a; 当kl时&#xff0c;所有cos项1&#xff0c;sin项0&#xff0c;求和为N。 当时&#xff0c;将求和符…

三角函数正交性的推导

定理&#xff1a; 一个三角函数系&#xff1a;1 cosx sinx cos 一个三角函数系&#xff1a;1&#xff0c;cosx , sinx , cos2x , sin2x , … , cosnx , sinnx , … 如果这一堆函数&#xff08;包括常数1&#xff09;中任何两个不同函数的乘积在区间[-π, π]上的积分等于…

OFDM时频脉冲形状与子载波正交性的理解

从多载波调制的角度来理解子载波间正交&#xff0c;首先要明确OFDM默认的脉冲成型是时域矩形窗&#xff0c;时域上每个子载波 e j 2 π f n t e^{j2πf_nt} ej2πfn​t 都被一个矩形窗脉冲 g T ( t ) g_T(t) gT​(t)成型。其次&#xff0c;OFDM子载波间隔 等于 OFDM符号周期的…

正交性原理与维纳霍夫(正则)方程

有期望信号d&#xff08;n&#xff09;&#xff0c;纯净信号x&#xff08;n&#xff09;&#xff0c;以及噪声信号g&#xff08;n&#xff09;; 有滤波器h&#xff08;m&#xff09;,以及滤波器输出信号y&#xff08;n&#xff09;&#xff0c;滤波器输出纯净信号x的估计值y&a…

三大变换与自控(五)三角函数的正交性证明

在本系列文章的第一篇中&#xff0c;我们提到任何信号都可以被分解为三角函数&#xff0c;是因为三角函数的正交性&#xff0c;因此在三角函数构建的坐标系中可以绘制任何图形。现在我们就来证明一下三角函数的正交性&#xff0c;以完善我们整个推导过程。 教材上的三角函数系…

机器学习的数学基础(3):正交性原理(orthogonality principle)

思考这样一个问题&#xff0c;令S为一个希尔伯特空间&#xff0c;而空间S的一个子空间 当我们给定了&#xff0c;如何求最近上距离x最近的点。 则我们用数学语言表示该问题为一个优化问题&#xff1a; 该问题的解可以直接通过一个定理给出&#xff0c;即正交性定理&#xff0…

傅里叶级数与傅里叶变换_Part1_三角函数系的正交性

傅里叶级数与傅里叶变换_Part1_三角函数系的正交性 参考链接&#xff1a; DR_CAN老师的原视频 0、复习Part0的内容 参考链接&#xff1a;傅里叶级数与傅里叶变换_Part0_欧拉公式证明三角函数和差公式证明 三角函数的和差公式如下 sin ⁡ ( α β ) sin ⁡ ( α ) cos ⁡ …

09 正交性

09 正交性 9-1 空间&#xff0c;向量空间和欧几里得空间 9-2 广义向量空间 9-3 子空间 9-4 维度 9-5 行空间和矩阵的行秩 9-6 列空间 9-7 矩阵的秩和矩阵的逆 9-8 零空间与看待零空间的三个视角 9-9 零空间与秩-零化度定理 9-10 左零空间&#xff0c;四大子空间和研究子空间的…

CSS正交性分析

Refer: 为什么 CSS 这么难学&#xff1f; 我先来解释一下什么是正交。你调过显示器的「亮度」、「色调」和「饱和度」吧。 「亮度」就是明暗程度&#xff0c;值越大&#xff0c;屏幕越亮。 「色调」就是颜色&#xff0c;你通过调色调&#xff0c;可以把红色调成绿色。 「饱和度…

勒让德多项式的正交性和归一化

罗德里格斯公式正交性归一化应用 这学期上数学课时老师布置了一道习题&#xff1a;计算勒让德多项式的模。翻看本科数学物理方法教材&#xff0c;发现计算方法较复杂&#xff0c;且用到了生成函数 为了方便理清整个计算过程&#xff0c;这一博客直接从罗德里格斯公式出发并避免…