四、Linux C/C++基于epoll模型实现httpserver(以及server的吞吐能力测试)

本文详细介绍了如何通过C++实现一个简单的HTTP服务器,利用epoll进行多路复用,然后使用wrk工具对其吞吐能力进行测试。作者展示了代码实例并解释了QPS的概念和测试过程。
摘要由CSDN通过智能技术生成

httpserver

该示例可通过浏览器访问httpserver:192.168.1.100:2048(此地址为自己应用程序里绑定的地址端口),服务器返回一个静态页面的响应。实现简单的后端http服务器。

代码示例

#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <sys/poll.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

using namespace std;

#define BUFFER_LEN 1024
#define CLIENT_MAX_COUNT 2048
typedef int (*EPOLL_CALLBACK)(int fd);
struct CONNECT_ITEM {
	char rBuffer[BUFFER_LEN] = {0};
	int rLen = 0;
	char wBuffer[BUFFER_LEN] = {0};
	int wLen = 0;

	union {
		EPOLL_CALLBACK accept_callback = nullptr;
		EPOLL_CALLBACK recv_callback;
	}recv_t;
	EPOLL_CALLBACK send_callback = nullptr;
}connect_item[CLIENT_MAX_COUNT];
typedef CONNECT_ITEM connection_t;

int epfd = 0;

enum _EPOLL_CTRL{
	ADD,
	MOD
};

int http_response(connection_t& conn) {
#if 1
	conn.wLen = sprintf(conn.wBuffer, 
		"HTTP/1.1 200 OK\r\n"
		"Accept-Ranges: bytes\r\n"
		"Content-Length: 82\r\n"
		"Content-Type: text/html\r\n"
		"Date: Sat, 06 Aug 2023 13:16:46 GMT\r\n\r\n"
		"<html><head><title>0voice.king</title></head><body><h1>King</h1></body></html>\r\n\r\n");
#else
	//读取.html文件内容
	int filefd = open("./test.html", O_RDONLY);

	//获取文件内容的大小
	struct stat statBuf;
	fstat(filefd, &statBuf);

	conn.wLen = sprintf(conn.wBuffer, 
						"HTTP/1.1 200 OK\r\n"
						"Accept-Ranges: bytes\r\n"
						"Content-Length: %ld\r\n"
						"Content-Type: text/html\r\n"
						"Date: Sat, 06 Aug 2023 13:16:46 GMT\r\n\r\n", statBuf.st_size);


	int rSize = read(filefd, conn.wBuffer + conn.wLen, statBuf.st_size);
	conn.wLen += rSize;
#endif

	return conn.wLen;
}

void setEvent(int fd, EPOLL_EVENTS events, _EPOLL_CTRL ctrl) {
	epoll_event ev;
	ev.events = events; //默认水平触发(LT),有(数据)事件就会一直触发,知道全部处理完
	/*
		EPOLLET为边沿触发(ET),当有事件发生时只触发一次,
		比如来数据了,如果一次没有读完,不会再触发了,所以必须全部读完,在进行下一次epoll_wait
	*/
	//ev.events = EPOLLIN | EPOLLET;
	ev.data.fd = fd;
	epoll_ctl(epfd, ctrl == ADD ? EPOLL_CTL_ADD : EPOLL_CTL_MOD, fd, &ev);
}

int recv_cb(int fd) {
	char* buffer = connect_item[fd].rBuffer;
	int index = connect_item[fd].rLen;

	//int count = recv(fd, buffer + index, BUFFER_LEN - index, 0);
	int count = recv(fd, buffer, BUFFER_LEN, 0);
	
	if (count == 0) {
		//printf("disconnect: %d %d \n", fd, index);

		epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);		
		close(fd);
		
		return count;
	}else if (count < 0) {
		//printf("error\n");

		return count;
	}

	//connect_item[fd].rLen += count;
	connect_item[fd].rLen = count;

	//printf("RECV===>>> clientfd: %d, count: %d, buffer: %s\n", fd, count, buffer);

	//发送buffer赋值
