前言
在前几篇文章中,主要对项目的框架和代码的实现角度介绍了此项目,但是骡子是马还得牵出来遛一遛,本篇文章主要是介绍一下如何遛你的码,即对代码进行功能性的测试,看代码是否能够稳定运行,话不多说,马上开始。
正文
1. 编译
首先得保证你写的代码从语法和逻辑上大致都没有问题才能进行跑。当进行编译不通过时,根据编译器提示的错误慢慢梳理即可;当运行时程序崩溃时,可根据打印日志信息,对根据蛛丝马迹找到线索,解开bug。如下是日志宏的介绍,方便运行时进行查看出错的可能位置,进而追踪和定位Bug,然后才是分析和解决Bug。
顺便分享一下之前梳理的系统日志的介绍:
以及关于代码的处理流程:
相关接口:
#include <time.h>
time_t time(time_t *tloc);
/*
参数:输出型参数,获取时间戳。
返回值:获取时间戳
*/
struct tm *localtime(const time_t *timep);
struct tm {
int tm_sec; /* Seconds (0-60) */
int tm_min; /* Minutes (0-59) */
int tm_hour; /* Hours (0-23) */
int tm_mday; /* Day of the month (1-31) */
int tm_mon; /* Month (0-11) */
int tm_year; /* Year - 1900 */
int tm_wday; /* Day of the week (0-6, Sunday = 0) */
int tm_yday; /* Day in the year (0-365, 1 Jan = 0) */
int tm_isdst; /* Daylight saving time */
};
/*
参数:输出型参数,获取时间戳。
返回值:转成存放时间类型的结构体对象。
*/
size_t strftime(char *s, size_t max, const char *format,const struct tm *tm);
/*
参数
1.输出型参数,存放struct tm转为将字符串的结果。
2.输出结果的最大长度。
3.输入型参数,时间类型的结构体对象。
返回值:
1.成功,转成字符串的实际长度。
2.失败返回0,注意返回0不一定意味着失败。
*/
#include <stdio.h>
int fprintf(FILE *stream, const char *format, ...);
/*
参数
1.文件流指针,比如stdout,stdin,stderr
2.格式化处理的字符串。
3.可变参数列表,在内部可变参数作为参数传递时需要用到宏__VA_ARGS__,且前面需加上##说明其为参数名的一部分。
返回值:
1.成功,转成字符串的实际长度。
2.失败返回0,注意返回0不一定意味着失败。
*/
实现:
//时间函数
#include<ctime>
//输入输出
#include<cstdio>
//字符串
#include<cstring>
//错误码
#include<errno.h>
//设日志等级为信息,调试, 警告, 错误, 值越大等级越高。
enum level
{
INFOR,
DEBUG,
WARING,
ERROR
};
/*
思路
1.获取当前时间戳。
2.将时间戳转化为struct tm类型结构。
3.将struct tm类型结构转换为标准形式的字符串存储起来。
4.打印信息,包含时间,所在文件名,文件的行数,日志等级和要所查看的可变参数。
*/
#define LOG(lev,format,...) do{\
time_t t = time(0);\
struct tm* ltm= localtime(&t);\
char t_str[32]={0};\
strftime(t_str,sizeof(t_str) - 1,"%H:%M:%S",ltm);\
fprintf(stdout,"[%s,%s,%d,%d]:"#format"\n",t_str,__FILE__,__LINE__,lev,##__VA_ARGS__);\
}while(0)
#define INF_LOG(format,...) LOG(INFOR,format,##__VA_ARGS__);
#define DEBUG_LOG(format,...) LOG(DEBUG,format,##__VA_ARGS__);
#define ERROR_LOG(format,...) LOG(ERROR,format,##__VA_ARGS__);
#define WARING_LOG(format,...) LOG(WARING,format,##__VA_ARGS__);
2. 运行
1. 服务器
//设置为你服务器的资源的绝对根目录即可,使用pwd命令cv。
std::string sdir = "/home/ubuntu/MudoProject/practical-projects/imitate_mudo_concurrent_server/source/Http/wwwroot/";
void Print(const HttpRequest& req,HttpResponse* rsp)
{
//组装Http协议信息
//版本号 状态码 状态表示\r\n
std::stringstream stm;
stm << "------------------------------------------------------------------" << "\r\n";
stm << req._version << " " << req._method << " " << req._source_dir << "\r\n";
//头部信息
for(auto& kv : req._headers)
{
stm << kv.first << ": " << kv.second << "\r\n";
}
//参数
for(auto& kv : req._parameter)
{
stm << kv.first << ": " << kv.second << "\r\n";
}
stm << "\r\n";
//头部信息
stm << req._body;
stm << "\r\n";
stm << "+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++" << "\r\n";
//设置响应正文
rsp->SetContent(stm.str(),"text/plain");
}
//Get,,Post,Delete,此动态处理本项目没有相应的业务需求因此将数据打包返回。
//Put,可以进行简单的测试,即上传大文件。
void Get(const HttpRequest& req,HttpResponse* rsp)
{
//将数据按原样为正文进行返回。
Print(req,rsp);
}
void Put(const HttpRequest& req,HttpResponse* rsp)
{
Util::WriteFile(sdir + "bf.txt",req._body);
}
void Post(const HttpRequest& req,HttpResponse* rsp)
{
Print(req,rsp);
}
void Delete(const HttpRequest& req,HttpResponse* rsp)
{
Print(req,rsp);
}
int main()
{
//创建服务器,使用默认端口号8000
HttpServer svr;
//设置功能性请求。
svr.SetHander("GET","/get",Get);
svr.SetHander("POST","/post",Post);
svr.SetHander("PUT","/put",Put);
svr.SetHander("DELETE","/delete",Delete);
//启动服务器,内部默认是4个线程
svr.Start();
return 0;
}
当进行简单的功能测试时,可以下载PostMan工具,模拟Http协议报文进行发送,验证对应的功能和服务器的运行状况。
2. 客户端
在实战项目的开篇文章当中,通过浏览器测试获取主页和返回错误界面,本质是一种前端网页的编写,博主找的是现成的,因为博主是C++后端方向的,只是懂怎么用,怎么编写等学有余力的时候,再进行学习相应的前端知识并分享给大家,在这是算是给自己挖一个坑,如下主要是通过客户端和相应的测试工具进行测试的。
传输大文件
- 生成一个大文件:
dd if=/dev/zero of=bf.txt bs=1M count=100
//创建一个100MB的大文件。
dd
/*
dd命令
参数:
if,inputfile,即设置输入的文件,通常为/dev/zero设备中的垃圾输入流,可以提取无尽的0。
of,outputfile,设置输出文件的文件名。
bs,blocksize,块大小。
count,块的数量。
bs * count,即为创建文件的大小。
*/
说明:我的云服务器是一核两G的,只有2G内存,除去系统占用的之外,极限最大传输的文件可能也就1G左右,条件有限,有条件的同学可以使用虚拟机分配4/6G的内存,进行测试。
实现:
#include"/home/ubuntu/MudoProject/practical-projects/imitate_mudo_concurrent_server/include/mudo.h"
#include"/home/ubuntu/MudoProject/practical-projects/imitate_mudo_concurrent_server/include/http.h"
//进行长连接分批发送数据处理的测试。
int main()
{
CSocket skt("49.232.138.58");
skt.Connect();
//非活跃,且为长连接持续时长为30秒
std::string reqline = "PUT /put HTTP/1.1\r\n";
std::string headers = "Connection: keep-alive\r\n";
std::string body;
Util::ReadFile("./bf.txt",&body);
headers += "Content-Length: " + std::to_string(body.size()) + "\r\n\r\n";
std::string data = reqline + headers + body;
skt.Send(data);
while(1) sleep(1);
skt.Close();
return 0;
}
传输如果有误,则根据日志信息慢慢测就完了,没问题,则进行数据验证。
- 命令
md5sum [文件名]
看服务器传输成功的文件数据的MD5值是否与客户端的文件的Md5相同,如果相同则验证无误。
长连接
- 非活跃连接
#include"/home/ubuntu/MudoProject/practical-projects/imitate_mudo_concurrent_server/include/mudo.h"
//进行长连接以及非活跃连接的测试。
int main()
{
CSocket skt("49.232.138.58");//这是我云服务器的公网ip,查看你服务器的ip设置即可。
skt.Connect();
//非活跃,且为长连接持续时长为30秒,30s是此项目中设置的,自己也可设置。
std::string data = "GET /get HTTP/1.1\r\nConnection: keep-alive\r\n\r\n";
skt.Send(data);
char buffer[65536] = {0};
ssize_t ret = skt.Recv(buffer,sizeof(buffer) - 1);
while(1) sleep(1);
return 0;
}
说明:设置并查看服务器的日志信息,先运行服务器,运行起来之后,如果客户端也运行,然后发送一次连接30s后,服务器日志连接出现销毁信息,说明测试成功。
- 分批发送数据
#include"/home/ubuntu/MudoProject/practical-projects/imitate_mudo_concurrent_server/include/mudo.h"
//进行长连接分批发送数据处理的测试。
int main()
{
CSocket skt("49.232.138.58");
skt.Connect();
//非活跃,且为长连接持续时长为30秒
std::string data = "GET /get HTTP/1.1\r\nConnection: keep-alive\r\nContent-Length: 100\r\n\r\n";
//先只发送请求,后面是只发送数据
skt.Send(data);
//发送数据10次,每次发送数据的长度为10,最终于Content-Length: 100相照应。
int cnt = 10;
while(cnt--)
{
skt.Send("5205205205");
}
char buffer[65536] = {0};
//进行阻塞接收
ssize_t ret = skt.Recv(buffer,sizeof(buffer) - 1);
if(ret == -1)
{
INF_LOG("reason:%s",strerror(errno));
}
else
{
printf("%s",buffer);
}
skt.Close();
return 0;
}
如果正常的收到消息,则说明测试无误,如果没有则看看日志信息,并查看连接测试部分的代码哪出问题了,Debug要耐心!
短连接
#include"/home/ubuntu/MudoProject/practical-projects/imitate_mudo_concurrent_server/include/mudo.h"
//进行短连接的测试。
int main()
{
INF_LOG("Short Connetion Test.");
CSocket skt("49.232.138.58");
skt.Connect();
//短连接直接关闭
for(int i = 0; i < 3; i++)
{
std::string data = "GET /get HTTP/1.1\r\n\r\n";
ssize_t ret = skt.Send(data);
if(ret == -1 || ret == 0)
{
INF_LOG("reason:%s",strerror(errno));
}
char buffer[65536] = {0};
ret = skt.Recv(buffer,sizeof(buffer) - 1);
if(ret == -1)
{
INF_LOG("reason:%s",strerror(errno));
}
else
{
printf("%s",buffer);
}
}
while(1) sleep(1);
skt.Close();
return 0;
}
短连接只需发送一次消息,查看响应的头部是否有Connection: close,如果有则无误,如果没有,则再看看服务器是否把连接关闭了,如果都没有则说明连接模块出问题了,慢慢看日志调吧兄弟。。
压力测试
-
测试工具:Webbench
说明:这是博主从网上找的整理过后放到我的Gitee仓库中便于查看,整理了文档,感兴趣的同学可进一步学习~
-
测试:在你的服务器上使用如下命令进行测试,博主的服务器配置太低只能每秒处理1000个客户端,可以给虚拟机分配4-8G的内存,在上面跑一跑。
-
标准:并发量,可以同时处理多少客户端的请求而不会出现连接失败。Qps,即每秒钟处理的包的数量。
-
示例:如下是博主下的Kali虚拟机,用来简单的测一下,1000的并发量还是轻轻松松的。
补充:本项目还没有经过7*24小时的测试,因此还是有一些小bug的,因此不太稳定,因此供大家进行参考还是可以的,与此同时博主在之后会找时间提高所写代码的稳定性的,又挖了一个坑^ _ ^。
尾序
仿mudo库实现高并发服务器组件项目到这里就到尾声了,博主在后续将开启MySQL的分享,敬请期待!我是舜华期待与你的下一次相遇!我们下篇文章再见了!