TCP加速代理原型的实现与测试
socket通信报文格式一、基本原理
图1 Split-Connections机制与常规TCP连接的对比
图2 TCP代理通信过程
参考文献《Implementation Experiments of the TCP Proxy Mechanism》和RFC 3135,本文的TCP加速代理采用Split-Connections机制实现。Split-Connections是一种基本的TCP连接代理机制,代理服务器透明地将客户请求和服务器响应进行双向转换,以协助双方完成通信的全过程。如图1和图2所示,TCP代理A 与B分别位于发送端网络流出口处和接收端网络流出口处,当TCP发送端要与TCP接收端建立TCP连接时,TCP代理A截获该SYN包,并仿冒SYN包中所指定的TCP对端节点(Receiver host)回复SYN-ACK
包给发送端,从而与发送端之间建立TCP连接A段;同时,代理A会代替发送端发起与接收端之间的TCP 连接,而TCP代理B会截获该SYN包,并仿冒接收端回复SYN-ACK包给代理A,从而与代理A之间建立TCP连接B段;同时,代理B会与真正的TCP接收端之间建立TCP连接C段。这样Split-Connections 就建立完成了,之后TCP代理会代替TCP数据包接收端回复ACK相应包给发送端,并将TCP数据包转发给接收端。
采用这样的Split-Connections机制,我们只需在TCP代理上应用优化机制和更优的TCP协议栈,从而实现在不对原通信双方进行任何修改的基础上,透明的实现TCP层的加速,减少TCP流在广域网上的传输时间、加快新连接的建立、增大带宽利用率。
二、实现方案
依据上述原理,目前已实现了一个预先创建线程池的并发服务器版本的TCP加速代理原型。该原型的流程图如图3所示。
图3 TCP加速代理原型的流程图
在实现TCP加速代理服务器原型代码的基础上,我们对代理实施了一套TCP效率提升机制,包括:l选择性确认(Selective Acknowledgements),(RFC 2018)
TCP通信时,如果发送序列中间某个数据包丢失,TCP会通过重传最后确认的包开始的后续包,这样原先已经正确传输的包也可能重复发送,急剧降低TCP性能。而SACK(Selective Acknowledgment, 选择性确认)技术能使TCP只重新发送丢失的包,不用发送后续所有的包,而且提供相应机制使接收方能告诉发送方哪些数据丢失,哪些数据重发了,哪些数据已经提前收到等。SACK信息是通过TCP头的选项部分提供的,信息分两种,一种标识是否支持SACK,是在TCP握手时发送;另一种是具体的SACK信息。
我们在TCP代理上通过sysctl -w p_sack=1的方式启用SACK机制。
l Nagle算法– (RFC 896)
在通过TCP socket 进行通信时,数据都拆分成了数据块,这样它们就可以封装到给定连接的TCP payload(指TCP 数据包中的有效负荷)中了。TCP payload 的大小取决于几个因素(例如最大报文长度和路径),但是这些因素在连接发起时都是已知的。为了达到最好的性能,我们的目标是使用尽可能多的可用数据来填充每个报文。当没有足够的数据来填充payload 时(也称为最大报文段长度(maximu
m segment size)或MSS),TCP 就会采用Nagle 算法自动将一些小的缓冲区连接到一个报文段中。这样可以通过最小化所发送的报文的数量来提高应用程序的效率,并减轻整体的网络拥塞问题。
尽管Nagle算法可以通过将这些数据连接成更大的报文来最小化所发送的报文的数量,但是有时应用程序可能更希望只发送一些较小的报文。一个简单的例子是telnet 程序,它让用户可以与远程系统进行交互,这通常都是通过一个shell 来进行的。如果用户被要求用发送报文之前输入的字符来填充某个报文段,那么这种方法就绝对不能满足我们的需要。另外一个例子是HTTP 协议。通常,客户机浏览器会产生一个小请求(一条HTTP 请求消息),然后Web 服务器就会返回一个响应(Web 页面)。
因此,在需要最小化传输延时的情况中,我们可以在代理上,对此TCP连接禁用Nagle 算法,即设置TCP_NODELAY socket 选项,如下所示:
int sock, flag;
sock = socket( AF_INET, SOCK_STREAM, 0 );
/* 禁用Nagle算法*/
flag = 1;
setsockopt( sock, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(flag) );
l最小化系统调用的负载
任何时候通过一个socket 来读写数据时,都是在使用一个系统调用(system call)。这个调用(例如read 或write)跨越了用户空间应用程序与内核的边界。另外,在进入内核之前,调用会通过C 库来进入内核中的一个通用函数(system_call())。从system_call()中,这个调用会进入文件系统层,内核会在这儿确定正在处理的是哪种类型的设备。最后,调用会进入socket层,数据就是在这里进行读取或进行排队从而通过socket 进行传输的(这涉及数据的副本)。这个过程说明系统调用不仅仅是在应用程序和内核中进行操作的,而且还要经过应用程序和内核中的很多层次。这个过程耗费的资源很高,因此调用次数越多,通过这个调用链进行的工作所需要的时间就越长,应用程序的性能也就越低。因此,我们需要在代理上最小化使用这些调用的次数。
l调节TCP 窗口和socket缓冲区大小
Bandwidth Delay Product(BDP)的大小由链接带宽(link bandwidth)和RTT这两个值所确定,即BDP = link_bandwidth * RTT。BDP 给出了一种简单的方法来计算理论上最优的TCP socket缓冲区大小(其中保存了排队等待传输和等待应用程序接收的数据)。如果缓冲区太小,那么TCP窗口就不能完全打开,这会对TCP性能造成限制。如果缓冲区太大,那么宝贵的内存资源就会造成浪费。如果设置的缓冲区大小正好合适,那么就可以完全利用可用的带宽。下面我们来看一个例子:如果应用程序是通过一个100Mbps的局域网进行通信,其RRT为100 ms:
BDP = 100Mbps * 0.1sec / 8 = 1250KB
因此,我们可以将TCP默认缓冲区大小设置为1.25MB,同时,启用窗口扩大选项(RFC1323),使得TCP窗口定义从16位增加为32位,这样就能在通信过程中最优的利用可用带宽,而不因为受到窗口和缓冲区大小限制而浪费带宽。
在Linux 2.6上默认的TCP窗口大小是110KB,这会将上述TCP连接的吞吐量限制为8.8Mbps,其计算方法为throughput = window_size / RTT,则有
110KB*8 / 0.1sec = 8.8Mbps
而在Windows xp上,其默认TCP窗口大小仅64KB,其吞吐量会被限制在5.12 Mbps:
64KB*8/0.1 = 5.12Mbps
但是,当我们将默认TCP窗口设置为1.25MB时,其吞吐量能达到100Mbps:
1250KB *8/ 0.1 = 100Mbps
由此可见,采用不同大小的TCP窗口和socket缓冲区对吞吐量的影响很大。我们可以在代理上通过以下方法实现:
首先,用sysctl -w p_window_scaling=1使得TCP协议栈支持32位的TCP接收窗口;其次,修改socket缓冲区大小:
echo "4096 65536 524288">/proc/sys/net/ipv4/tcp_wmem
sysctl -wmem_max=2097152
echo "4096 65536 524288">/proc/sys/net/ipv4/tcp_rmem
sysctl -mem_max=2097152
sysctl -somaxconn=262144
sysctl -wmem_default=1048576
sysctl -mem_default=1048576
另外,可以利用SO_SNDBUF和SO_RCVBUF这两个socket选项来针对某一个或一类TCP连接来调整其发送和接收缓冲区的大小。
int sock, sock_buf_size;
sock = socket( AF_INET, SOCK_STREAM, 0 );
sock_buf_size = BDP;
ret = setsockopt( sock, SOL_SOCKET, SO_SNDBUF,(char *)&sock_buf_size, sizeof(sock_buf_size) );
ret = setsockopt( sock, SOL_SOCKET, SO_RCVBUF, (char *)&sock_buf_size, sizeof(sock_buf_size) );
为了能达到对网络带宽的最佳使用状态,在之后的工作中,我们希望能实现一个依据网络状态对BDP的值作出动态变化的工具。
l时间戳选项(RFC 1323)
时间戳选项使发送方在每个报文段中放置一个时间戳值。接收方在确认中返回这个数值,从而允许发送方为每一个收到的ACK计算RTT(此处必须指出“每一个收到的ACK”而不是“每一个报文段”,是因为TCP可以用一个ACK来确认多个报文段),从而实现对RTT的更准确的计算,尤其是在启用了32位大窗口时。当然,在获得了更精确的RTT计算值的同时,也增加了TCP包的长度,它会在包头增加12个字节,因而此应用会在增加网络的负载。
该选项的启用是通过sysctl -w p_timestamps=1来设置,当将其值设为0时就是关闭该选项。
l更优的TCP拥塞控制协议
目前大量学术研究表明TCP在高速网络中难以充分利用网络带宽,不能有效的进行大量的数据量传输。其原因在于标准TCP在拥塞避免阶段采用了AIMD(additive increase multiplicative decrease)策略,窗口增加慢,当因拥塞发生丢包时,拥塞窗口剧烈减小,这就导致了大拥塞窗口要在拥塞发生后恢复慢,限制了获取空闲带宽的能力,导致链路在相当长一段时间内利用率低。因此,在TCP代理上可以通过采用更优的TCP拥塞控制协议来达到加速的目的。现在可用的新TCP拥塞控制协议有:highspeed(简称HSTCP)-- RFC 3649《HighSpeed TCP for Large Congestion Windows》
scalable(简称STCP)--《Scalable TCP: Improving Performance in Highspeed Wide Area Networks》htcp -- 《H-TCP: TCP for High-Speed and Long-Distance Network》
cubic --《CUBIC: A New TCP-Friendly High-Speed TCP Variant》
首先,可以修改系统默认TCP拥塞控制协议:
sysctl -w p_congestion_control=highspeed
其次,可以利用socket选项TCP_CONGESTION(这些选项在include/linux/tcp.h中定义)来单独针对某个TCP连接设置其通信所采用的TCP拥塞控制算法。
int sock;
sock = socket( AF_INET, SOCK_STREAM, 0 );
ret = setsockopt( sock, SOL_SOCKET, SO_SNDBUF,(char *)&sock_buf_size, sizeof(sock_buf_size) );
三、实验环境及测试结果分析
我们的实验测试包括广域网上的真实网络测试,和局域网中模拟广域网的可控环境测试。
1、真实网络测试

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。