对于生命,你不妨大胆一点,
因为我们始终要失去它。
--- 尼采 ---
✨✨✨项目地址在这里 ✨✨✨
✨✨✨https://gitee.com/penggli_2_0/TcpServer✨✨✨
仿mudou的高并发服务器
1 前言
上一篇文章我们基本实现了高并发服务器所需的基础模块,通过TcpServer类可以快速搭建一个TCP服务器。我们的最终目的是使用这个高并发服务器去实现一些业务,那么在网络通信中,我们就可以来实现一下HTTP服务。让浏览器可以访问获取数据。
为了实现HTTP服务器首要的工作就是实现HTTP协议,协议是网络通信的基础!只有确定了协议我们才能正常解析请求报文,并组织应答报文,可以让浏览器成功获取数据。
完成HTTP协议之后,就是设计一种报文解析模块,可以从缓冲区中获取数据,进行解析数据,得到完整请求。
最终将这些整合为一个HTTP服务器模块,设计回调函数,实现HTTP服务器的功能!
2 Util工具类
在HTTP服务器处理中,经常需要一些常用操作,比如切分字符串,编码转换,通过状态码找到对应状态解析… Util工具类就是用来实现这些功能的类!
- SplitStr
- 功能:根据指定的分隔符
sep
将字符串src
切分成多个子字符串,并将这些子字符串存储在sub
向量中。 - 返回值:返回切分后的子字符串数量。
- 功能:根据指定的分隔符
- ReadFile
- 功能:以二进制方式读取文件
filename
的内容到字符串buf
中。 - 返回值:如果文件打开和读取成功,返回
true
;否则返回false
。
- 功能:以二进制方式读取文件
- WriteFile
- 功能:以二进制方式将字符串
buf
的内容写入到文件filename
中,如果文件已存在则覆盖。 - 返回值:如果文件打开和写入成功,返回
true
;否则返回false
。
- 功能:以二进制方式将字符串
- UrlEncode
- 功能:对字符串
url
进行 URL 编码,可以选择是否将空格编码为+
。 - 返回值:返回编码后的字符串。
- 功能:对字符串
- HexToC
- 功能:将十六进制字符转换为对应的整数值。
- 返回值:返回转换后的整数值。
- UrlDecode
- 功能:对字符串
url
进行 URL 解码,可以选择是否将+
解码为空格。 - 返回值:返回解码后的字符串。
- 功能:对字符串
- StatuDesc
- 功能:根据给定的状态码
code
返回对应的状态描述。 - 返回值:返回状态描述字符串,如果状态码未知,则返回 “Unkonw”。
- 功能:根据给定的状态码
- ExtMime
- 功能:根据 URL 的扩展名返回对应的 MIME 类型。
- 返回值:返回 MIME 类型字符串,如果扩展名未知,则返回 “application/octet-stream”。
- IsLegPath
- 功能:检查字符串
path
是否是合法的路径,主要检查是否存在非法的 “…” 使用。 - 返回值:如果路径合法,返回
true
;否则返回false
。
- 功能:检查字符串
- IsDir
- 功能:检查给定的路径
dir
是否是一个目录。 - 返回值:如果是目录,返回
true
;否则返回false
。
- 功能:检查给定的路径
- IsRegular
- 功能:检查给定的路径
dir
是否是一个常规文件。 - 返回值:如果是常规文件,返回
true
;否则返回false
。
- 功能:检查给定的路径
// 公共方法类
class Util
{
public:
static ssize_t SplitStr(const std::string &src, const std::string &sep, std::vector<std::string> &sub)
{
// 根据sep分隔符切分字符串
int offset = 0; // 偏移量
while (offset < src.size())
{
size_t pos = src.find(sep, offset);
// 没有找到sep
if (pos == std::string::npos)
{
// 直接将offset后的字符串当成子串
sub.push_back(src.substr(offset));
break;
}
// 找到了sep
else
{
size_t len = pos - offset;
if (len == 0)
{
offset++;
continue;
}
sub.push_back(src.substr(offset, len));
offset += len; // 偏移量向后移动
}
}
return sub.size();
}
static bool ReadFile(const std::string &filename, std::string *buf)
{
std::ifstream ifs(filename, std::ios::binary); // 以读方式打开文件,采取二进制读取方式
if (ifs.is_open() == false)
{
LOG(ERROR, "Open %s Failed!\n", filename.c_str());
return false;
}
// 获取文件大小
ifs.seekg(0, ifs.end); // 将读取位置移动到文件末尾
size_t n = ifs.tellg(); // 此时的偏移量即为文件大小
ifs.seekg(0, ifs.beg); // 将读取位置移动到到文件开头
buf->resize(n); // 将缓冲区大小设置为文件大小
// 进行写入
ifs.read(&(*buf)[0], n);
// 关闭文件
ifs.close();
return true;
}
static bool WriteFile(const std::string &filename, const std::string &buf)
{
std::ofstream ofs(filename, std::ios::binary | std::ios::trunc); // 使用写方式打开进行二进制覆盖写
if (ofs.is_open() == false)
{
LOG(ERROR, "Open %s Failed!\n", filename.c_str());
return false;
}
// 进行写入
ofs.write(&buf[0], buf.size());
if (ofs.good() == false)
{
LOG(ERROR, "Write %s Failed!\n", filename.c_str());
return false;
}
ofs.close();
return true;
}
static std::string UrlEncode(const std::string &url, bool is_space_encode)
{
std::string ret;
// 进行编码
for (auto ch : url)
{
//. - _ ~ 四个字符绝对不编码
// 字母与数字不见编码
if (ch == '.' || ch == '-' || ch == '_' || ch == '~' || isalnum(ch))
{
ret += ch;
continue;
}
// 空格编码为 +
if (ch == ' ' && is_space_encode)
{
ret += '+';
continue;
}
// 其余字符进行编码
char buf[4]; // 编码格式 %___
snprintf(buf, 4, "%%%02X", ch);
ret += buf;
}
return ret;
}
// URL解码
static char HexToC(char c)
{
if (c >= '0' && c <= '9')
{
return c - '0';
}
else if (c >= 'a' && c <= 'z')
{
return c - 'a' + 10;
}
else if (c >= 'A' && c <= 'Z')
{
return c - 'A' + 10;
}
return -1;
}
static std::string UrlDecode(const std::string &url, bool is_space_decode)
{
std::string res;
// 遍历字符串 遇到%就进行解码
for (int i = 0; i < url.size(); i++)
{
if (url[i] == '%')
{
char v1 = HexToC(url[i + 1]);
char v2 = HexToC(url[i + 2]);
char c = (v1 << 4) + v2;
res += c;
i += 2;
continue;
}
else if (url[i] == '+' && is_space_decode)
{
res += ' ';
continue;
}
else
{
res += url[i];
}
}
return res;
}
// 返回状态码
static std::string StatuDesc(int code)
{
auto ret = _statu_msg.find(code);
if (ret == _statu_msg.end())
{
return "Unkonw";
}
return ret->second;
}
// 解析文件后缀
static std::string ExtMime(const std::string &url)
{
size_t pos = url.rfind('.');
// 没有找到返回
if (pos == std::string::npos)
{
LOG(DEBUG, "没有找到'.'\n");
return "applicantion/octet-stream";
}
std::string str = url.substr(pos);
LOG(DEBUG, "文件类型:%s\n", str.c_str());
auto it = _mime_msg.find