- 浏览: 36319 次
最新评论
UDP_CORK,TCP_CORK以及TCP_NODELAY
2010年10月14日
这三个选项十分有意思,并且困扰了很多人。特别是cork选项,它到底和nodelay有什么区别,到底怎样影响了Nagle算法。在tcp的实现中(特指linux内核的协议栈实现),cork和nodelay非常让人看不出区别,这一块的实现非常复杂,看内核实现之前最好先明白它们大概在说什么,否则很容易迷失的。
所谓的cork就是塞子的意思,形象地理解就是用cork将连接塞住,使得数据先不发出去,等到拔去塞子后再发出去,而nodelay事实上是为了禁用Nagle算法,Nagle算法为了增加了网络的吞吐量而牺牲了响应时间体验,这在有些应用中是不合适的,比如交互式应用(终端登录或者远程X应用 etc.),因此有必要提供一个选项将它禁用掉,Nagle算法在RFC1122中有提及,它的实现实际上很简单,利用了tcp本身的一些特性,在算法描述中,关键的一点是"什么时候真实的发送数据",这个问题的解答也是很简单,原则上只要发出的包都被对端ack了就可以发送了,这实际上也是一种权衡,Nagle算法最初的目的在于解决大量小包存在于网络从而造成网络拥塞的问题(一个小包可能只有几个字节,比如ls,cat等等,然而为每个小包封装几个协议头,其大小就不可忽视了,大量此类小包存在于网络势必会使得网络带宽的利用率大大下降),如果包被ack了,说明包已经离开了网络进入了对端主机,这样就可以发送数据了,此时无需再等,有多少数据发送多少(当然要考虑窗口大小和MTU),如果很极端地等待更多的数据,那么响应度会更低,换句话简单的说Nagle算法只允许一个未被ack的包存在于网络,它并不管包的大小,因此它事实上就是一个扩展的停-等协议,只不过它是基于包停-等的,而不是基于字节停-等的。
可以看出,Nagle算法完全由tcp协议的ack机制决定,这会带来一些问题,比如如果对端ack回复很快的话,Nagle事实上不会拼接太多的数据包,虽然避免了网络拥塞,网络总体的利用率依然很低,Nagle真的做到了"只做好一件事"的原则,然而有没有另外一种算法,可以提高整体网络利用率呢?也就是说尽量以不能再多的数据发送,这里之所以说是尽量还是权衡导致的,某时可以发送数据的时候将会发送数据,即使当前数据再小也不再等待后续的可能拼接成更大包的数据的到来。
实际上,这样的需求可以用TCP_CORK来实现,但是实现得可能并不像你想象的那么完美,cork并不会将连接完全塞住。内核其实并不知道应用层到底什么时候会发送第二批数据用于和第一批数据拼接以达到MTU的大小,因此内核会给出一个时间限制,在该时间内没有拼接成一个大包(努力接近MTU)的话,内核就会无条件发送,这里给出的只是一个大致的思想,真实的情况还要受到窗口大小以及拥塞情况的影响,因此tcp"何时发送数据"这个问题非常复杂。
Nagle算法和CORK算法非常类似,但是它们的着眼点不一样,Nagle算法主要避免网络因为太多的小包(协议头的比例非常之大)而拥塞,而CORK算法则是为了提高网络的利用率,使得总体上协议头占用的比例尽可能的小。如此看来这二者在避免发送小包上是一致的,在用户控制的层面上,Nagle算法完全不受用户socket的控制,你只能简单的设置TCP_NODELAY而禁用它,CORK算法同样也是通过设置或者清除TCP_cork使能或者禁用之,然而Nagle算法关心的是网络拥塞问题,只要所有的ack回来则发包,而CORK算法却可以关心内容,在前后数据包发送间隔很短的前提下(很重要,否则内核会帮你将分散的包发出),即使你是分散发送多个小数据包,你也可以通过使能CORK算法将这些内容拼接在一个包内,如果此时用Nagle算法的话,则可能做不到这一点。
接下来看一下内核代码,然后给出一个测试程序来感性感受这些选项。tcp的发送函数是tcp_sendmsg,这个函数中存在一个大循环,用于将用户数据置入skb中,它的形式如下:
int tcp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
size_t size)
{
while (--iovlen >= 0) {
0.更新数据结构元数据;
while (seglen > 0) {
int copy;
skb = sk->sk_write_queue.prev;
1.如果既有skb的长度过大或者根本还没有一个skb则分配一个skb;
2.将数据拷贝到既有的skb或者新的skb中;
3.更新skb和用户数据的元数据;
//如果数据还没有达到mss,则继续,换句话就是如果数据已经达到mss了就接着往下走来权衡是否马上发送。
if (skb->len != mss_now || (flags & MSG_OOB))
continue;
4.权衡发送与否
continue;
}
}
out:
//如果循环完成,所有数据都进入了skb,调用tcp_push来权衡是否发送
tcp_push(sk, tp, flags, mss_now, tp->nonagle);
}
tcp_push很短但是很复杂,
static inline void tcp_push(struct sock *sk, struct tcp_opt *tp, int flags,
int mss_now, int nonagle)
{
if (sk->sk_send_head) {
struct sk_buff *skb = sk->sk_write_queue.prev;
...
//如果有MSG_MORE,则当作cork来处理
__tcp_push_pending_frames(sk, tp, mss_now,
(flags & MSG_MORE) ? TCP_NAGLE_CORK : nonagle);
}
}
static __inline__ void __tcp_push_pending_frames(struct sock *sk,
struct tcp_opt *tp,
unsigned cur_mss,
int nonagle)
{
struct sk_buff *skb = sk->sk_send_head;
if (skb) {
if (!tcp_skb_is_last(sk, skb)) //如果已经有了很多的skb,则尽量马上发送
nonagle = TCP_NAGLE_PUSH;
//只有tcp_snd_test返回1才会发送数据,该函数很复杂
if (!tcp_snd_test(tp, skb, cur_mss, nonagle) ||
tcp_write_xmit(sk, nonagle))
tcp_check_probe_timer(sk, tp);
}
tcp_cwnd_validate(sk, tp);
}
static __inline__ int tcp_snd_test(struct tcp_opt *tp, struct sk_buff *skb,
unsigned cur_mss, int nonagle)
{
//如果有TCP_NAGLE_PUSH标志(或者tcp_nagle_check同意发送)且未ack的数据够少且...则可以发送
return (((nonagle&TCP_NAGLE_PUSH) || tp->urg_mode
|| !tcp_nagle_check(tp, skb, cur_mss, nonagle)) &&
((tcp_packets_in_flight(tp) snd_cwnd) ||
(TCP_SKB_CB(skb)->flags & TCPCB_FLAG_FIN)) &&
!after(TCP_SKB_CB(skb)->end_seq, tp->snd_una + tp->snd_wnd));
}
tcp_nagle_check函数是一个很重要的函数,它基本决定了数据是否可以发送的80%,内核源码中对该函数有一条注释:
-3. Or TCP_NODELAY was set.
-4. Or TCP_CORK is not set, and all sent packets are ACKed.
就是说如果TCP_NODELAY值为1就可以直接发送,或者cork被禁用的情况下所有发出的包都被ack了也可以发送数据,这里体现的就是Nagle算法和CORK算法的区别了,Nagle算法只要求所有的出发包都ack就可以发送,而不管当前包是否足够大(虽然它通过tcp_minshall_check保证了包不太小),而如果启用cork的话,可能仅仅数据被ack就不够了,这就是为何在代码注释中说cork要比Nagle更stronger的原因,同时这段代码也说明了为何TCP_CORK和TCP_NODELAY不能一起使用的原因,它们有共同的东西,却在做着不同的事情。看看tcp_nagle_check:
static __inline__ int
tcp_nagle_check(struct tcp_opt *tp, struct sk_buff *skb, unsigned mss_now, int nonagle)
{
return (skb->len flags & TCPCB_FLAG_FIN) &&
((nonagle&TCP_NAGLE_CORK) ||
(!nonagle &&
tp->packets_out &&
tcp_minshall_check(tp))));
}
看看__tcp_push_pending_frames的最后,有一个tcp_check_probe_timer调用,就是说在没有数据被发送的时候会调用这个函数。这个函数有两个作用,第一个是防止0窗口导致的死锁,另一个作用就是定时发送由于使能了CORK算法或者Nagle算法一直等待新数据拼接而没有机会发送的数据包。这个timer内置在重传timer之中,其时间间隔和rtt有关,一旦触发则会发送数据包或者窗口探测包。反过来可以理解,如果没有这个timer的话,启用cork的连接将几乎(可能根据实现的不同还会受别的因素影响,太复杂了)每次都发送mtu大小的数据包。该timer调用tcp_probe_timer函数:
static void tcp_probe_timer(struct sock *sk)
{
struct tcp_opt *tp = tcp_sk(sk);
int max_probes;
//1.如果有数据在网络上,则期望马上回来ack,ack中会通告对端窗口
//2.如果没有数据要发送,则无需关注对端窗口,即使为0也无所谓
if (tp->packets_out || !sk->sk_send_head) {
tp->probes_out = 0;
return;
}
//这个sysctl_tcp_retries2是可以调整的
max_probes = sysctl_tcp_retries2;
if (tp->probes_out > max_probes) {
tcp_write_err(sk);
} else {
tcp_send_probe0(sk);
}
}
tcp_send_probe0会调用tcp_write_wakeup函数,该函数会要么发送可以发送的数据,如果由于发送队列越过了发送窗口导致不能发送,则发送一个窗口探测包:
int tcp_write_wakeup(struct sock *sk)
{
if (sk->sk_state != TCP_CLOSE) {
struct tcp_opt *tp = tcp_sk(sk);
struct sk_buff *skb;
if ((skb = sk->sk_send_head) != NULL &&
before(TCP_SKB_CB(skb)->seq, tp->snd_una+tp->snd_wnd)) {
...//在sk_send_head队列上取出一个发送出去,其ack会带回对端通告窗口的大小
err = tcp_transmit_skb(sk, skb_clone(skb, GFP_ATOMIC));
...
return err;
} else {
...
return tcp_xmit_probe_skb(sk, 0);
}
}
return -1;
}
这个probe timer虽然一定程度阻碍了cork的满载发送,然而它却是必要的,这是由于tcp并不为纯的ack包(不带数据的ack包)提供确认,因此一旦这种ack包丢失,那么就有可能死锁,发送端的窗口无法更新,接收端由于已经发送了ack而等待接收数据,两端就这样僵持起来,因此需要一个timer,定期发送一个探测包,一个ack丢失,不能所有的ack都丢失吧,在timer到期时,如果本来发送队列上有数据要发送,则直接发送这些数据而不再发送探测包,因为发送了这些数据,所以它"破坏"了cork的承诺,不过也因此增强了响应度。
在示出应用程序之前,总结一下内核在哪里会发送tcp包,在解释在哪里会发送tcp包之前,首先说明内核协议栈为了高效和低耦合设计,tcp_sendmsg并不一定真实发送数据,真实发送数据的地点在:
1.tcp_sendmsg内部(废话!),如果权衡的结果需要发送则发送;
2.收到对端ack的时候会调用tcp_data_snd_check来发送,它同样完全按照cork策略来的;
3.probe timer到期后作为窗口探测包发送一些数据,它"破坏"了cork,在塞子上捅破一个口子;
4.连接断开或者进程退出时可能会将所有数据刷到对端;
5.当禁用cork或者启用nodelay的时候会将pending的数据刷入对端。
下面看一下应用层的测试程序:
客户端程序:client
#define BUFF_SIZE 500
#define REMOTE_PORT 6800
signed int len = 0;
int main(int argc, char *argv[])
{
int sock;
struct sockaddr_in remote_addr;
int on = 1;
unsigned char buff[BUFF_SIZE];
int i;
if (argc != 5) {
printf("usage: client server_ip on|off cork|nodelay usec\n");
exit(-1);
}
int msd = atoi(argv[4]);
if (!strcmp(argv[2], "on"))
on = 1;
else if (!strcmp(argv[2], "off"))
on = 0;
for (i = 0; i socket(AF_INET, SOCK_STREAM, 0);
if (!strcmp(argv[3], "nodelay")) {
setsockopt(sock, SOL_TCP, TCP_NODELAY, &dontroute, sizeof(dontroute));
} else if (!strcmp(argv[3], "cork")) {
setsockopt(sock, SOL_TCP, TCP_CORK, &dontroute, sizeof(dontroute));
}
struct sockaddr_in sa;
memset (&sa, '\0', sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_addr.s_addr = inet_addr (argv[1]);
sa.sin_port = htons(REMOTE_PORT);
connect(sock, (struct sockaddr*) &sa, sizeof(sa));
while(1) {
len = send(sock, buff, BUFF_SIZE, MSG_MORE);
if (len socket (AF_INET, SOCK_STREAM, 0);
memset (&sa_serv, '\0', sizeof(sa_serv));
sa_serv.sin_family = AF_INET;
sa_serv.sin_addr.s_addr = INADDR_ANY;
sa_serv.sin_port = htons (6800);
err = bind(listen_sd, (struct sockaddr*) &sa_serv, sizeof (sa_serv));
err = listen (listen_sd, 5);
client_len = sizeof(sa_cli);
while (1) {
sd = accept (listen_sd, (struct sockaddr*) &sa_cli, &client_len);
while (1) {
err = read(sd, buf, sizeof(buf));
if (err 发送,如果usleep的时间再长一些,probe timer就是"帮忙"发送数据了,给你的感觉是,启用了cork为何看起来没有什么用。这个时间在不同环境在有所不同,因为probe timer导致了cork的破坏,而probe timer和rtt有关,rtt又和网络环境有关...再进行一个测试,执行下列命令:sysctl -w net.ipv4.tcp_retries2=-1
然后以比较高的时间间隔以及比较小的BUFF_SIZE在开启cork情况下运行client程序,我们发现第一个包还没发完进程就会退出,这是由于cork尽力在组包,间隔过大导致probe timer过期,然后tp->probes_out > max_probes判断通过,导致超时退出,这个可以从/proc/net/netstat中的超时计数器中看出来,如果间隔比较短,每次新的数据pending到既有的skb上而不发送,重置probe timer,使得timer总是不过期,终于pending的数据到达了mtu的大小,cork的满载发送起作用进而发送之。
还有一个概念是"糊涂窗口",那就是接收端接收缓慢并不断确认,导致窗口一直很小,而发送端收到ack就再次发送小包,这样导致一直发送-确认很小的包...这个是可以通过应用层编程来避免的,另外也可以通过cork算法或者Nagle算法来减轻,但是无论怎样都逃不过一些timer自动帮你发送数据。
最后,好像遗漏了UDP_CORK,很简单,udp没有连接,没有确认,因此也就不需要什么timer之类的复杂机制,也因此,它是真正承诺的cork,除非你在应用层手工拔掉塞子,否则数据将不会发出。
发表评论
-
同步对象Event的用法
2012-01-20 10:27 669同步对象Event的用法 2010年07月05日 首先介 ... -
同步、异步、阻塞和非阻塞
2012-01-20 10:27 792同步、异步、阻塞和非阻塞 2010年11月03日 原文网 ... -
如何从Android系统中删除“无用”程序
2012-01-19 15:27 734如何从Android系统中删除“无用”程序 2010年10 ... -
刷埃及
2012-01-19 15:27 976刷埃及 2011年10月05日 1:电脑端: ... -
WINDOWS PHONE 7使用必知
2012-01-19 15:27 788WINDOWS PHONE 7使用必知 20 ... -
卸载――将vs2008彻底删除
2012-01-19 15:27 15134卸载――将vs2008彻底删除 2010年07月25日 ... -
解决导入Android-sample出错问题
2012-01-19 15:27 1558解决导入Android-sample出错问题 2011年07 ... -
我的日志
2012-01-17 05:14 808我的日志 7小时前 在座的所有人qt遐倏,赆伊∝ ... -
写给男孩懵懂的青春
2012-01-17 05:14 1264写给男孩懵懂的青春 7小时前 1. 清雅,脱俗。这是她带 ... -
木有标题
2012-01-17 05:14 788木有标题 7小时前 坐在山之巅,抬头欲抓住一片云彩,让它 ... -
准备买进!再创新低将是绝对的“空头陷阱”!
2012-01-17 05:14 835准备买进!再创新低将是绝对的“空头陷阱”! 7小时前 ... -
都2012了你还瞎晃悠啥呢
2012-01-17 05:14 776都2012了你还瞎晃悠啥呢 7小时前 ... -
自己做屏保
2012-01-16 04:07 1020自己做屏保 2011年05月08 ... -
黑客基地联盟网站刷钻教程欢迎转载
2012-01-16 04:06 5761黑客基地联盟网站刷钻教程欢迎转载 2010年07月28日 ... -
Windows消息拦截
2012-01-16 04:06 1720Windows消息拦截 2010年02月06日 拦截应 ... -
雨林木风 Windows server 2003
2012-01-16 04:06 993雨林木风 Windows server 2003 2009年 ... -
【源于网络知识点收集】插件破解方法专辑(二)
2012-01-16 04:06 846【源于网络知识点收集】插件破解方法专辑(二) 2010年03 ...
相关推荐
TCP/IP协议是互联网通信的基础,其中的Nagle算法、TCP_NODELAY和TCP_CORK是优化TCP连接传输效率的重要策略。Nagle算法旨在解决小数据包发送的问题,通过合并多个小数据段来减少网络中的小包数量,从而提高带宽利用率...
TCP_CORK禁用Nagle实现文件传输速度优化,加速tcp传输速度.zip
A great tutorial introduction of visual servo control by Peter Cork
apr_cv_tcp_nodelay_with_cork=yes apr_cv_mutex_recursive=yes \ apr_cv_mutex_robust_shared=no ac_cv_sizeof_struct_iovec=8 make && make install ``` **2. APR-Util** - **版本**:1.3.10 - **配置命令...
`TCP_NODELAY` 和 `TCP_CORK` 都是针对TCP协议中Nagle算法的配置项,用于控制数据发送行为。 **TCP_NODELAY:** - **作用:** 禁用Nagle算法,使得小的数据包能够立即发送而不是等待累积足够大的数据量再发送。 - *...
"装瓶器::bottle_with_popping_cork:简单的UI仪表板套件"是一个专为项目管理和跟踪设计的用户界面工具。这个仪表板套件以其直观、易用的特性,帮助用户高效地进行项目管理工作。它可能包含了各种组件和模块,如任务...
"bottle_with_popping_cork"、"speaking_head"和"red_question_mark"这些符号可能是项目或模板的图形元素,代表了互动性、对话和问题解答,暗示了这个模板是为创建交互式的语音应用设计的。 **标签解析:** - **...
GitHub-Flask GitHub-Flask是用于通过GitHub验证Flask应用程序的扩展。 它还为对GitHub API的各种其他请求提供支持。 与Python 2.7和3.4兼容。 安装 GitHub-Flask在PyPI上可用: $ pip install github-flask ...
7. **TCP优化**:包括TCP选项的设置,如TCP_NODELAY(禁用Nagle算法)和TCP_CORK,以优化小包传输效率;还有TCP Keepalive,用于检测长时间无数据交换的连接是否存活。 8. **UDP协议**:与TCP相比,UDP是一种无连接...
利用Render Props模式和Hooks来获得最大的灵活性以及新的Context API Handle承诺。 React组件和钩子用于声明式承诺解析和数据获取。 无需假设数据的形状或请求的类型,就可以轻松处理异步过程的每个状态。 与获取,...
轻松处理承诺。 React组件和钩子用于声明式承诺解析和数据获取。 无需假设数据的形状或请求的类型,就可以轻松处理异步过程的每个状态。 与fetch ,Axios或其他数据获取库(甚至GraphQL)一起使用。...
此仓库包含与开发人员和设计师相关的所有黑色星期五和网络星期一交易。 您有要求向我提交PR或DM :smiling_face_with_halo: 请关注此以提出更多建议。 注意:请勿发送垃圾邮件,否则PR将被拒绝。...
:joker::male_sign::joker:Déjamemostrarte un truco:bottle_with_popping_cork:LS:bottle_with_popping_cork:Blåturen:bottle_with_popping_cork:Sørlandstreff:minus::minus::minus::minus::minus::minus::...
- **TCP选项与参数**:如TCP_NODELAY(禁用Nagle算法),TCP_QUICKACK(快速确认),TCP_CORK(延迟发送)等,可以根据需求调整TCP的行为。 - **流量控制**:TCP通过滑动窗口机制实现流量控制,防止发送方过快导致...
apr_cv_mutex_robust_shared="no" apr_cv_tcp_nodelay_with_cork="yes" \ ac_cv_sizeof_struct_iovec="8" apr_cv_mutex_recursive="yes" ``` (3) 编译:`make` (4) 安装:`make install` **1.2 mipse-linux系统...
Webpack冲洗块 :bottle_with_popping_cork: :bottle_with_popping_cork: :bottle_with_popping_cork: :rocket: :rocket: :rocket: Webpack 4演示更新正在进行中现在支持Webpack 4积极的代码拆分我们更新了webpack-...
babel-plugin-universal-import安装yarn add babel-plugin-universal-import.babelrc: { "plugins" : [ "universal-import" ]}它能做什么从,它执行以下操作: import universal from 'react-universal-component'...
此外,熟悉并熟练运用套接字选项(如SO_REUSEADDR、SO_RCVBUF、SO_SNDBUF等)和TCP选项(如TCP_NODELAY、TCP_CORK等)可以优化网络性能。同时,理解TCP的拥塞控制算法(如慢启动、快速重传和快速恢复)有助于设计更...