使用 send / recv 做 IO 性能测试(TCP)
send/recv 是 Linux socket 原生接口,适合做用户态TCP IO吞吐、延迟测试 ,区别于
epoll/io_uring,这里给最简单的阻塞版本,再给性能测试思路。 原理:服务端 recv,客户端 send,传输固定大小数据,统计耗时、带宽。
一、完整C代码(阻塞TCP,send/recv 测试)
server.c(接收端,recv)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <time.h>
#define PORT 8888
#define BUF_SIZE (1<<20) // 1MB buffer
int main() {
int server_fd, new_socket;
struct sockaddr_in addr;
socklen_t addrlen = sizeof(addr);
char *buf = malloc(BUF_SIZE);
server_fd = socket(AF_INET, SOCK_STREAM, 0);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(PORT);
bind(server_fd, (struct sockaddr*)&addr, sizeof(addr));
listen(server_fd, 1);
printf("server listen on %d\n", PORT);
new_socket = accept(server_fd, (struct sockaddr*)&addr, &addrlen);
printf("client connected\n");
long long total_recv = 0;
clock_t start = clock();
while (1) {
int n = recv(new_socket, buf, BUF_SIZE, 0);
if (n <= 0) break;
total_recv += n;
}
clock_t end = clock();
double sec = (double)(end - start)/CLOCKS_PER_SEC;
printf("recv total: %lld bytes, time: %.2fs, bw: %.2f MB/s\n",
total_recv, sec, total_recv / sec / (1<<20));
close(new_socket);
close(server_fd);
free(buf);
return 0;
}
client.c(发送端,send)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#define PORT 8888
#define BUF_SIZE (1<<20)
#define SEND_TOTAL (10LL << 30) // 10GB 测试流量
int main() {
int sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serv_addr;
char *buf = malloc(BUF_SIZE);
memset(buf, 'A', BUF_SIZE);
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr);
connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
printf("connected to server\n");
long long remain = SEND_TOTAL;
clock_t start = clock();
while (remain > 0) {
int send_len = (remain > BUF_SIZE) ? BUF_SIZE : remain;
int n = send(sock, buf, send_len, 0);
if (n <= 0) {
perror("send");
break;
}
remain -= n;
}
clock_t end = clock();
double sec = (double)(end - start)/CLOCKS_PER_SEC;
printf("send total: %lld bytes, time: %.2fs, bw: %.2f MB/s\n",
SEND_TOTAL, sec, SEND_TOTAL / sec / (1<<20));
close(sock);
free(buf);
return 0;
}
二、编译 & 运行
gcc server.c -o server -O2
gcc client.c -o client -O2
窗口1(服务端)
./server
窗口2(客户端)
./client
默认本地回环测试 10GB 流量,测 TCP 吞吐。 改
127.0.0.1换成远端IP,测跨机器网络IO。
三、send/recv 关键坑(做IO测试必须注意)
-
send 不代表数据已经送到对端 send 只是拷贝到内核 socket send buffer,成功返回仅代表拷贝完成;真正收完是对端
recv。所以吞吐统计一定要以 server recv 累计字节为准,client send 的计数会偏乐观。
-
send/recv 返回值永远要检查 TCP是字节流,不保证一次send多少就一次recv多少 。
send()返回可能小于传入长度(部分发送),上面代码已经处理了剩余数据。 -
buffer 大小影响测试结果 调整
BUF_SIZE:4k/64k/1M/4M,用来测试不同应用层缓冲区对IO的影响。 -
阻塞 vs 非阻塞 上面是阻塞socket。 非阻塞:
fcntl(sock, F_SETFL, O_NONBLOCK),此时 send/recv 可能返回-1,errno=EAGAIN,需要循环重试。适合高并发IO测试。 -
socket buffer 调优(提升上限)
临时修改内核tcp缓冲区
sysctl -w net.core.rmem_max=16777216
sysctl -w net.core.wmem_max=16777216
代码里也可以用 setsockopt 设置 SO_RCVBUF / SO_SNDBUF。