#if 0
	memcpy(connect_item[fd].wBuffer, connect_item[fd].rBuffer, connect_item[fd].rLen);
	connect_item[fd].wLen = connect_item[fd].rLen;
#else
	http_response(connect_item[fd]);
#endif

	//改变该文件描述符的事件类型为EPOLLOUT
	setEvent(fd, EPOLLOUT, MOD);

	return count;
}

int send_cb(int fd) {
	char* buffer = connect_item[fd].wBuffer;
	int index = connect_item[fd].wLen;
	
	int count = send(fd, buffer, connect_item[fd].wLen, 0);
	//printf("SEND===>>> clientfd: %d, count: %d, buffer: %s\n", fd, count, buffer);

	//改变该文件描述符的事件类型为EPOLLIN
	setEvent(fd, EPOLLIN, MOD);

	return count;
}

int accept_cb(int fd) {
	struct sockaddr_in clientaddr;
	socklen_t len = sizeof(clientaddr);
	
	int clientfd = accept(fd, (struct sockaddr*)&clientaddr, &len);

	setEvent(clientfd, EPOLLIN, ADD);

	memset(connect_item[clientfd].rBuffer, 0, sizeof(connect_item[clientfd].rBuffer));
	connect_item[clientfd].rLen = 0;
	memset(connect_item[clientfd].wBuffer, 0, sizeof(connect_item[clientfd].wBuffer));
	connect_item[clientfd].wLen = 0;
	connect_item[clientfd].recv_t.recv_callback = recv_cb;
	connect_item[clientfd].send_callback = send_cb;
	
	//printf("ACCEPT===>>> clientfd:%d\n", clientfd);

	return clientfd;
}

int main() {
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);

    sockaddr_in serverAddr;
    memset(&serverAddr, 0, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    serverAddr.sin_port = htons(2048);
    bind(listenfd, (sockaddr*)&serverAddr, sizeof(serverAddr));

    listen(listenfd, 10);

    epfd = epoll_create(1); // int size

	connect_item[listenfd].recv_t.accept_callback = accept_cb;
	setEvent(listenfd, EPOLLIN, ADD);

	struct epoll_event events[1024] = {0};
	while (1) {
		int nready = epoll_wait(epfd, events, 1024, -1);

		for (int i = 0; i < nready; i++) {
			int connfd = events[i].data.fd;
		    if (events[i].events & EPOLLIN) {
				int count = connect_item[connfd].recv_t.recv_callback(connfd);
			}else if (events[i].events & EPOLLOUT) {
				connect_item[connfd].send_callback(connfd);
			}
		}
	}

    close(listenfd);
    return 0;
}

吞吐能力测试

通过wrk工具对该server进行吞吐能力(QPS)测试。

  • 什么是QPS?

      QPS即每秒查询率,是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。
      QPS = req/sec(请求数/秒),即每秒的响应请求数,也即是最大吞吐能力。
    

wrk工具是专门用来测试服务器的QPS。

wrk工具下载地址:git clone https://gitee.com/why168/wrk.git,下载下来后,直接在其目录下,执行make,编译完成后,执行./wrk 以及加上对应的参数就可以进行测试了。示例:
在这里插入图片描述

  • 执行命令:

    ./wrk -c 100 -d10s -t 50 http://192.168.204.128:2048

    -c 100:100个连接
    -t 50:50个线程
    -d10s:持续测量10s
    http://192.168.204.128:2048:服务器地址

  • 测试结果:

      Running 10s test @ http://192.168.204.128:2048
      	50 threads and 100 connections
      	Thread Stats   Avg     Stdev     Max       +/- Stdev
      		Latency   4.77ms   30.11ms   838.79ms  99.09%
      		Req/Sec   830.76   418.18    27.10k    95.60%
      	396632 requests in 10.10s, 77.54MB read
      Requests/sec:  39278.90
      Transfer/sec:  7.68MB
    

    Requests/sec: 39278.90:代表每秒可处理39278.90个http请求。

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值