Unix网络编程 提高 TCP I/O 性能的3点经验

来源:互联网 发布:试衣服的软件 编辑:程序博客网 时间:2024/05/19 15:21

用John Nagle算法最小化报文传输延时
通过 TCP socket 进行通信时,数据都拆分成了数据块,放到一个TCP报文中为了达到最好的性能,总希望尽可能多的可用数据来填充每个报文已达到为最大报文段长度(maximum segment size 或MSS)。当没有足够的数据来填充 payload 时,TCP 就会采用 Nagle 算法自动将一些小的缓冲区连接到一个报文段中。这样可以通过最小化所发送的报文的数量来提高应用程序的效率,并减轻整体的网络拥塞问题。
但这导致了数据收发的延时,特别是当一端连续发送多个小型数据,然后读响应时,多个发送的数据会被 Nagle 算法整合成一个包然后发送,这样会造成大量读写socket请求时收发缓慢(比如本地机器1秒收发几十个请求/响应)。如果需要最小化传输延时,可以设置 TCP_NODELAY socket 选项以禁用 Nagle 算法,如下:
int sock, flag, ret;
/* Create new stream socket */
sock = socket( AF_INET, SOCK_STREAM, 0 );
/* Disable the Nagle (TCP No Delay) algorithm */
flag = 1;
ret = setsockopt( sock, IPPROTO_TCP, TCP_NODELAY, (void *)&flag, sizeof(flag) );
if (ret == -1) {
       printf("Couldn't setsockopt(TCP_NODELAY)/n");
       exit(-1);
}
经测试,简单的本地收发动作可以达到1秒30000次,效率提升了数千倍。当然在非本地机器环境下不可能提升这么多^_^。

二、减少系统调用 提高I/O效率
任何时候通过一个 socket 来读写数据时,您都是在使用一个系统调用(system call)。这个调用(例如 read 或 write)跨越了用户空间应用程序与内核的边界。另外,在进入内核之前,您的调用会通过 C 库来进入内核中的一个通用函数(system_call())。从 system_call() 中,这个调用会进入文件系统层,内核会在这儿确定正在处理的是哪种类型的设备。最后,调用会进入 socket 层,数据就是在这里进行读取或进行排队从而通过 socket 进行传输的(这涉及数据的副本)。
这个过程耗费的资源很高,因此调用次数越多,通过这个调用链进行的工作所需要的时间就越长,应用程序的性能也就越低。我们无法避免这些系统调用,但我们可以最

小化的使用这些调用。
在将数据读/写一个 socket 时,尽量一次读/写所有的数据,而不是执行多次读/写数据的操作:
write( sockfd, data1, ...);
write( sockfd, data2, ...);
write( sockfd, data3, ...);
read( sockfd, response, ...);
read( sockfd, result, ...);
改为
snprintf( data, format, .... );
write( sockfd, data, ...);
read( sockfd, result, ...);
// get response and handle result

三、调节合适的TCP发送/接收缓冲区
TCP 的性能取决于几个方面的因素。两个最重要的因素是链接带宽(link bandwidth)(报文在网络上传输的速率)和 往返时间(round-trip time) 或 RTT(发送报文与接收到另一端的响应之间的延时)。这两个值确定了称为 Bandwidth Delay Product(BDP)的内容:
BDP = link_bandwidth * RTT
带宽可以从网卡信息查看,RTT可以通过ping获取平均值。BDP决定了TCP滑动窗口的大小,即socket的发送接收缓冲区的大小。
如果应用程序是通过一个 100Mbps 的局域网进行通信,其 RRT 为 50 ms,那么 BDP 就是:
100MBps * 0.050 sec / 8 = 0.625MB = 625KB      此处除以 8 是将位转换成通信使用的字节。
因此,我们可以将 TCP 窗口设置为 BDP 或 1.25MB(2*625k)。
通过设置SO_RCVBUF和SO_SNDBUF,可以达到改变发送接收缓冲区大小的目的:
ret = getsockopt( sockfd, SOL_SOCKET, SO_SNDBUF,
                       (void *)&original, sizeof(original) );
if(....){
ret = setsockopt( sockfd, SOL_SOCKET, SO_SNDBUF
                       (void *)&new, sizeof(new) );
}

当然也可以设置系统的缓冲区,比如:
echo "8192 9000000 12000000" > /proc/sys/net/ipv4/tcp_wmem

原创粉丝点击