523
社区成员
发帖
与我相关
我的任务
分享概述
在分布式系统、网络服务和中间件的开发与测试中,QPS(Queries Per Second,每秒查询率)是一个重要的性能指标。今天我们将实现简易的TCP QPS测试工具,该工具可以模拟多线程并发连接,测量TCP服务器的处理能力。
1.编译命令:
gcc -o tcp_qps_tcpclient tcp_qps_tcpclient.c -lpthread
2.运行命令:
./tcp_qps_tcpclient -s 127.0.0.1 -p 2048 -t 50 -c 100 -n 1000000
3.参数说明:
-s:服务器IP地址-p:服务器端口-t:线程数-c:连接数(当前版本中实际使用的是线程数,此参数未直接使用)-n:总请求数typedef struct test_context_s {
char serverip[16];
short port;
int threadnum; // 用于指定线程数量
int connection; // 用于表示连接数
int requestion; // 用于表示请求数
#if 1
int failed; // 失败的次数
#endif
} test_context_t;
这个结构体封装了所有的测试配置和统计信息,便于在多线程间共享数据。
int connect_tcpserver(char *ip, unsigned short port) {
int connfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(struct sockaddr_in));
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_addr(ip);
serveraddr.sin_port = htons(port);
int ret = connect(connfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in));
if(ret) {
perror("connnet failed");
return -1;
}
return connfd;
}
使用标准的BSD Socket API建立TCP连接,每个线程创建一个独立的连接。
int send_recv_tcppkt(int connfd) {
#if 0
int ret = send(connfd, TEST_MESSAGE, sizeof(TEST_MESSAGE), 0);
if(ret < 0) {
perror("send failed");
exit(1);
}
char rbuffer[RBUFFER_LENGTH] = {0};
ret = recv(connfd, rbuffer, RBUFFER_LENGTH, 0);
if(ret < 0) {
perror("recv failed");
exit(1);
}
if(strcmp(TEST_MESSAGE, rbuffer) != 0){
printf("'%s' != '%s'", TEST_MESSAGE, rbuffer);
exit(1);
}
#else // 分别测试 128 256 512
char wbuffer[WBUFFER_LENGTH] = {0};
for(int i = 0;i < 16; i++){ // 2:128 4:256 8:512
strcpy(wbuffer + i * strlen(TEST_MESSAGE), TEST_MESSAGE);
}
int ret = send(connfd, wbuffer, strlen(wbuffer), 0);
if(ret < 0) {
perror("send failed");
exit(1);
}
char rbuffer[RBUFFER_LENGTH] = {0};
ret = recv(connfd, rbuffer, RBUFFER_LENGTH, 0);
if(ret < 0) {
perror("recv failed");
exit(1);
}
if(strcmp(wbuffer, rbuffer) != 0){
printf("'%s' != '%s'", wbuffer, rbuffer);
exit(1);
}
#endif
return 0;
}
当前可以实现使用128、256、512 字节的大数据包进行测试,可以通过#if 0可以切换为64字节小数据包测试模式。
static void *test_qps_entry(void *arg) {
test_context_t *pctx = arg;
// 建立连接
int fd = connect_tcpserver(pctx->serverip, pctx->port);
// 计算每个线程需要处理的请求数
int count = pctx->requestion / pctx->threadnum;
// 循环发送请求
for(int i = 0; i < count; i++) {
int ret = send_recv_tcppkt(fd);
if(ret != 0) {
pctx->failed++;
}
}
return NULL;
}
每个线程独立处理一部分请求,确保总请求数均匀分布到各个线程。
int main(int argc, char *argv[]) {
// 参数解析
test_context_t ctx = {0};
while((opt = getopt(argc, argv, "s:p:t:c:n:?")) != -1) {
// ... 参数处理
}
// 创建线程
pthread_t *ptid = malloc(ctx.threadnum * sizeof(pthread_t));
// 记录开始时间
struct timeval tv_begin, tv_end;
gettimeofday(&tv_begin, NULL);
// 启动所有线程
for(int i = 0; i < ctx.threadnum; i++) {
pthread_create(&ptid[i], NULL, test_qps_entry, &ctx);
}
// 等待所有线程完成
for(int i = 0; i < ctx.threadnum; i++) {
pthread_join(ptid[i], NULL);
}
// 记录结束时间并计算统计信息
gettimeofday(&tv_end, NULL);
int time_used = TIME_SUB_MS(tv_end, tv_begin);
// 输出结果
printf("access: %d, failed: %d, time: %dms, qps: %d\n",
ctx.requestion - ctx.failed, ctx.failed,
time_used, ctx.requestion * 1000 / time_used);
return 0;
}
运行工具后,编译指定ip与端口号以及我们想实现的连接数,
./tcp_qps_tcpclient -s 10.66.189.100 -p 9999 -t 50 -c 100 -n 1000
会输出如下格式的结果:

