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与端口号测试工具,测试功能: