TCP简易发包工具实现(测qps)

小捏哩 2025-12-03 09:58:38

目录

  • TCP简易发包工具实现(测qps)
  • 1. 工具功能特点
  • 2. 使用方法
  • 3. 实现细节解析
  • 3.1 核心数据结构
  • 3.2 TCP连接建立
  • 3.3 数据发送与接收
  • 3.4 多线程测试入口
  • 3.5 主控制逻辑
  • 4. 示例测试结果
  • 5. 总结

TCP简易发包工具实现(测qps)

TCP简易发包工具实现(测qps)

概述

在分布式系统、网络服务和中间件的开发与测试中,QPS(Queries Per Second,每秒查询率)是一个重要的性能指标。今天我们将实现简易的TCP QPS测试工具,该工具可以模拟多线程并发连接,测量TCP服务器的处理能力。

1. 工具功能特点

  1. 多线程支持:可配置线程数量,模拟并发访问
  2. 灵活配置:支持自定义服务器地址、端口、连接数、请求数等参数
  3. 数据验证:发送和接收的数据进行比对,确保传输正确性
  4. 性能统计:自动计算总耗时、成功/失败次数和QPS值

2. 使用方法

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:总请求数

3. 实现细节解析

3.1 核心数据结构

typedef struct test_context_s {
    char serverip[16];
    short port;
    int threadnum;  // 用于指定线程数量
    int connection; // 用于表示连接数
    int requestion; // 用于表示请求数

#if 1
    int failed; // 失败的次数
#endif
} test_context_t;

这个结构体封装了所有的测试配置和统计信息,便于在多线程间共享数据。

3.2 TCP连接建立

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连接,每个线程创建一个独立的连接。

3.3 数据发送与接收

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字节小数据包测试模式。

3.4 多线程测试入口

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;
}

每个线程独立处理一部分请求,确保总请求数均匀分布到各个线程。

3.5 主控制逻辑

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;
}

4. 示例测试结果

运行工具后,编译指定ip与端口号以及我们想实现的连接数,

./tcp_qps_tcpclient -s 10.66.189.100 -p 9999 -t 50 -c 100 -n 1000

会输出如下格式的结果:

请添加图片描述

5. 总结

这个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;
}


https://github.com/0voice

...全文
44 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

523

社区成员

发帖
与我相关
我的任务
社区描述
零声学院,目前拥有上千名C/C++开发者,我们致力将我们的学员组织起来,打造一个开发者学习交流技术的社区圈子。
nginx中间件后端 企业社区
社区管理员
  • Linux技术狂
  • Yttsam
  • 零声教育-晚晚
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

请新加入的VIP学员,先将自己参加活动的【所有文章】,同步至社区:

【内容管理】-【同步至社区-【零声开发者社区】

试试用AI创作助手写篇文章吧