这个TCP QPS测试工具虽然简单,但涵盖了网络性能测试的基本要素:并发连接、请求模拟、数据验证和性能统计。通过这个工具,开发者可以快速评估TCP服务器的处理能力,发现性能瓶颈。
在实际应用中,可以根据具体需求对工具进行扩展,比如增加HTTP协议支持、添加更详细的统计信息、支持分布式测试等。希望这个实现能为需要网络性能测试的开发者提供一个有用的起点。
完整代码:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <pthread.h>
#define TEST_MESSAGE "ABCDEFGHIJKLMNOPQRSNUVWXYZ123456789abcdefghijklmnopqrstuvwxyz\r\n"
#define RBUFFER_LENGTH 2048
#define WBUFFER_LENGTH 2048
#define TIME_SUB_MS(tv1, tv2) ((tv1.tv_sec - tv2.tv_sec) * 1000 + (tv1.tv_usec - tv2.tv_usec) / 1000)
typedef struct test_context_s {
char serverip[16];
short port;
int threadnum; // 用于指定线程数量
int connection; // 用于表示连接数
int requestion; // 用于表示请求数
#if 1
int failed; // 失败的次数
#endif
} test_context_t;
int connect_tcpserver(char *ip, unsigned short port) {
int connfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(struct sockaddr_in));
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_addr(ip);
serveraddr.sin_port = htons(port);
int ret = connect(connfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in));
if(ret) {
perror("connnet failed");
return -1;
}
return connfd;
}
int send_recv_tcppkt(int connfd) {
#if 0
int ret = send(connfd, TEST_MESSAGE, sizeof(TEST_MESSAGE), 0);
if(ret < 0) {
perror("send failed");
exit(1);
}
char rbuffer[RBUFFER_LENGTH] = {0};
ret = recv(connfd, rbuffer, RBUFFER_LENGTH, 0);
if(ret < 0) {
perror("recv failed");
exit(1);
}
if(strcmp(TEST_MESSAGE, rbuffer) != 0){
printf("'%s' != '%s'", TEST_MESSAGE, rbuffer);
exit(1);
}
#else // 分别测试 128 256 512
char wbuffer[WBUFFER_LENGTH] = {0};
for(int i = 0;i < 16; i++){ // 2:128 4:256 8:512
strcpy(wbuffer + i * strlen(TEST_MESSAGE), TEST_MESSAGE);
}
int ret = send(connfd, wbuffer, strlen(wbuffer), 0);
if(ret < 0) {
perror("send failed");
exit(1);
}
char rbuffer[RBUFFER_LENGTH] = {0};
ret = recv(connfd, rbuffer, RBUFFER_LENGTH, 0);
if(ret < 0) {
perror("recv failed");
exit(1);
}
if(strcmp(wbuffer, rbuffer) != 0){
printf("'%s' != '%s'", wbuffer, rbuffer);
exit(1);
}
#endif
return 0;
}
static void *test_qps_entry(void *arg) {
test_context_t *pctx = arg;
int fd = connect_tcpserver(pctx->serverip, pctx->port); // 对应参数c 连接的数量
if(fd < 0){
printf("connect_tcpserver failed\n");
return NULL;
}
int count = pctx->requestion / pctx->threadnum; // 每个线程处理的请求数
for(int i = 0; i < count; i++){
int ret = send_recv_tcppkt(fd); // 对应参数n,发送的次数
if(ret != 0){
printf("send_recv_tcppkt failed\n");
pctx->failed++;
}
}
return NULL;
}
// ./tcp_qps_tcpclient -s 127.0.0.1 -p 2048 -t 50 -c 100 -n 1000000 127.0.0.1 没有mtu
int main(int argc, char *argv[]) {
test_context_t ctx = {0};
int opt = 0;
while((opt = getopt(argc, argv, "s:p:t:c:n:?")) != -1){
switch(opt){
case 's':
printf("s: %s \n", optarg);
strcpy(ctx.serverip, optarg);
break;
case 'p':
printf("p: %s \n", optarg);
ctx.port = atoi(optarg);
break;
case 't':
printf("t: %s \n", optarg);
ctx.threadnum = atoi(optarg);
break;
case 'c':
printf("c: %s \n", optarg);
ctx.connection = atoi(optarg);
break;
case 'n':
printf("n: %s \n", optarg);
ctx.requestion = atoi(optarg);
break;
default:
return -1;
}
}
pthread_t *ptid = malloc(ctx.threadnum * sizeof(pthread_t));
struct timeval tv_begin, tv_end;
gettimeofday(&tv_begin, NULL);
for(int i = 0; i < ctx.threadnum; i++){
pthread_create(&ptid[i], NULL, test_qps_entry, &ctx);
}
for(int i = 0; i < ctx.threadnum; i++){
pthread_join(ptid[i], NULL);
}
gettimeofday(&tv_end, NULL);
int time_used = TIME_SUB_MS(tv_end, tv_begin);
printf("access: %d, failed: %d, time: %d, qps: %d\n", ctx.requestion - ctx.failed, ctx.failed,
time_used, ctx.requestion * 10000 / time_used);
return 0;
}