前边我们了解了Reactor模式和Proactor模式,哪个性能更好呢?需要我们进行测试。前边我们用io_uring实现了Proactor模式,io_uring是2019年才加入到Linux内核中的,提供了三个系统调用函数。都有些抽象,我是直接来拿跑的。
无论是我们epoll实现的Reactor模式还是io_uring实现的Proactor模式,两者都是服务器端。QPS:Queries Per Second意思是“每秒查询率”,是一台服务器每秒能够相应的查询次数,是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。互联网中,作为域名系统服务器的机器的性能经常用每秒查询率来衡量。也就是说,qps这个参数是我们服务器端的一个重要性能。计算 qps的方法是通过统计在一定时间内处理的请求数,然后除以该时间间隔,得到平均每秒的请求数。
我们可以写一个客户端的代码,分别对epoll实现的Reactor服务器和io_uring实现的Proactor服务器进行测试。客户端大致流程:对服务端发起请求到接收到服务器回发的数据,统计期间的时间。
用getopt进行解析命令行参数
-t:表示线程数量
-c:表示连接数量
-n:表示请求数量
tcp客户端测试代码如下:
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <pthread.h>
#include <arpa/inet.h>
//定义函数计算两个时间结点的差(单位:毫秒)
#define TIME_SUB_MS(tv1, tv2) ((tv1.tv_sec - tv2.tv_sec) * 1000 + (tv1.tv_usec - tv2.tv_usec) / 1000)
//结构体test_context_s定义线程入口函数(test_qps_entry)需要的变量
typedef struct test_context_s {
char serverip[16];
int port;
int threadnum;
int connection;
int requestion;
#if 1
int failed;
#endif
} test_context_t;
typedef struct test_context_s test_context_t;
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));
if (ret) {
perror("connect");
return -1;
}
return connfd;
}
//定义测试发送数据 长度为64个字符
#define TEST_MESSAGE "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyz\r\n"
#define RBUFFER_LENGTH 2048
#define WBUFFER_LENGTH 2048
int send_recv_tcppkt(int fd) {
char wbuffer[WBUFFER_LENGTH] = {0};
int i = 0;
//循环将字符串 TEST_MESSAGE 复制到 wbuffer 数组中
//每次复制之前需要计算目标位置的偏移量(i * strlen(TEST_MESSAGE))
for (i = 0;i < 8;i ++) {
strcpy(wbuffer + i * strlen(TEST_MESSAGE), TEST_MESSAGE);
}
int res = send(fd, wbuffer, strlen(wbuffer), 0);
if (res < 0) {
exit(1);
}
char rbuffer[RBUFFER_LENGTH] = {0};
res = recv(fd, rbuffer, RBUFFER_LENGTH, 0);
if (res <= 0) {
exit(1);
}
if (strcmp(rbuffer, wbuffer) != 0) {
printf("failed: '%s' != '%s'\n", rbuffer, wbuffer);
return -1;
}
return 0;
}
//线程入口函数
static void *test_qps_entry(void *arg) {
test_context_t *pctx = (test_context_t*)arg;
int connfd = connect_tcpserver(pctx->serverip, pctx->port);
if (connfd < 0) {
printf("connect_tcpserver failed\n");
return NULL;
}
int count = pctx->requestion / pctx->threadnum;
int i = 0;
int res;
while (i++ < count) {
res = send_recv_tcppkt(connfd);
if (res != 0) {
printf("send_recv_tcppkt failed\n");
pctx->failed ++; //
continue;
}
}
return NULL;
}
int main(int argc, char *argv[]) {
int ret = 0;
test_context_t ctx = {0};
int opt;
//识别信息 ./test_qps_tcpclient -s 192.168.147.129(看自己的主机地址,或者用回环地址127.0.0.1也行) -p 2048 -t 50 -c 100 -n 1000000
//地址:192.168.147.129
//连接的端口:2048
//创建的线程数:50
//连接数:100
//请求数:1000000
//赋值给对应的变量
while ((opt = getopt(argc, argv, "s:p:t:c:n:?")) != -1) {
switch (opt) {
//serverip:服务器ip地址
case 's':
printf("-s: %s\n", optarg);
strcpy(ctx.serverip, optarg);
break;
//port:服务器的端口
case 'p':
printf("-p: %s\n", optarg);
//atpi函数:将字符串转换成整数
ctx.port = atoi(optarg);
break;
//threadnum:线程数
case 't':
printf("-t: %s\n", optarg);
ctx.threadnum = atoi(optarg);
break;
//connection:连接数量
case 'c':
printf("-c: %s\n", optarg);
ctx.connection = atoi(optarg);
break;
//requestion:请求数量
case 'n':
printf("-n: %s\n", optarg);
ctx.requestion = atoi(optarg);
break;
default:
return -1;
}
}
//为threadnum个线程的内存
pthread_t *ptid = malloc(ctx.threadnum * sizeof(pthread_t));
int i = 0;
struct timeval tv_begin;
gettimeofday(&tv_begin, NULL);
//创建我们设定的线程数量,数量为threadnum
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);
}
struct timeval tv_end;
gettimeofday(&tv_end, NULL);
int time_used = TIME_SUB_MS(tv_end, tv_begin);
//time_used单位是毫秒,要想计算qps,需要换算成秒,需要将请求数量*1000
printf("success: %d, failed: %d, time_used: %d, qps: %d\n", ctx.requestion-ctx.failed,
ctx.failed, time_used, ctx.requestion * 1000 / time_used);
free(ptid);
return ret;
}
1.测试epoll实现的服务器的qps:
服务器端代码(在前边文章C语言实现Reactor中有)进行编译运行等待客户端连接(关闭服务器端所有日志信息)
上述客户端代码编译运行
2.测试io_uring实现的服务器的qps:
服务器端代码(在前边文章io_uring实现Proactor中有)进行编译运行等待客户端连接(关闭服务器端所有日志信息)
上述客户端代码编译运行(注意修改自己服务器端代码中端口)
feXw-1724248941756)]
2.测试io_uring实现的服务器的qps:
服务器端代码(在前边文章io_uring实现Proactor中有)进行编译运行等待客户端连接(关闭服务器端所有日志信息)
[外链图片转存中…(img-5Ao2AjHx-1724248941756)]
上述客户端代码编译运行(注意修改自己服务器端代码中端口)
[外链图片转存中…(img-XmFjRxwA-1724248941757)]
通过上述测试,可以大致看出io_uring实现的异步Proactor性能的qps比epoll实现的同步Reactor性能要好。