cpp项目 ---ranxin_tinyhttpd(二)
cpp项目 ---ranxin_tinyhttpd
CPP重构版
正片开始!
重构策略
- 面向对象设计:每个类有明确的职责,通过类和对象的组合来完成服务器的功能。
- 线程池:为了提高效率,可以使用线程池来管理
ConnectionHandler
的实例,避免频繁创建和销毁线程的开销。 - 异常处理:使用C++异常处理机制来处理错误,提高代码的健壮性。
- 智能指针:使用智能指针管理动态分配的内存,减少内存泄漏的风险。
- STL:使用标准模板库(STL)提供的数据结构和算法,如
std::vector
,std::map
等。
主体设计结构
下列代码只放hpp定义,具体实现请前往仓库(文章最底部)
主体架构图
main 函数
#include <iostream>
#include <memory>
#include <thread>
#include "Server.h"
#include "ConnectionHandler.h"
int main() {
try {
Server server(4000); // 创建Server实例,监听端口4000
server.start(); // 启动服务器,开始监听端口
std::cout << "Server started on port 4000" << std::endl;
while (true) {
// 等待并接受客户端连接
int clientSocket = server.acceptConnection();
if (clientSocket < 0) {
std::cerr << "Failed to accept client connection" << std::endl;
continue;
}
// 使用智能指针管理ConnectionHandler,确保资源正确释放
std::shared_ptr<ConnectionHandler> handler(new ConnectionHandler(clientSocket));
// 创建一个线程来处理连接,实现并发处理
std::thread([handler]() {
handler->handleRequest();
}).detach(); // 将线程分离,让它独立执行
}
} catch (const std::exception& e) {
std::cerr << "Exception caught in main: " << e.what() << std::endl;
} catch (...) {
std::cerr << "Unknown exception caught in main" << std::endl;
}
return 0;
}
注意,这里的Lambda表达式捕获时使用的是值捕获,如果是引用捕获,中间shared_ptr如果执行或修改了参数,那么表达式内部也会发生变化(socket描述符发生变化,通道无法建立)
同时,不能直接使往thread传入函数指针,因为这里调用的是对象的成员函数,因此传入指针前必须先捕获对象,这就要求使用lambda表达式了
1. Server类
-
职责:负责服务器的初始化、启动、监听。
-
方法
:
start()
: 配置服务器,绑定端口,监听连接。acceptConnection()
: 等待并接受客户端连接。
#ifndef SERVER_CLASS_H
#define SERVER_CLASS_H
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <cstring>
#include <unistd.h>
class Server {
private:
int server_fd; // 服务器套接字文件描述符
struct sockaddr_in address; // 服务器地址
int addrlen; // 地址长度
public:
Server(int port);
bool start();
int acceptConnection();
~Server();
};
#endif
2. ConnectionHandler类
-
职责:处理单个客户端连接。
-
方法
:
handleRequest()
: 处理客户端的HTTP请求。isStaticResource();
: 判断静态资源是否存在
#ifndef CONNECTION_HANDLER_CLASS_H
#define CONNECTION_HANDLER_CLASS_H
#include <iostream>
#include <string>
#include <sys/select.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdexcept>
class ConnectionHandler {
private:
int socket;
public:
ConnectionHandler(int _socket);
void handleRequest();
};
bool isStaticResource(std::string& file);
#endif
3. Request类
-
职责:解析和存储HTTP请求信息。
-
属性
:
- HTTP方法(GET、POST等)
- URL
- 查询字符串
- 报文头
- 报文体
-
方法
:
parseRequest()
: 从客户端连接中读取并解析HTTP请求。
#ifndef REQUEST_CLASS_H
#define REQUEST_CLASS_H
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
class Request {
public:
std::string method;
std::string path;
std::string queryString;
int contentLength;
std::string content;
// 解析请求
void parseRequest(const std::string& requestData);
};
#endif
4. Response类
-
职责:构建和存储HTTP响应信息,发送响应。
-
方法
:
setBody()
: 设置响应体。handleStaticFile
: 处理静态文件handleCgiFile
:处理CGI文件sendResponse
:发送响应notFound()
:处理其他情况
#ifndef RESPONSE_CLASS_H
#define RESPONSE_CLASS_H
#include <string>
#include <sys/socket.h>
#include <fstream>
#include <sstream> // 注意添加这个头文件,因为使用了ostringstream
#include "request.hpp"
#include "CGI_handler.hpp"
class Response {
public:
int clientSocket;
std::string header;
std::string body;
std::string resBuf;
Response(int _socket);
void setBody(const std::string& responseBody);
std::string toString() const;
void handleStaticFile(const std::string& path);
void handleCgiFile(const Request& request);
void notFound();
void sendResponse(int client_fd);
};
#endif // RESPONSE_CLASS_H
5. CGIHandler类
-
职责:执行CGI脚本并处理其输出。
-
方法
:
executeCgi()
: 处理CGI脚本setEnv()
: 设置环境变量childProcess()
: 子进程处理parentProcess()
: 父进程处理
#ifndef CGI_HANDLER_CLASS_H
#define CGI_HANDLER_CLASS_H
#include <string>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <cstdlib>
#include <iostream>
#include <stdexcept>
#include <vector>
class CgiHandler {
private:
std::string method;
std::string path;
std::string queryString;
int contentLength;
int clientSocket;
int cgi_output[2];
int cgi_input[2];
public:
CgiHandler(const std::string& _path, const std::string& _method, const std::string& _queryString, const int& _contentLength, int _clientSocket);
void executeCgi();
protected:
void setEnv();
void childProcess();
void parentProcess();
};
#endif // CGI_HANDLER_CLASS_H
6. Logger类
-
职责:提供日志记录功能。
-
方法
:
log()
: 记录日志信息。
暂无
ACHIEVE
Q&A
-
Q:为什么每次都是用 const type & 进行传参
A:const 是为了防止代码被修改,& 是减少整个拷贝需要的开销(内存、时间),总体来说是为了安全和性能
-
Q:为什么要使用shared_ptr 来处理client连接
A:
std::shared_ptr
的引用计数机制是线程安全的,这意味着它可以安全地在多个线程间共享std::shared_ptr
提供自动的内存管理功能,它会跟踪引用计数,当没有任何shared_ptr
对象指向当前资源时,它会自动释放该资源- 具体详见下一章CPP学习日记(五)
TODO
- 线程池管理client
- 编写Logger类保存日志
- 利用事件循环机制(select、poll、epoll)实现I/O多路复用
ISSUE
- POST带请求体无法传递给管道
参考资料
Tinyhttpd仓库(fork的)
附上本项目仓库
本文链接:
/archives/1709558840292
版权声明:
本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自
打工人驿站!
喜欢就支持一下吧
打赏
微信
支付宝