目录
1、项目介绍
基于多线程和事件驱动的服务器。用于处理大量并发连接,实现高并发和高性能的网络服务的一个网络组件。使用主从Reactor实现连接的监听和读写操作的分离,并采用一个线程对应一个事件循环的方式避免了多线程下的线程安全问题。
2、测试计划
项目完成之后,我们本篇对我写的项目进行一下测试;
我们的测试计划是:
- 1. 设计测试用例
- 2. 根据测试用例进行对应的测试
接下来就一一进行实现:
3、测试工具
Postman、webbench
4、涉及到的测试动作
功能测试:覆盖了基本服务器的基本请求方式,并进行了边缘功能测试。
自动化测试:使用自动化测试来进行压力测试,测试当客户端数量很多时,服务器是否存在BUG。
5、设计测试用例
对于高并发服务器项目设计测试用例,我主要从以下几个方面进行了设计:
- 功能测试
- 界面测试
- 性能测试
- 易用性测试
- 兼容性测试
- 安全性测试
- 弱网测试
5、执行测试
5.1、基本功能测试
1、将请求信息组织成一个指定格式的字符串,给客户端设为为他的响应正文,发送给客户端
2、直接输入地址客户端就会得到页面
输入用户和密码 post没有问题
使用postman工具进行测试,这个其实就是一个http的测试工具。可以模拟给服务器发送指定方法的指定请求。
5.2、边缘功能测试
1、长连接请求测试
每隔3s向服务器发送发送一个请求,查看是否会收到响应。
预期结果:可以进行长连接通信
2、 超时连接释放测试
创建一个客户端,给服务器发送一次数据,就不动了,(或者一直不发送消息)看服务器是否会正常的超时关闭连接。
/*超时连接测试1:创建一个客户端,给服务器发送一次数据后,不动了,查看服务器是否会正常的超时关闭连接*/
#include "../source/server.hpp"
int main()
{
Socket cli_sock;
cli_sock.CreateClient(8085, "127.0.0.1");
std::string req = "GET /hello HTTP/1.1\r\nConnection: keep-alive\r\nContent-Length: 0\r\n\r\n";
while(1) {
assert(cli_sock.Send(req.c_str(), req.size()) != -1);
char buf[1024] = {0};
assert(cli_sock.Recv(buf, 1023));
DBG_LOG("[%s]", buf);
sleep(15);//超时时间设置为15s
}
cli_sock.Close();
return 0;
}
可以看到连接超时会自动断开连接
3、客户端给服务器发送一个数据,告诉服务器要发送1024字节数据,但实际发送的数据不足1024,查看服务器处理结果
1)如果数据只发送一次,服务器将得不到完整请求,就不会进行业务处理,客户端也得不到响应,最终超时关闭连接。
2)数据连着给服务器发送多次小的请求,服务器会将后面的请求当作前面请求的正文进行处理,而后面处理的时候可能会因为处理错误而关闭连接。
预期结果:服务器第一次接收请求不完整,会将后面的请求当作第一次请求的正文进行处理,最后对剩下的请求处理的时候处理出错,关闭连接
#include "../source/server.hpp"
int main()
{
Socket cli_sock;
cli_sock.CreateClient(8085, "127.0.0.1");
std::string req = "GET /hello HTTP/1.1\r\nConnection: keep-alive\r\nContent-Length: 100\r\n\r\nbitejiuyeke";
while(1) {
assert(cli_sock.Send(req.c_str(), req.size()) != -1);//先只发送一次
// assert(cli_sock.Send(req.c_str(), req.size()) != -1);
// assert(cli_sock.Send(req.c_str(), req.size()) != -1);
char buf[1024] = {0};
assert(cli_sock.Recv(buf, 1023));
DBG_LOG("[%s]", buf);
sleep(3);
}
cli_sock.Close();
return 0;
}
1)到达超时时间之后连接就会自动关闭
2)
4、业务处理超时,查看服务器的处理情况
进行压力测试,模拟10个客户端不断向服务器发送HTTP请求,测试服务器的并发处理能力和稳定性。每个子进程都会持续不断地发送相同的HTTP GET请求到服务器,并打印接收到的响应,可以用来测试服务器的性能和稳定性。
当服务器达到一个性能瓶颈,在一次业务处理中花费太长时间(超过了服务器设置的非活跃时间)
1)在一次业务处理中,耗费太长时间,导致其他连接也被连累超时,其他的连接可能会被拖累超时释放
解释:假设现在 12345描述符就绪了,在处理1的时候花费了30s处理完了,导致2345描述符因为长时间没有刷新活跃度(没有延迟超时释放)。就会出现:1、如果接下来的2345描述符都是通信连接描述符,如果都就绪了,则并不影响,因为接下来会进行处理并刷新活跃度。2、如果接下来的2号描述符是定时器时间描述符,定时器触发超时,执行定时任务,就会将345描述符给释放掉,这时候一旦345描述符对应的连接被释放,接下来在处理345事件的时候就会导致程序崩溃(内存访问错误)。因此在这时候,在本次事件处理处理中,并不能直接对连接进行释放,而应该将释放操作压入到任务池中,等到事件处理完了执行任务池中的任务的时候,再去释放。
/* 业务处理超时,查看服务器的处理情况
当服务器达到了一个性能瓶颈,在一次业务处理中花费了太长的时间(超过了服务器设置的非活跃超时时间)
1. 在一次业务处理中耗费太长时间,导致其他的连接也被连累超时,其他的连接有可能会被拖累超时释放
假设现在 12345描述符就绪了, 在处理1的时候花费了30s处理完,超时了,导致2345描述符因为长时间没有刷新活跃度
1. 如果接下来的2345描述符都是通信连接描述符,如果都就绪了,则并不影响,因为接下来就会进行处理并刷新活跃度
2. 如果接下来的2号描述符是定时器事件描述符,定时器触发超时,执行定时任务,就会将345描述符给释放掉
这时候一旦345描述符对应的连接被释放,接下来在处理345事件的时候就会导致程序崩溃(内存访问错误)
因此这时候,在本次事件处理中,并不能直接对连接进行释放,而应该将释放操作压入到任务池中,
等到事件处理完了执行任务池中的任务的时候,再去释放
*/
#include "../source/server.hpp"
int main()
{
signal(SIGCHLD, SIG_IGN);
for (int i = 0; i < 10; i++) { //多个进行 多个客户端
pid_t pid = fork();
if (pid < 0) {
DBG_LOG("FORK ERROR");
return -1;
}else if (pid == 0) {
Socket cli_sock;
cli_sock.CreateClient(8085, "127.0.0.1");
std::string req = "GET /hello HTTP/1.1\r\nConnection: keep-alive\r\nContent-Length: 0\r\n\r\n";
while(1) {
assert(cli_sock.Send(req.c_str(), req.size()) != -1);
char buf[1024] = {0};
assert(cli_sock.Recv(buf, 1023));
DBG_LOG("[%s]", buf);
}
cli_sock.Close();
exit(0);
}
}
while(1) sleep(1);
return 0;
}
5、一次性给服务器发送多条数据,然后查看服务器的处理结果,每一次请求都应该得到正常处理。
/*一次性给服务器发送多条数据,然后查看服务器的处理结果*/
/*每一条请求都应该得到正常处理*/
#include "../source/server.hpp"
int main()
{
Socket cli_sock;
cli_sock.CreateClient(8085, "127.0.0.1");
std::string req = "GET /hello HTTP/1.1\r\nConnection: keep-alive\r\nContent-Length: 0\r\n\r\n";
req += "GET /hello HTTP/1.1\r\nConnection: keep-alive\r\nContent-Length: 0\r\n\r\n";
req += "GET /hello HTTP/1.1\r\nConnection: keep-alive\r\nContent-Length: 0\r\n\r\n";
while(1) {
assert(cli_sock.Send(req.c_str(), req.size()) != -1);
char buf[1024] = {0};
assert(cli_sock.Recv(buf, 1023));
DBG_LOG("[%s]", buf);
sleep(3);
}
cli_sock.Close();
return 0;
}
6、给服务器上传一个大文件,(服务器是把数据放到先放到缓冲区里面,从缓冲区再进行处理),服务器将文件保存下来,观察处理结果。
预期结果:上传的文件和服务器保存的文件一致
把文件的数据一次性读取出来,发送给服务器,服务器对其进行接收。
/*大文件传输测试,给服务器上传一个大文件,服务器将文件保存下来,观察处理结果*/
/*
上传的文件,和服务器保存的文件一致
*/
#include "../source/http/http.hpp"
int main()
{
Socket cli_sock;
cli_sock.CreateClient(8085, "127.0.0.1");
std::string req = "PUT /1234.txt HTTP/1.1\r\nConnection: keep-alive\r\n";
std::string body;
Util::ReadFile("./hello.txt", &body);
req += "Content-Length: " + std::to_string(body.size()) + "\r\n\r\n";
assert(cli_sock.Send(req.c_str(), req.size()) != -1);
assert(cli_sock.Send(body.c_str(), body.size()) != -1);
char buf[1024] = {0};
assert(cli_sock.Recv(buf, 1023));
DBG_LOG("[%s]", buf);
sleep(3);
cli_sock.Close();
return 0;
}
5.3、性能压力测试
并发量:可以同时处理多少客户端的请求而不会出现连接失败
QPS:每秒钟处理的包的数量
借助:webbench工具
工具原理:创建大量进程,在进程中,创建客户端连接服务器,发送请求,收到响应后关闭连接,开始下一个连接的建立
测试环境:云服务器是1核2G带宽1M的云服务器,客户端是Ubuntu环境
使用webbench以一万并发量向服务器发送请求,进行了24h测试。
最终得到的结果是:并发量是1000的时候,QPS是每一千四百多的,在并发量为一万的时候服务器仍然可以正常运行。QPS是两万。