- 浏览: 188782 次
- 性别:
- 来自: 北京
文章分类
最新评论
-
grzrt:
zkf55915 写道哥们怎么用啊
好久不用了,就是看帮助资 ...
淘宝MetaQ开源消息队列安装 -
zkf55915:
哥们怎么用啊
淘宝MetaQ开源消息队列安装 -
grzrt:
jinnianshilongnian 写道整这个了?
没有 看 ...
linux内核中链表的实现 -
jinnianshilongnian:
整这个了?
linux内核中链表的实现
案例一:同事随手写个压力测试程序,其实现逻辑为:每秒钟先连续发N个132字节的包,然后连续收N个由后台服务回显回来的132字节包。其代码简化如下:
char sndBuf[132];
char rcvBuf[132];
while (1) {
for (int i = 0; i < N; i++){
send(fd, sndBuf, sizeof(sndBuf), 0);
...
}
for (int i = 0; i < N; i++) {
recv(fd, rcvBuf, sizeof(rcvBuf), 0);
...
}
sleep(1);
}
在实际测试中发现,当N大于等于3的情况,第2秒之后,每次第三个recv调用,总会阻塞40毫秒左右,但在分析Server端日志时,发现所有请求在Server端处理时耗均在2ms以下。
当时的具体定位过程如下:先试图用strace跟踪客户端进程,但奇怪的是:一旦strace attach上进程,所有收发又都正常,不会有阻塞现象,一旦退出strace,问题重现。经同事提醒,很可能是strace改变了程序或系统的某些东西(这个问题现在也还没搞清楚),于是再用tcpdump抓包分析,发现Server后端在回现应答包后,Client端并没有立即对该数据进行ACK确认,而是等待了近40毫秒后才确认。经过Google,并查阅《TCP/IP详解卷一:协议》得知,此即TCP的延迟确认(Delayed Ack)机制。
其解决办法如下:在recv系统调用后,调用一次setsockopt函数,设置TCP_QUICKACK。最终代码如下:
char sndBuf[132];
char rcvBuf[132];
while (1) {
for (int i = 0; i < N; i++) {
send(fd, sndBuf, 132, 0);
...
}
for (int i = 0; i < N; i++) {
recv(fd, rcvBuf, 132, 0);
setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, (int[]){1}, sizeof(int));
}
sleep(1);
}
案例二:在营销平台内存化CDKEY版本做性能测试时,发现请求时耗分布异常:90%的请求均在2ms以内,而10%左右时耗始终在38-42ms之间,这是一个很有规律的数字:40ms。因为之前经历过案例一,所以猜测同样是因为延迟确认机制引起的时耗问题,经过简单的抓包验证后,通过设置TCP_QUICKACK选项,得以解决时延问题。
延迟确认机制
在《TCP/IP详解卷一:协议》第19章对其进行原理进行了详细描述:TCP在处理交互数据流(即Interactive Data Flow,区别于Bulk Data Flow,即成块数据流,典型的交互数据流如telnet、rlogin等)时,采用了Delayed Ack机制以及Nagle算法来减少小分组数目。
书上已经对这两种机制的原理讲的很清晰,这里不再做复述。本文后续部分将通过分析TCP/IP在Linux下的实现,来解释一下TCP的延迟确认机制。
1、为什么TCP延迟确认会导致延迟?
其实仅有延迟确认机制,是不会导致请求延迟的(初以为是必须等到ACK包发出去,recv系统调用才会返回)。一般来说,只有当该机制与Nagle算法或拥塞控制(慢启动或拥塞避免)混合作用时,才可能会导致时耗增长。我们下面来详细看看是如何相互作用的:
延迟确认与Nagle算法
我们先看看Nagle算法的规则(可参考tcp_output.c文件里tcp_nagle_check函数注释):
1)如果包长度达到MSS,则允许发送;
2)如果该包含有FIN,则允许发送;
3)设置了TCP_NODELAY选项,则允许发送;
4)未设置TCP_CORK选项时,若所有发出去的包均被确认,或所有发出去的小数据包(包长度小于MSS)均被确认,则允许发送。
对于规则4),就是说要求一个TCP连接上最多只能有一个未被确认的小数据包,在该分组的确认到达之前,不能发送其他的小数据包。如果某个小分组的确认被延迟了(案例中的40ms),那么后续小分组的发送就会相应的延迟。也就是说延迟确认影响的并不是被延迟确认的那个数据包,而是后续的应答包。
1 00:44:37.878027 IP 172.25.38.135.44792 > 172.25.81.16.9877: S 3512052379:3512052379(0) win 5840 <mss 1448,wscale 7>
2 00:44:37.878045 IP 172.25.81.16.9877 > 172.25.38.135.44792: S 3581620571:3581620571(0) ack 3512052380 win 5792 <mss 1460,wscale 2>
3 00:44:37.879080 IP 172.25.38.135.44792 > 172.25.81.16.9877: . ack 1 win 46
......
4 00:44:38.885325 IP 172.25.38.135.44792 > 172.25.81.16.9877: P 1321:1453(132) ack 1321 win 86
5 00:44:38.886037 IP 172.25.81.16.9877 > 172.25.38.135.44792: P 1321:1453(132) ack 1453 win 2310
6 00:44:38.887174 IP 172.25.38.135.44792 > 172.25.81.16.9877: P 1453:2641(1188) ack 1453 win 102
7 00:44:38.887888 IP 172.25.81.16.9877 > 172.25.38.135.44792: P 1453:2476(1023) ack 2641 win 2904
8 00:44:38.925270 IP 172.25.38.135.44792 > 172.25.81.16.9877: . ack 2476 win 118
9 00:44:38.925276 IP 172.25.81.16.9877 > 172.25.38.135.44792: P 2476:2641(165) ack 2641 win 2904
10 00:44:38.926328 IP 172.25.38.135.44792 > 172.25.81.16.9877: . ack 2641 win 134
从上面的tcpdump抓包分析看,第8个包是延迟确认的,而第9个包的数据,在Server端(172.25.81.16)虽然早就已放到TCP发送缓冲区里面(应用层调用的send已经返回)了,但按照Nagle算法,第9个包需要等到第个7包(小于MSS)的ACK到达后才能发出。
延迟确认与拥塞控制
我们先利用TCP_NODELAY选项关闭Nagle算法,再来分析延迟确认与TCP拥塞控制是如何互相作用的。
慢启动:TCP的发送方维护一个拥塞窗口,记为cwnd。TCP连接建立是,该值初始化为1个报文段,每收到一个ACK,该值就增加1个报文段。发送方取拥塞窗口与通告窗口(与滑动窗口机制对应)中的最小值作为发送上限(拥塞窗口是发送方使用的流控,而通告窗口则是接收方使用的流控)。发送方开始发送1个报文段,收到ACK后,cwnd从1增加到2,即可以发送2个报文段,当收到这两个报文段的ACK后,cwnd就增加为4,即指数增长:例如第一个RTT内,发送一个包,并收到其ACK,cwnd增加1,而第二个RTT内,可以发送两个包,并收到对应的两个ACK,则cwnd每收到一个ACK就增加1,最终变为4,实现了指数增长。
在Linux实现里,并不是每收到一个ACK包,cwnd就增加1,如果在收到ACK时,并没有其他数据包在等待被ACK,则不增加。
本人使用案例1的测试代码,在实际测试中,cwnd从初始值2开始,最终保持3个报文段的值,tcpdump结果如下:
1 16:46:14.288604 IP 172.16.1.3.1913 > 172.16.1.2.20001: S 1324697951:1324697951(0) win 5840 <mss 1460,wscale 2>
2 16:46:14.289549 IP 172.16.1.2.20001 > 172.16.1.3.1913: S 2866427156:2866427156(0) ack 1324697952 win 5792 <mss 1460,wscale 2>
3 16:46:14.288690 IP 172.16.1.3.1913 > 172.16.1.2.20001: . ack 1 win 1460
......
4 16:46:15.327493 IP 172.16.1.3.1913 > 172.16.1.2.20001: P 1321:1453(132) ack 1321 win 4140
5 16:46:15.329749 IP 172.16.1.2.20001 > 172.16.1.3.1913: P 1321:1453(132) ack 1453 win 2904
6 16:46:15.330001 IP 172.16.1.3.1913 > 172.16.1.2.20001: P 1453:2641(1188) ack 1453 win 4140
7 16:46:15.333629 IP 172.16.1.2.20001 > 172.16.1.3.1913: P 1453:1585(132) ack 2641 win 3498
8 16:46:15.337629 IP 172.16.1.2.20001 > 172.16.1.3.1913: P 1585:1717(132) ack 2641 win 3498
9 16:46:15.340035 IP 172.16.1.2.20001 > 172.16.1.3.1913: P 1717:1849(132) ack 2641 win 3498
10 16:46:15.371416 IP 172.16.1.3.1913 > 172.16.1.2.20001: . ack 1849 win 4140
11 16:46:15.371461 IP 172.16.1.2.20001 > 172.16.1.3.1913: P 1849:2641(792) ack 2641 win 3498
12 16:46:15.371581 IP 172.16.1.3.1913 > 172.16.1.2.20001: . ack 2641 win 4536
上表中的包,是在设置TCP_NODELAY,且cwnd已经增长到3的情况,第7、8、9发出后,受限于拥塞窗口大小,即使此时TCP缓冲区有数据可以发送亦不能继续发送,即第11个包必须等到第10个包到达后,才能发出,而第10个包明显有一个40ms的延迟。
注:通过getsockopt的TCP_INFO选项(man 7 tcp)可以查看TCP连接的详细信息,例如当前拥塞窗口大小,MSS等。
2、为什么是40ms?这个时间能不能调整呢?
首先在redhat的官方文档中,有如下说明:
一些应用在发送小的报文时,可能会因为TCP的Delayed Ack机制,导致一定的延迟。其值默认为40ms。可以通过修改tcp_delack_min,调整系统级别的最小延迟确认时间。例如:
# echo 1 > /proc/sys/net/ipv4/tcp_delack_min
即是期望设置最小的延迟确认超时时间为1ms。
不过在slackware和suse系统下,均未找到这个选项,也就是说40ms这个最小值,在这两个系统下,是无法通过配置调整的。
linux-2.6.39.1/net/tcp.h下有如下一个宏定义:
#define TCP_DELACK_MIN ((unsigned)(HZ/25)) /* minimal time to delay before sending an ACK */
注:Linux内核每隔固定周期会发出timer interrupt(IRQ 0),HZ是用来定义每秒有几次timer interrupts的。举例来说,HZ为1000,代表每秒有1000次timer interrupts。HZ可在编译内核时设置。在我们现有服务器上跑的系统,HZ值均为250。
以此可知,最小的延迟确认时间为40ms。
TCP连接的延迟确认时间一般初始化为最小值40ms,随后根据连接的重传超时时间(RTO)、上次收到数据包与本次接收数据包的时间间隔等参数进行不断调整。具体调整算法,可以参考linux-2.6.39.1/net/ipv4/tcp_input.c, Line 564的tcp_event_data_recv函数。
3、为什么TCP_QUICKACK需要在每次调用recv后重新设置?
在man 7 tcp中,有如下说明:
TCP_QUICKACK
Enable quickack mode if set or disable quickack mode if cleared. In quickack mode, acks are sent immediately, rather than delayed if needed in accordance to normal TCP operation. This flag is not permanent, it only enables a switch to or from quickack mode. Subsequent operation of the TCP protocol will once again enter/leave quickack mode depending on internal protocol processing and factors such as delayed ack timeouts occurring and data transfer. This option should not be used in code intended to be portable.
手册中明确描述TCP_QUICKACK不是永久的。那么其具体实现是如何的呢?参考setsockopt函数关于TCP_QUICKACK选项的实现:
case TCP_QUICKACK:
if (!val) {
icsk->icsk_ack.pingpong = 1;
} else {
icsk->icsk_ack.pingpong = 0;
if ((1 << sk->sk_state) &
(TCPF_ESTABLISHED | TCPF_CLOSE_WAIT) &&
inet_csk_ack_scheduled(sk)) {
icsk->icsk_ack.pending |= ICSK_ACK_PUSHED;
tcp_cleanup_rbuf(sk, 1);
if (!(val & 1))
icsk->icsk_ack.pingpong = 1;
}
}
break;
其实linux下socket有一个pingpong属性来表明当前链接是否为交互数据流,如其值为1,则表明为交互数据流,会使用延迟确认机制。但是pingpong这个值是会动态变化的。例如TCP链接在要发送一个数据包时,会执行如下函数(linux-2.6.39.1/net/ipv4/tcp_output.c, Line 156):
/* Congestion state accounting after a packet has been sent. */
static void tcp_event_data_sent(struct tcp_sock *tp,
struct sk_buff *skb, struct sock *sk)
{
......
tp->lsndtime = now;
/* If it is a reply for ato after last received
* packet, enter pingpong mode.
*/
if ((u32)(now - icsk->icsk_ack.lrcvtime) < icsk->icsk_ack.ato)
icsk->icsk_ack.pingpong = 1;
}
最后两行代码说明:如果当前时间与最近一次接受数据包的时间间隔小于计算的延迟确认超时时间,则重新进入交互数据流模式。也可以这么理解:延迟确认机制被确认有效时,会自动进入交互式。
通过以上分析可知,TCP_QUICKACK选项是需要在每次调用recv后重新设置的。
4、为什么不是所有包都延迟确认?
TCP实现里,用tcp_in_quickack_mode(linux-2.6.39.1/net/ipv4/tcp_input.c, Line 197)这个函数来判断是否需要立即发送ACK。其函数实现如下:
/* Send ACKs quickly, if "quick" count is not exhausted
* and the session is not interactive.
*/
static inline int tcp_in_quickack_mode(const struct sock *sk)
{
const struct inet_connection_sock *icsk = inet_csk(sk);
return icsk->icsk_ack.quick && !icsk->icsk_ack.pingpong;
}
要求满足两个条件才能算是quickack模式:
1、pingpong被设置为0。
2、快速确认数(quick)必须为非0。
关于pingpong这个值,在前面有描述。而quick这个属性其代码中的注释为:scheduled number of quick acks,即快速确认的包数量,每次进入quickack模式,quick被初始化为接收窗口除以2倍MSS值(linux-2.6.39.1/net/ipv4/tcp_input.c, Line 174),每次发送一个ACK包,quick即被减1。
5、关于TCP_CORK选项
TCP_CORK选项与TCP_NODELAY一样,是控制Nagle化的。
1、打开TCP_NODELAY选项,则意味着无论数据包是多么的小,都立即发送(不考虑拥塞窗口)。
2、如果将TCP连接比喻为一个管道,那TCP_CORK选项的作用就像一个塞子。设置TCP_CORK选项,就是用塞子塞住管道,而取消TCP_CORK选项,就是将塞子拔掉。例如下面这段代码:
int on = 1;
setsockopt(sockfd, SOL_TCP, TCP_CORK, &on, sizeof(on)); //set TCP_CORK
write(sockfd, ...); //e.g., http header
sendfile(sockfd, ...); //e.g., http body
on = 0;
setsockopt(sockfd, SOL_TCP, TCP_CORK, &on, sizeof(on)); //unset TCP_CORK
当TCP_CORK选项被设置时,TCP链接不会发送任何的小包,即只有当数据量达到MSS时,才会被发送。当数据传输完成时,通常需要取消该选项,以便被塞住,但是又不够MSS大小的包能及时发出去。如果应用程序确定能一起发送多个数据集合(例如HTTP响应的头和正文),建议设置TCP_CORK选项,这样在这些数据之间不存在延迟。为提升性能及吞吐量,Web Server、文件服务器这一类一般会使用该选项。
著名的高性能Web服务器Nginx,在使用sendfile模式的情况下,可以设置打开TCP_CORK选项:将nginx.conf配置文件里的tcp_nopush配置为on。(TCP_NOPUSH与TCP_CORK两个选项实现功能类似,只不过NOPUSH是BSD下的实现,而CORK是Linux下的实现)。另外Nginx为了减少系统调用,追求性能极致,针对短连接(一般传送完数据后,立即主动关闭连接,对于Keep-Alive的HTTP持久连接除外),程序并不通过setsockopt调用取消TCP_CORK选项,因为关闭连接会自动取消TCP_CORK选项,将剩余数据发出。
原文: http://blog.csdn.net/turkeyzhou/article/details/6764389
发表评论
-
Eclipse 相同变量的高亮 及颜色
2013-02-18 17:26 1664在Eclipse/MyEclipse中如果不小心把变量的高 ... -
copy项目是容易出现的错误--webAppRootKey错误
2012-12-05 21:18 733Tomcat 发布多个项目时抛的webAppRootKey错误 ... -
redis主从的配置和使用
2012-11-23 14:24 1039redis主从的配置和使 ... -
Zookeeper的一致性协议:Zab
2012-11-04 16:14 1241Zookeeper使用了一种称为 ... -
Linux修改MySql默认存储引擎为InnoDB
2012-09-13 18:25 1581一、关闭相关应用 二、停止mysql bin/m ... -
MySQL数据库的初始化mysql_install_db
2012-09-13 14:13 4684一、mysql_install_db说明 当MySQL的 ... -
四层和七层负载均衡的区别介绍
2012-09-12 11:46 879简单理解四层和七层负载均衡:①所谓四层就是基于IP+端口 ... -
Linux下高并发Tcp需要突破的限制
2012-09-06 13:47 7831、修改用户进程可打开文件数限制 在Linux平台上, ... -
Nagle算法 TCP_NODELAY和TCP_CORK
2012-09-06 08:43 1221Nagle算法 根据创建者John Nagle命 ... -
Oracle之自治事务
2012-09-04 11:32 1003昨天处理项目中的一个 ... -
socket中accept()函数的理解
2012-09-01 22:41 6847如果客户端有连接请 ... -
mysql 主从复制1201错误
2012-08-19 15:59 949工作日志之-MySQL slave Replication E ... -
Redis学习手册(主从复制)
2012-08-19 11:39 824一、Redis的Replication: 这里首先需要说 ... -
Redis学习手册(持久化)
2012-08-19 11:39 782一、Redis提供了哪些持久化机制: 1). RDB持久 ... -
Redis学习手册(虚拟内存)
2012-08-19 11:38 729一、简介: 和大多NoS ... -
socket中的TIME_WAIT状态
2012-08-16 11:47 773TCP要保证在所有可能的 ... -
bloom filter 的Java 版
2012-07-26 21:50 888属于转贴:http://www.cnblo ... -
人生吐槽
2012-07-18 15:36 678自从09年毕业到现在正好三年, -
MySQL源码分析(1):主要模块及数据流
2012-05-26 11:07 1140经过多年的发展,mysql的主要模块已经稳定,基本不会有大的修 ... -
Handlersocket的安装
2012-05-25 21:13 856一、下载mysql,我选择的是mysql-5.5.15源码安装 ...
相关推荐
- **自适应RTT计算**:Linux TCP根据当前网络状况动态调整RTT的计算方法,从而更准确地反映实际网络延迟。 - **多路径支持**:为了提高带宽利用率和容错能力,Linux TCP支持多路径传输,即使在网络路径发生变化时也...
在Linux操作系统中,TCP(Transmission Control Protocol)是一种广泛使用的传输层协议,它是互联网协议栈(TCP/IP协议族)的重要组成部分。TCP确保了数据的可靠传输,通过提供面向连接、顺序交付和错误检测的服务,...
深入研究Linux TCP源码有助于我们理解TCP协议的内在工作原理,解决网络问题,甚至定制和优化TCP行为以满足特定应用的需求。如果你正在从事网络编程或者系统优化,这份资料将会是一个宝贵的参考。
分析工作开始之前需要对Linux内核进行准备,了解用户层的TCP工作原理是基础,这包括对RFC文档的阅读,如RFC793定义了传输控制协议的基本规则,RFC1323介绍了TCP的高性能扩展,RFC2018讨论了TCP的选择性确认(SACK)...
在无线通信领域,尤其是使用Linux系统环境下的TCP通信,经常会遇到各种问题,其中最常见的是**通信超时**和**接收超时**。 1. **通信超时**: - **网络拥塞**:无线模块在尝试获取网络资源时可能会遇到网络拥塞的...
TCP连接迁移是解决网络服务器负载问题的关键技术之一,尤其在Linux环境中,通过对TCP/IP协议栈的定制和内核级别的操作,可以实现连接的透明迁移。这种技术提高了网络服务的可伸缩性和可靠性,对于服务器集群的负载...
同时,TCP还采用确认应答和重传机制,确保数据在在网络中的可靠传输。在Linux中,开发人员可以使用套接字(socket)API来创建和操作TCP连接,例如调用`socket()`, `bind()`, `listen()`, `accept()` 和 `connect()` ...
- **特定功能支持**:可能包含对某些特定TCP选项或扩展的支持,如TCP Fast Open、延迟确认或SACK(选择性确认)。 - **故障恢复和可靠性**:在高可用性场景下,可能有额外的错误检测和恢复机制,比如心跳检测、连接...
9. **TCP性能优化**:TCP窗口大小调整、延迟确认、快速重传和快速恢复等技术可以提升TCP的性能。了解这些技术可以帮助你根据实际情况优化网络应用。 10. **示例代码解析**:压缩包中的TCP文件很可能包含客户端和...
TCP提供多种选项以优化性能,例如`TCP_NODELAY`禁用Nagle算法以减少小包延迟,`TCP_QUICKACK`快速确认等。这些选项可以通过`setsockopt()`设置。 六、TCP流量控制与拥塞控制 TCP内置了流量控制和拥塞控制机制。...
Linux内核的TCP/IP协议栈还包括了多种优化措施,如快速重传、快速恢复、延迟确认等,以提高网络性能和响应速度。 通过阅读和分析Linux 2.6.18内核的TCP/IP协议栈源码,我们可以深入了解网络通信的底层原理,这对于...
- `tcp_sack`:选择性确认(SACK)允许接收方告诉发送方哪些数据段已经正确接收,从而优化重传策略。 - `tcp_timestamps`:启用时间戳以支持PAWS(无序数据包拒绝服务)和RTT(往返时间)测量。 - `tcp_rmem`/`...
- **快速重传和快速恢复**:当接收端检测到失序的报文段时,会立即发送重复确认,从而让发送端快速重传未收到的段,减少延迟。 - **拥塞避免**:在检测到网络拥塞迹象后,TCP会线性或指数性地减小拥塞窗口,以避免...
除了基本的协议实现,书中的内容可能还会扩展到其他高级主题,如TCP连接的释放(四次挥手),TCP的延迟确认,以及Nagle算法等优化策略。对于C/C++程序员来说,理解这些底层机制有助于写出更高效、更稳定的网络程序。...
标题中的“TCP.zip_tcp ip协议栈_tcp linux”表明这是一个关于TCP/IP协议栈...此外,还能帮助读者解决网络通信中可能出现的问题,如丢包、拥塞、延迟等,并能更好地理解和利用Linux内核提供的工具进行网络诊断和优化。
6. **性能优化**:Linux 2.6内核中还包含了一些针对特定场景的优化,比如延迟应答、Nagle算法(减少小包发送)和SACK(选择性确认)选项,以提高网络效率。 在"30 TCP的输出.pdf"文档中,可能会详细解析TCP连接的...
- **性能优化**:包括快速重传、延迟确认等策略,以提高TCP的效率。 学习Linux版sock源码,不仅能深入理解TCP/IP协议的工作原理,还能帮助开发者优化网络应用,解决网络编程中遇到的实际问题。这是一项基础而关键的...
最后,优化TCP性能是另一个重要的方面,包括调整TCP窗口大小、使用Nagle算法、延迟确认等策略,以提高网络效率。同时,了解TCP的拥塞控制算法,如慢启动、快速重传和快速恢复,可以帮助理解TCP在网络中的行为。 ...
TCP是一种面向连接的、可靠的传输协议,它保证了数据的顺序和无丢失传输,而UDP则是一种无连接的、不可靠的传输协议,它不保证数据的顺序或完整性,但具有更低的延迟和更高的效率。 **Linux TCP服务器编程** 在...
在Windows和Linux操作系统中,TCP都得到了深入的应用和优化,以确保跨平台的数据通信的高效性和稳定性。 在Windows系统中,TCP实现主要由Windows Socket API(Winsock)提供,它为应用程序提供了与网络协议无关的...