负载测试
测试环境说明
- 云服务器:2核2G 2M Linux
- 服务端和客户端环境:同在上述服务器中运行
- 测试工具:
- wrk 模拟高并发请求
- htop检测CPU与内存使用情况
低负载压测:百万请求测试,2线程,100并发,1000s(16分钟)测试
低负载压测:2个线程,100并发量,30s测试
低负载压测:2个线程,100并发量,600s测试
中负载压测: 12线程,并发6000,3600秒测试
高负载压测:
功能性测试
项目的核心在于构建一个快速构建高性能服务器的组件,完成该组件的构建后,搭建了一个HTTP服务器。该块测试内容即是使用该组件,搭建HTTP服务器是否可以正常工作。
【测试1】客户端与服务器之间的长连接(keep-live)功能
- 测试思路
- 创建一个长连接请求,并设计一个接收数据的缓冲区
- 循环执行向服务器发送长连接的请求,每等待三秒接收影响并打印数据
- 测试客户端和服务器之间长连接是否可以正常保持和超时时间处理的情况
- 测试文件中添加try-catch捕捉异常,以及日志记录
- 截图说明(服务端+客户端)
- 测试源码
void logMessage(const std::string& message) {
std::cout << message << std::endl;
}
int main() {
try {
Socket cli_sock;
if (!cli_sock.CreateClient(8085, "127.0.0.1")) {
throw std::runtime_error("Failed to create client socket");
}
std::string req = "GET /hello HTTP/1.1\r\nConnection: keep-alive\r\nContent-Length: 0\r\n\r\n";
while (true) {
auto start_time = std::chrono::high_resolution_clock::now();
if (cli_sock.Send(req.c_str(), req.size()) == -1) {
throw std::runtime_error("Failed to send request");
}
char buf[1024] = {0};
if (!cli_sock.Recv(buf, 1023)) {
throw std::runtime_error("Failed to receive response");
}
auto end_time = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> elapsed = end_time - start_time;
logMessage("Received: " + std::string(buf));
logMessage("Response time: " + std::to_string(elapsed.count()) + " seconds");
std::this_thread::sleep_for(std::chrono::seconds(3));
}
cli_sock.Close();
} catch (const std::exception& e) {
logMessage("Error: " + std::string(e.what()));
return 1;
}
return 0;
}
【测试2】测试客户端和服务端之间的超时连接问题
- 测试思路
- 客户端会发送一次数据后进入休眠,检查服务器是否会在客户端没有后续活动的时候,关闭该超时连接
void logMessage(const std::string& message) {
std::cout << message << std::endl;
}
int main() {
try {
Socket cli_sock;
if (!cli_sock.CreateClient(8085, "127.0.0.1")) {
throw std::runtime_error("Failed to create client socket");
}
std::string req = "GET /hello HTTP/1.1\r\nConnection: keep-alive\r\nContent-Length: 0\r\n\r\n";
for (int i = 0; i < 5; ++i) { // 进行5次请求测试
auto start_time = std::chrono::high_resolution_clock::now();
if (cli_sock.Send(req.c_str(), req.size()) == -1) {
throw std::runtime_error("Failed to send request");
}
char buf[1024] = {0};
if (!cli_sock.Recv(buf, 1023)) {
throw std::runtime_error("Failed to receive response");
}
auto end_time = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> elapsed = end_time - start_time;
logMessage("Received: " + std::string(buf));
logMessage("Response time: " + std::to_string(elapsed.count()) + " seconds");
std::this_thread::sleep_for(std::chrono::seconds(15)); // 等待15秒
}
cli_sock.Close();
} catch (const std::exception& e) {
logMessage("Error: " + std::string(e.what()));
return 1;
}
return 0;
}
【测试3】测试客户端本来声明要给服务端发送1024数据,但是实际数据不足1024字节情况的处理情况
- 测试思路
- 分开发送请求头和正文:模拟实际网络情况中网络分包的问题,验证服务器的反应
void logMessage(const std::string& message) {
std::cout << message << std::endl;
}
int main() {
try {
Socket cli_sock;
if (!cli_sock.CreateClient(8085, "127.0.0.1")) {
throw std::runtime_error("Failed to create client socket");
}
// 请求头,声明内容长度为100字节
std::string req = "GET /hello HTTP/1.1\r\nConnection: keep-alive\r\nContent-Length: 100\r\n\r\n";
std::string body = "bitejiuyeke"; // 实际正文只有11字节
for (int i = 0; i < 3; ++i) { // 测试3次
// 发送请求头
if (cli_sock.Send(req.c_str(), req.size()) == -1) {
throw std::runtime_error("Failed to send request header");
}
// 发送正文
if (cli_sock.Send(body.c_str(), body.size()) == -1) {
throw std::runtime_error("Failed to send request body");
}
char buf[1024] = {0};
if (!cli_sock.Recv(buf, 1023)) {
throw std::runtime_error("Failed to receive response");
}
logMessage("Received: " + std::string(buf));
std::this_thread::sleep_for(std::chrono::seconds(3)); // 等待3秒
}
cli_sock.Close();
} catch (const std::exception& e) {
logMessage("Error: " + std::string(e.what()));
return 1;
}
return 0;
}
【测试4】测试服务器在处理业务超时的情况,重点测试服务器处理一个业务请求耗时过长的时候,导致其他连接超时并可能引起程序崩溃的问题
- 测试逻辑
- 创建多个子进程
- 每个子进程都创建一个客户端连接到服务器
- 子进程无限循环的发送请求和接收响应,然后输出响应内容
- 服务器设计说明
- 可能出现的问题:服务器处理任务时花费大量时间,可能会导致其他连接因为等待时间过长,然后其连接释放了
- 问题举例:例如多个排队的文件描述符,全部都已经到达就绪状态,在处理首个文件描述符的时候,花费了大量时间,如果后续连接是通信连接描述符则不影响,如果后面排队的描述符中有定时器文件描述符,该定时器触发的时候,就会引起连环效应,将后面的文件描述符都释放
- 解决:处理超时连接的时候,释放连接的操作不直接进行,而是将释放压入到任务池中,等到该超时事件处理完成后,再去释放超时连接
void logMessage(const std::string& message) {
std::cout << message << std::endl;
}
void signalHandler(int signum) {
logMessage("Interrupt signal (" + std::to_string(signum) + ") received.");
// Cleanup and close up stuff here
exit(signum);
}
int main() {
signal(SIGCHLD, SIG_IGN); // 忽略子进程退出信号
for (int i = 0; i < 10; i++) {
pid_t pid = fork();
if (pid < 0) {
logMessage("FORK ERROR");
return -1;
} else if (pid == 0) {
try {
Socket cli_sock;
if (!cli_sock.CreateClient(8085, "127.0.0.1")) {
throw std::runtime_error("Failed to create client socket");
}
std::string req = "GET /hello HTTP/1.1\r\nConnection: keep-alive\r\nContent-Length: 0\r\n\r\n";
while (true) {
auto start_time = std::chrono::high_resolution_clock::now();
if (cli_sock.Send(req.c_str(), req.size()) == -1) {
throw std::runtime_error("Failed to send request");
}
char buf[1024] = {0};
if (!cli_sock.Recv(buf, 1023)) {
throw std::runtime_error("Failed to receive response");
}
auto end_time = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> elapsed = end_time - start_time;
logMessage("Received: " + std::string(buf));
logMessage("Response time: " + std::to_string(elapsed.count()) + " seconds");
}
cli_sock.Close();
} catch (const std::exception& e) {
logMessage("Error: " + std::string(e.what()));
}
exit(0);
}
}
signal(SIGINT, signalHandler); // 捕捉 SIGINT 信号,进行清理
while (true) {
sleep(1); // 主进程保持运行
}
return 0;
}