TCP简易发包工具

TCP简易发包工具

简介:按照命令行参数,向指定服务器发送一系列数据包,根据数据收发的耗时计算出服务器的qps。

架构及设计

​ 在主函数入口输入参数,按照参数创建连接向服务器发送数据,根据总耗时计算qps。按照需求指定命令行参数:s:p:t:c:n,分别代表服务器ip,服务器端口、创建线程、创建的连接数、发送的次数。

​ 将这些信息抽象为结构体:

typedef struct test_context_s{

    char serverip[16];
    int port;
    int threadnum;
    int connection;
    int requestion;
#if 1
     //用于标识失败次数
    int failed;
#endif
}test_context_t;

​ 总共设计三个函数,线程回调函数,当线程创建成功在回调函数中建立连接向服务器发送数据;建立连接,按照函数入口参数建立连接获取到连接文件描述符;数据包函数,向服务器发送指定数据并接收,同时判断收发数据是否一致。

函数设计与实现

  • 主函数

​ 通过函数getopt()获取到参数:

	opt = getopt(argc, argv, "s:p:t:c:n:?")
    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));
    int i = 0;

    //创建线程
    for(i = 0;i < ctx.threadnum;i++){
        pthread_create(&ptid[i],NULL,test_qps_entry,&ctx);
    }
	//等待每个线程运行完毕
	for(i = 0;i < ctx.threadnum;i++){
        pthread_join(ptid[i],NULL);
    }
  • 线程回调函数

​ 创建线程进入回调函数,该函数作为该工具的核心函数,为每个线程分别建立连接并发送数据。

void *test_qps_entry(void *arg) {
    test_context_t *pctx = (test_context_t *)arg;
    int i, j;
    int connfd;
    int request_per_conn;
    int total_requests = 0;
    
    // 计算每个连接的请求数
    request_per_conn = pctx->requestion / (pctx->threadnum * pctx->connection);
    if (pctx->requestion % (pctx->threadnum * pctx->connection) != 0) {
        request_per_conn++;
    }
    
    // 为每个线程创建多个连接
    for (i = 0; i < pctx->connection; i++) {
        connfd = connect_tcpserver(pctx->serverip, pctx->port);
        if (connfd < 0) {
            printf("connect_tcpserver erro\n");
            pctx->failed++;
            continue;
        }
        
        // 发送请求
        for (j = 0; j < request_per_conn && total_requests < pctx->requestion; j++) {
            if (send_recv_tcppkt(connfd) != 0) {
                pctx->failed++;
            }
            total_requests++;
        }
        
        close(connfd);
    }
    
    return NULL;
}
  • 建立连接函数

​ 固定流程创建套接字,按照ip地址端口号创建连接:

int connect_tcpserver(const char *ip, unsigned short port) {
    int connfd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in tcpserver_addr;
    memset(&tcpserver_addr, 0, sizeof(struct sockaddr_in));

    tcpserver_addr.sin_family = AF_INET;
    tcpserver_addr.sin_addr.s_addr = inet_addr(ip);
    tcpserver_addr.sin_port = htons(port);

    int ret = connect(connfd, (struct sockaddr *)&tcpserver_addr, sizeof(struct sockaddr_in));
    return connfd;
}
  • 数据包函数

​ 用于发送与接收数据,同时判断收发数据是否匹配。需要注意的是,因为TCP的数据包长度限制,在接收数据时应该循环接收,一直到EOF截止。

int send_recv_tcppkt(int fd) {
    char wbuffer[WBUFFER_LENGTH] = {0};
    int i = 0;

    int total_sent = 0;
    int message_len = strlen(wbuffer);
    while (total_sent < message_len) {
        int res = send(fd, wbuffer + total_sent, message_len - total_sent, 0);
        if (res < 0) {
            perror("send");
            return -1;
        }
        total_sent += res;
    }

    char rbuffer[RBUFFER_LENGTH] = {0};
    int total_received = 0;
    while (total_received < message_len) {
        int res = recv(fd, rbuffer + total_received, RBUFFER_LENGTH - total_received, 0);
        if (res <= 0) {
            perror("recv");
            return -1;
        }
        total_received += res;
    }

    if (strncmp(rbuffer, wbuffer, message_len) != 0) {
        printf("failed: response does not match sent data\n");
        return -1;
    }

    return 0;
}

编译与验证

​ 至此工具编写完成,编译指定ip与端口号测试工具,测试功能:

posted @ 2025-06-19 19:51  +_+0526  阅读(62)  评论(0)    收藏  举报