【发布时间】:2017-02-01 09:31:45
【问题描述】:
我有一个用 C 语言编写的客户端服务器程序。其目的是了解通过 TCP 传输大数据的速度有多快。接收端操作系统(Ubuntu Linux 14.*)经过调整以提高 TCP 性能,根据以下关于 tcp / socket / windows scaling 等的文档:
net.ipv4.tcp_window_scaling = 1
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 16384 16777216
这个aprt,我还通过setsockopt调用增加了单个套接字缓冲区的大小。
但我没有看到程序对这些变化做出响应 - 总体吞吐量有时持平甚至下降。当我在接收端使用 tcpdump 时,在大多数 (99%) 情况下,我看到长度为 1368 的 tcp 数据包的单调模式。
19:26:06.531968 IP <SRC> > <DEST>: Flags [.], seq 25993:27361, ack 63, win 57, options [nop,nop,TS val 196975830 ecr 488095483], length 1368
根据文档,tcp 窗口缩放选项会根据需求和容量增加接收帧大小 - 但我看到的只是“win 57” - 接收缓冲区中剩余的字节非常少,与期待。
因此我开始怀疑我对调整本身的假设,并有以下问题:
发送端是否需要任何特定的可调参数来改善客户端接收?确保您(程序)一次写入整个数据块还不够?
在客户端可调如上面提到的必要和充分?系统中的默认值太低,但我看不到 /etc/sysctl.conf 中应用的更改有任何影响。更改后运行 sysctl --system 是否足以使更改生效?还是我们需要重启系统?
如果操作系统是虚拟机,这些可调参数是否会在其完整性方面有意义,或者在真实物理机上是否有额外的步骤?
如果有帮助,我可以分享源代码,但我可以保证它只是一个微不足道的代码。
代码如下:
#cat client.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#define size 1024 * 1024 * 32
int main(){
int s;
char buffer[size];
struct sockaddr_in sa;
socklen_t addr_size;
s = socket(PF_INET, SOCK_STREAM, 0);
sa.sin_family = AF_INET;
sa.sin_port = htons(25000);
sa.sin_addr.s_addr = inet_addr("<SERVERIP");
memset(sa.sin_zero, '\0', sizeof sa.sin_zero);
addr_size = sizeof sa;
connect(s, (struct sockaddr *) &sa, addr_size);
int rbl = 1048576;
int g = setsockopt(s, SOL_SOCKET, SO_RCVBUF, &rbl, sizeof(rbl));
while(1) {
int ret = read(s, buffer, size);
if(ret <= 0) break;
}
return 0;
}
以及服务器代码:
bash-4.1$ cat server.c
#include <sys/types.h>
#include <sys/mman.h>
#include <memory.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <errno.h>
#include <stdio.h>
#include <sys/socket.h>
extern int errno;
#define size 32 * 1024 * 1024
int main() {
int fdsocket;
struct sockaddr_in sock;
fdsocket = socket(AF_INET,SOCK_STREAM, 0);
int rbl = 1048576;
int g = setsockopt(fdsocket, SOL_SOCKET, SO_SNDBUF, &rbl, sizeof(rbl));
sock.sin_family = AF_INET;
sock.sin_addr.s_addr = inet_addr("<SERVERIP");
sock.sin_port = htons(25000);
memset(sock.sin_zero, '\0', sizeof sock.sin_zero);
g = bind(fdsocket, (struct sockaddr *) &sock, sizeof(sock));
if(g == -1) {
fprintf(stderr, "bind error: %d\n", errno);
exit(1);
}
int p = listen(fdsocket, 1);
char *buffer = (char *) mmap(NULL, size, PROT_WRITE|PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if(buffer == -1) {
fprintf(stderr, "%d\n", errno);
exit(-1);
}
memset(buffer, 0xc, size);
int connfd = accept(fdsocket, (struct sockaddr*)NULL, NULL);
rbl = 1048576;
g = setsockopt(connfd, SOL_SOCKET, SO_SNDBUF, &rbl, sizeof(rbl));
int wr = write(connfd, buffer, size);
close(connfd);
}
【问题讨论】:
-
为了获得有用的答案,您需要提供有关您的设置和遇到的问题的更多详细信息。例如,“我有两台具有 10gbit/s 网卡的机器连接到同一交换机上的两个 10gbit/s 交换机端口,但我似乎无法获得超过 5gbit/s 的吞吐量”与“我有一个客户端和服务器通过 WAN 连接,RTT 为 600 毫秒。我能做些什么来提高吞吐量。”
-
您似乎正在尝试增加接收器的窗口大小以加快传输速度。这是否甚至导致接收器宣传更大的窗口大小?只有当窗口大小是一个限制因素时,这才会有所帮助。如果您在进行更改之前进行了网络捕获,并使用 Wireshark 过滤器 tcp.analysis.flags 进行了查看,并且您看到零窗口广告,那么,是的,增加窗口大小会有所帮助。您能做的最好的事情是获取网络捕获并查看 Wireshark 中的 IO 图和 tcp.analysis.flags,看看有什么可以改进的。
-
win 57可能未缩放:参见wireshark and tcpdump -r: strange tcp window sizes。 -
服务器的
write()调用的返回值是多少?它真的发送了整个 32 MB 吗? (还要注意write()返回ssize_t,而不是int。它们不相同。)另外,您如何知道您的setsockopt()调用实际上可以增加您的套接字缓冲区大小?你忽略返回值。 -
@GireeshPunathil * 我看到 ssize_t 起源于: typedef long int __ssize_t;并且在 32 位系统中 int 和 ssize_t 是相同的。* 我看到您没有编写可移植或 64 位代码的经验。如果将
-m64之类的东西添加到您的完全独立且不相关的 编译器选项和您的代码中断。同样,ssize_t和int不一样。标准指定ssize_t而不是int或long int是有原因的。忽略它,你写的最好的情况是不可移植的,最坏的情况是损坏和错误的代码。
标签: c linux performance sockets tcp