应用层协议 HTTP
HTTP 协议
虽然我们说, 应用层协议是我们程序猿自己定的. 但实际上, 已经有大佬们定义了一些现成的, 又非常好用的应用层协议, 供我们直接参考使用. HTTP(超文本传输协议)就是其中之一。
在互联网世界中,HTTP(HyperText Transfer Protocol,超文本传输协议)是一个至关重要的协议。它定义了客户端(如浏览器)与服务器之间如何通信,以交换或传输超文本(如 HTML 文档)。
HTTP 协议是客户端与服务器之间通信的基础。客户端通过 HTTP 协议向服务器发送请求,服务器收到请求后处理并返回响应。HTTP 协议是一个无连接、无状态的协议,即每次请求都需要建立新的连接,且服务器不会保存客户端的状态信息。
认识 URL
平时我们俗称的 \"网址\" 其实就是说的 URL
urlencode 和 urldecode
像 / ? : 等这样的字符, 已经被 url 当做特殊意义理解了. 因此这些字符不能随意出现.比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义.
转义的规则如下:
将需要转码的字符转为 16 进制,然后从右到左,取 4 位(不足 4 位直接处理),每 2 位做一位,前面加上%,编码成%XY 格式
例如:
\"+\" 被转义成了 \"%2B\",urldecode 就是 urlencode 的逆过程;
HTTP 协议请求与响应格式
HTTP 请求
首行: [方法] + [url] + [版本]
Header: 请求的属性, 冒号分割的键值对;每组属性之间使用\\r\\n 分隔;遇到空行表示 Header 部分结束
Body: 空行后面的内容都是 Body. Body 允许为空字符串. 如果 Body 存在, 则在Header 中会有一个 Content-Length 属性来标识 Body 的长度;
基本的应答格式
所以http请求是一个大号的字符串
编写 HTTP 请求的代码 - 验证 http 请求
公共代码
Socket.hpp
#pragma once#include #include #include #include #include #include #include #include #include \"Log.hpp\"#include \"Common.hpp\"#include \"InetAddr.hpp\"namespace SocketModule{ using namespace LogModule; const static int gbacklog =16; const static int defaultfd =-1; // 基类socket class Socket { public: virtual ~Socket() {} virtual void SocketOrDie() = 0; virtual void BindOrDie(uint16_t port) = 0; virtual void ListenOrDie(int backlog) = 0; virtual std::shared_ptr Accept(InetAddr * client)= 0; virtual void Close()=0; virtual int Recv(std::string * out) = 0; virtual int Send(std::string &message) = 0; virtual int Connect(const std::string &server_ip ,uint16_t port) =0; public: void BuildTcpClientSocketMethod() { SocketOrDie(); } void BUildTcpLIstenSocketMethod(uint16_t port,int backlog = gbacklog) { SocketOrDie(); BindOrDie(port); ListenOrDie(backlog); } // void BUildUdpSocketMethod() // { // SocketOrDie(); // BindOrDie(); // } }; class TcpSocket : public Socket { public: TcpSocket() :_sockfd(defaultfd) {} TcpSocket(int fd): _sockfd(fd) {} ~TcpSocket() {} void SocketOrDie() override { _sockfd = ::socket(AF_INET, SOCK_STREAM, 0); if (_sockfd < 0) { LOG(Loglevel::FATAL) << \"创建套接字失败!\"; exit(SOCKET_ERR); } LOG(Loglevel::INIF) << \"创建套接字成功!\"; } void BindOrDie(uint16_t port) override { InetAddr localaddr(port); int n = ::bind(_sockfd, localaddr.NetAddrPtr(), localaddr.NetAddrLen()); if (n < 0) { LOG(Loglevel::FATAL) << \"绑定失败!\"; exit(BIND_ERR); } LOG(Loglevel::INIF) << \"绑定成功!\"; } void ListenOrDie(int backlog) override { int n = ::listen(_sockfd, backlog); if (n < 0) { LOG(Loglevel::FATAL) << \"监听失败!\"; exit(LISTEN_ERR); } LOG(Loglevel::INIF) << \"监听成功!\"; } std::shared_ptr Accept(InetAddr * client) override { struct sockaddr_in peer; socklen_t len = sizeof(peer); int fd =::accept(_sockfd,CONV(peer),&len); if (fd < 0) { LOG(Loglevel::WARNING)<<\"连接失败!\"; exit(-1); } LOG(Loglevel::WARNING)<SetAddr(peer); return std::make_shared(fd); } void Close() override { if(_sockfd >=0) { ::close(_sockfd); } } int Recv(std::string * out) override { //流式读取,不关心读到的是什么 char buffer[4096]; ssize_t n =::recv(_sockfd,buffer,sizeof(buffer)-1,0); if (n >0) { buffer[n]=0; *out+=buffer; return n; } return n; } int Send(std::string &message) override { return send(_sockfd,message.c_str(),message.size(),0); } int Connect(const std::string &server_ip ,uint16_t port) override { InetAddr server(server_ip,port); return ::connect(_sockfd,server.NetAddrPtr(),server.NetAddrLen()) ; } private: int _sockfd; // }; // class UdpSocket : public Socket // { // };}
Mutex.hpp
#pragma once#include #include namespace MutexModule{ class Mutex { public: Mutex() { pthread_mutex_init(&_mutex, nullptr); } void Lock() { int n = pthread_mutex_lock(&_mutex); (void)n; } void Unlock() { int n = pthread_mutex_unlock(&_mutex); (void)n; } ~Mutex() { pthread_mutex_destroy(&_mutex); } pthread_mutex_t *get() { return &_mutex; } private: pthread_mutex_t _mutex; }; class LockGuard { public: LockGuard(Mutex &mutex):_mutex(mutex) { _mutex.Lock(); } ~LockGuard() { _mutex.Unlock(); } private: Mutex &_mutex; };}
Log.hpp
#ifndef __LOG_HPP__#define __LOG_HPP__#include #include #include \"Mutex.hpp\"#include #include #include #include #include #includenamespace LogModule{ const std::string sep = \"\\r\\n\"; using namespace MutexModule ; // 2.刷新策略 class LogStrategy { public: ~LogStrategy() = default; virtual void SyncLog(const std::string &message) = 0; }; // 显示器刷新日志的策略 class ConsoleLogStrategy : public LogStrategy { public: ConsoleLogStrategy() {} ~ConsoleLogStrategy() {} void SyncLog(const std::string &message) override { LockGuard lockguard(_mutex); std::cout << message << sep; } private: Mutex _mutex; }; // 缺省文件路径以及文件本身 const std::string defaultpath = \"./log\"; const std::string defaultfile = \"my.log\"; // 文件刷新日志的策略 class FileLogStrategy : public LogStrategy { public: FileLogStrategy(const std::string &path = defaultpath, const std::string &file = defaultfile) : _path(path), _file(file) { LockGuard lockguard(_mutex); if (std::filesystem::exists(_path)) // 判断路径是否存在 { return; } try { std::filesystem::create_directories(_path); } catch (const std::filesystem::filesystem_error &e) { std::cerr << e.what() << \'\\n\'; } } void SyncLog(const std::string &message) override { LockGuard lockguard(_mutex); std::string filename = _path + (_path.back() == \'/\' ? \"\" : \"/\") + _file; std::ofstream out(filename, std::ios::app); // 追加写入 if (!out.is_open()) { return; } out << message << sep; out.close(); } ~FileLogStrategy() {} private: Mutex _mutex; std::string _path; // 日志文件的路径 std::string _file; // 要打印的日志文件 }; // 形成日志等级 enum class Loglevel { DEBUG, INIF, WARNING, ERROR, FATAL }; std::string Level2Str(Loglevel level) { switch (level) { case Loglevel::DEBUG: return \"DEBUG\"; case Loglevel::INIF: return \"INIF\"; case Loglevel::WARNING: return \"WARNING\"; case Loglevel::ERROR: return \"ERROR\"; case Loglevel::FATAL: return \"FATAL\"; default: return \"UNKNOWN\"; } } std::string GetTimeStamp() { time_t cuur =time(nullptr); struct tm curr_tm; localtime_r(&cuur,&curr_tm); char buffer[128]; snprintf(buffer,sizeof(buffer),\"%4d-%02d-%02d %02d:%02d:%02d\", curr_tm.tm_year+1900, curr_tm.tm_mon+1, curr_tm.tm_mday, curr_tm.tm_hour, curr_tm.tm_min, curr_tm.tm_sec ); return buffer; } class Logger { public: Logger() { EnableConsoleLogStrategy(); } // 选择某种策略 // 1.文件 void EnableFileLogStrategy() { _ffush_strategy = std::make_unique(); } // 显示器 void EnableConsoleLogStrategy() { _ffush_strategy = std::make_unique(); } // 表示的是未来的一条日志 class LogMessage { public: LogMessage(Loglevel &level, std::string &src_name, int line_number, Logger &logger) : _curr_time(GetTimeStamp()) , _level(level) , _pid(getpid()) , _src_name(src_name) , _line_number(line_number) , _logger(logger) { // 合并左半部分 std::stringstream ss; ss << \"[\" << _curr_time << \"] \" << \"[\" << Level2Str(_level) << \"] \" << \"[\" << _pid << \"] \" << \"[\" << _src_name << \"] \" << \"[\" << _line_number << \"] \" << \"- \"; _loginfo = ss.str(); } template LogMessage &operator<<(const T &info) { // 右半部分,可变 std::stringstream ss; ss <SyncLog(_loginfo); } } private: std::string _curr_time; // 日志时间 Loglevel _level; // 日志状态 pid_t _pid; // 进程pid std::string _src_name; // 文件名称 int _line_number; // 对应的行号 std::string _loginfo; // 合并之后的一条完整信息 Logger &_logger; }; LogMessage operator()(Loglevel level, std::string src_name, int line_number) { return LogMessage(level, src_name, line_number, *this); } ~Logger() {} private: std::unique_ptr _ffush_strategy; }; //全局日志对象 Logger logger; //使用宏,简化用户操作,获取文件名和行号 // __FILE__ 一个宏,替换完成后目标文件的文件名 // __LINE__ 一个宏,替换完成后目标文件对应的行号 #define LOG(level) logger(level,__FILE__,__LINE__) #define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy() #define Enable_File_Log_Strategy() logger.EnableFileLogStrategy() }#endif
Main.cc
#include\"Http.hpp\"// http portint main(int argc,char* argv[]){ if(argc != 2) { std::cout << \"Usage: \"<<argv[0]<<\" port\"<<std::endl; exit(USAGE_ERR); } uint16_t port = std::stoi(argv[1]); std::unique_ptr httpsvr = std::make_unique(port); httpsvr->Start(); return 0;}
Common.hpp
#pragma once#include #include#include #include #include #include#include #include #include #include enum ExitCode{ OK = 0, USAGE_ERR, SOCKET_ERR, BIND_ERR, LISTEN_ERR, CONNECT_ERR, FORK_ERR};class NoCopy{public:NoCopy(){}~NoCopy(){}NoCopy(const NoCopy&)=delete;const NoCopy& operator= (const NoCopy &)=delete;};#define CONV(addr) ((struct sockaddr *)&addr)
InetAddr.hpp
#pragma once#include \"Common.hpp\"class InetAddr{public: InetAddr() { } InetAddr(struct sockaddr_in &addr) { SetAddr(addr); } InetAddr(const std::string &ip,uint16_t port) :_ip(ip) ,_port(port) { //主机转网络 memset(&_addr,0,sizeof(_addr)); _addr.sin_family=AF_INET; inet_pton(AF_INET,_ip.c_str(),&_addr.sin_addr); _addr.sin_port=htons(_port); } InetAddr(uint16_t port) :_port(port),_ip(\"0\") { //端口转 memset(&_addr,0,sizeof(_addr)); _addr.sin_family=AF_INET; _addr.sin_addr.s_addr=INADDR_ANY; _addr.sin_port = htons(_port); } uint16_t Port() { return _port; } std::string Ip() { return _ip; } void SetAddr(struct sockaddr_in &addr) { //网络转主机 _addr=addr; _port = ntohs(addr.sin_port); //_ip = inet_ntoa(addr.sin_addr); char ipbuffer[64]; inet_ntop(AF_INET,&_addr.sin_addr,ipbuffer,sizeof(_addr)); _ip=ipbuffer; } const struct sockaddr_in &NetAddr() { return _addr; } const struct sockaddr *NetAddrPtr() { return CONV(_addr); } socklen_t NetAddrLen() { return sizeof(_addr); } bool operator==(const InetAddr &addr) { return addr._ip == _ip && addr._port == _port; } std::string StringAddr() { return _ip + \":\" + std::to_string(_port); } ~InetAddr() {}private: struct sockaddr_in _addr; std::string _ip; uint16_t _port;};
Tcpserver.hpp
#include \"Socket.hpp\"#include#include#include #include using namespace LogModule;using namespace SocketModule;using ioservice_t = std::function<void(std::shared_ptr&sock,InetAddr &client)>;class TcpServer{public:TcpServer(uint16_t port):_port(port),_listensockptr(std::make_unique()),_isrunning(false){ _listensockptr->BUildTcpLIstenSocketMethod(_port);}void Start(ioservice_t callback){ _isrunning =true; while(_isrunning) { InetAddr client; auto sock = _listensockptr->Accept(&client); if(sock == nullptr) { continue; } LOG(Loglevel::DEBUG)<<\"accept is running\"<<client.StringAddr(); //sock && client pid_t id =fork(); if (id < 0) { LOG(Loglevel::FATAL)<Close(); if(fork()>0) { exit(0); } //孙子进程是孤儿进程 callback(sock,client); exit(OK); } else { //父进程 关闭sock sock->Close(); pid_t rid = ::waitpid(id,nullptr,0); (void)rid; } } _isrunning =false;}~TcpServer(){}private:uint16_t _port;std::unique_ptr_listensockptr;bool _isrunning;};
Makefile
myhttp:Main.ccg++ -o $@ $^ -std=c++17.PHONY:cleanclean:rm -f myhttp
Http.hpp
#pragma once#include \"TcpServer.hpp\"#include #include #include #include using namespace SocketModule;const std::string gspace = \" \";const std::string glinespace = \"\\r\\n\";const std::string glinesep = \": \";class HttpRequest{public: std::string Serialize() { return std::string(); } bool Deserialize(std::string &reqstr) { return true; } HttpRequest() {} ~HttpRequest() {}private: std::string _method; std::string _url; std::string _version; std::unordered_map _headers; std::string _blankline; std::string _text;};class HttpResponse{ public: std::string Serialize() { std::string status_line = _version +\" \"+gspace+std::to_string(_code)+gspace+_desc+glinespace; std::string resp_header; for(auto &header : _headrs) { std::string line = header.first+glinesep+header.second+glinespace; resp_header+=line; } return status_line+resp_header+_blankline+_text; } bool Deserialize(std::string &reqstr) { return true; } HttpResponse() :_blankline(glinespace) {} ~HttpResponse(){}std::string _version; //版本int _code; //状态码std::string _desc; //状态码描述std::unordered_map _headrs;//相应报头std::string _blankline;//空行std::string _text;//正文};class Http{public: Http(uint16_t port) : tsvrp(std::make_unique(port)) { } void HandlenHttpRequest(std::shared_ptr &sock, InetAddr &client) { #ifndef DEBUG #define DEBUG std::string httprequest; sock->Recv(&httprequest); std::cout<<httprequest<<std::endl; //构建应答,内存级 + 固定 HttpResponse resp; resp._version = \"HTTP?1.1\"; resp._code = 200;//正常 resp._desc =\"OK\"; resp._text =\"\\ \\ \\ \\ \\ Hello World \\ \\ \\ Hello World
\\ \\ \"; std::string response_str = resp.Serialize(); sock->Send(response_str); #endif } void Start() { tsvrp->Start([this](std::shared_ptr &sock, InetAddr &client) { this->HandlenHttpRequest(sock, client); }); } ~Http() {}private: std::unique_ptr tsvrp;};
这是浏览器返回的部分http
respones返回一个html
HTTP 的方法
其中最常用的就是 GET 方法和 POST 方法.
HTTP 常见方法
1.GET 方法(重点)
用途:用于请求 URL 指定的资源。
示例:GET /index.html HTTP/1.1
特性:指定资源经服务器端解析后返回响应内容。
form 表单:https://www.runoob.com/html/html-forms.html
2.POST 方法(重点)
用途:用于传输实体的主体,通常用于提交表单数据。
示例:POST /submit.cgi HTTP/1.1
特性:可以发送大量的数据给服务器,并且数据包含在请求体中。
form 表单:https://www.runoob.com/html/html-forms.html
3.PUT 方法(不常用)
用途:用于传输文件,将请求报文主体中的文件保存到请求 URL 指定的位置。
示例:PUT /example.html HTTP/1.1
特性:不太常用,但在某些情况下,如 RESTful API 中,用于更新资源。
4. HEAD 方法
用途:与 GET 方法类似,但不返回报文主体部分,仅返回响应头。
示例:HEAD /index.html HTTP/1.1
特性:用于确认 URL 的有效性及资源更新的日期时间等。
5. DELETE 方法(不常用)
用途:用于删除文件,是 PUT 的相反方法。
示例:DELETE /example.html HTTP/1.1
特性:按请求 URL 删除指定的资源。
6. OPTIONS 方法
用途:用于查询针对请求 URL 指定的资源支持的方法。
示例:OPTIONS * HTTP/1.1
特性:返回允许的方法,如 GET、POST 等。
HttpDamon 1
Util.hpp
#pragma once#include #include #include class Util{ public: //打开指定文件static bool ReadFileContent(const std::string &filename,std::string *out) { //version1 std::ifstream in(filename); if(!in.is_open()) { return false; } std::string line; while(std::getline(in,line)) { *out+=line; } in.close(); return true; }};
index.html
Hello World Hello World !
Are you OK ?
Common.hpp
#pragma once#include #include#include #include #include #include#include #include #include #include enum ExitCode{ OK = 0, USAGE_ERR, SOCKET_ERR, BIND_ERR, LISTEN_ERR, CONNECT_ERR, FORK_ERR};class NoCopy{public:NoCopy(){}~NoCopy(){}NoCopy(const NoCopy&)=delete;const NoCopy& operator= (const NoCopy &)=delete;};#define CONV(addr) ((struct sockaddr *)&addr)
Http.hpp
#pragma once#include \"TcpServer.hpp\"#include #include #include #include #include\"Util.hpp\"using namespace SocketModule;const std::string gspace = \" \";const std::string glinespace = \"\\r\\n\";const std::string glinesep = \": \";const std::string webroot = \"./wwwroot\";const std::string homepage = \"/index.html\";class HttpRequest{public: std::string Serialize() { return std::string(); } bool Deserialize(std::string &reqstr) { return true; } HttpRequest() {} ~HttpRequest() {}private: std::string _method; std::string _url; std::string _version; std::unordered_map _headers; std::string _blankline; std::string _text;};class HttpResponse{ public: std::string Serialize() { std::string status_line = _version +\" \"+gspace+std::to_string(_code)+gspace+_desc+glinespace; std::string resp_header; for(auto &header : _headrs) { std::string line = header.first+glinesep+header.second+glinespace; resp_header+=line; } return status_line+resp_header+_blankline+_text; } bool Deserialize(std::string &reqstr) { return true; } HttpResponse() :_blankline(glinespace) {} ~HttpResponse(){}std::string _version; //版本int _code; //状态码std::string _desc; //状态码描述std::unordered_map _headrs;//相应报头std::string _blankline;//空行std::string _text;//正文};class Http{public: Http(uint16_t port) : tsvrp(std::make_unique(port)) { } void HandlenHttpRequest(std::shared_ptr &sock, InetAddr &client) { #ifndef DEBUG #define DEBUG std::string httprequest; sock->Recv(&httprequest); std::cout<<httprequest<Send(response_str); #endif } void Start() { tsvrp->Start([this](std::shared_ptr &sock, InetAddr &client) { this->HandlenHttpRequest(sock, client); }); } ~Http() {}private: std::unique_ptr tsvrp;};
InerAddr.hpp
#pragma once#include \"Common.hpp\"class InetAddr{public: InetAddr() { } InetAddr(struct sockaddr_in &addr) { SetAddr(addr); } InetAddr(const std::string &ip,uint16_t port) :_ip(ip) ,_port(port) { //主机转网络 memset(&_addr,0,sizeof(_addr)); _addr.sin_family=AF_INET; inet_pton(AF_INET,_ip.c_str(),&_addr.sin_addr); _addr.sin_port=htons(_port); } InetAddr(uint16_t port) :_port(port),_ip(\"0\") { //端口转 memset(&_addr,0,sizeof(_addr)); _addr.sin_family=AF_INET; _addr.sin_addr.s_addr=INADDR_ANY; _addr.sin_port = htons(_port); } uint16_t Port() { return _port; } std::string Ip() { return _ip; } void SetAddr(struct sockaddr_in &addr) { //网络转主机 _addr=addr; _port = ntohs(addr.sin_port); //_ip = inet_ntoa(addr.sin_addr); char ipbuffer[64]; inet_ntop(AF_INET,&_addr.sin_addr,ipbuffer,sizeof(_addr)); _ip=ipbuffer; } const struct sockaddr_in &NetAddr() { return _addr; } const struct sockaddr *NetAddrPtr() { return CONV(_addr); } socklen_t NetAddrLen() { return sizeof(_addr); } bool operator==(const InetAddr &addr) { return addr._ip == _ip && addr._port == _port; } std::string StringAddr() { return _ip + \":\" + std::to_string(_port); } ~InetAddr() {}private: struct sockaddr_in _addr; std::string _ip; uint16_t _port;};
Log.hpp
#ifndef __LOG_HPP__#define __LOG_HPP__#include #include #include \"Mutex.hpp\"#include #include #include #include #include #includenamespace LogModule{ const std::string sep = \"\\r\\n\"; using namespace MutexModule ; // 2.刷新策略 class LogStrategy { public: ~LogStrategy() = default; virtual void SyncLog(const std::string &message) = 0; }; // 显示器刷新日志的策略 class ConsoleLogStrategy : public LogStrategy { public: ConsoleLogStrategy() {} ~ConsoleLogStrategy() {} void SyncLog(const std::string &message) override { LockGuard lockguard(_mutex); std::cout << message << sep; } private: Mutex _mutex; }; // 缺省文件路径以及文件本身 const std::string defaultpath = \"./log\"; const std::string defaultfile = \"my.log\"; // 文件刷新日志的策略 class FileLogStrategy : public LogStrategy { public: FileLogStrategy(const std::string &path = defaultpath, const std::string &file = defaultfile) : _path(path), _file(file) { LockGuard lockguard(_mutex); if (std::filesystem::exists(_path)) // 判断路径是否存在 { return; } try { std::filesystem::create_directories(_path); } catch (const std::filesystem::filesystem_error &e) { std::cerr << e.what() << \'\\n\'; } } void SyncLog(const std::string &message) override { LockGuard lockguard(_mutex); std::string filename = _path + (_path.back() == \'/\' ? \"\" : \"/\") + _file; std::ofstream out(filename, std::ios::app); // 追加写入 if (!out.is_open()) { return; } out << message << sep; out.close(); } ~FileLogStrategy() {} private: Mutex _mutex; std::string _path; // 日志文件的路径 std::string _file; // 要打印的日志文件 }; // 形成日志等级 enum class Loglevel { DEBUG, INIF, WARNING, ERROR, FATAL }; std::string Level2Str(Loglevel level) { switch (level) { case Loglevel::DEBUG: return \"DEBUG\"; case Loglevel::INIF: return \"INIF\"; case Loglevel::WARNING: return \"WARNING\"; case Loglevel::ERROR: return \"ERROR\"; case Loglevel::FATAL: return \"FATAL\"; default: return \"UNKNOWN\"; } } std::string GetTimeStamp() { time_t cuur =time(nullptr); struct tm curr_tm; localtime_r(&cuur,&curr_tm); char buffer[128]; snprintf(buffer,sizeof(buffer),\"%4d-%02d-%02d %02d:%02d:%02d\", curr_tm.tm_year+1900, curr_tm.tm_mon+1, curr_tm.tm_mday, curr_tm.tm_hour, curr_tm.tm_min, curr_tm.tm_sec ); return buffer; } class Logger { public: Logger() { EnableConsoleLogStrategy(); } // 选择某种策略 // 1.文件 void EnableFileLogStrategy() { _ffush_strategy = std::make_unique(); } // 显示器 void EnableConsoleLogStrategy() { _ffush_strategy = std::make_unique(); } // 表示的是未来的一条日志 class LogMessage { public: LogMessage(Loglevel &level, std::string &src_name, int line_number, Logger &logger) : _curr_time(GetTimeStamp()) , _level(level) , _pid(getpid()) , _src_name(src_name) , _line_number(line_number) , _logger(logger) { // 合并左半部分 std::stringstream ss; ss << \"[\" << _curr_time << \"] \" << \"[\" << Level2Str(_level) << \"] \" << \"[\" << _pid << \"] \" << \"[\" << _src_name << \"] \" << \"[\" << _line_number << \"] \" << \"- \"; _loginfo = ss.str(); } template LogMessage &operator<<(const T &info) { // 右半部分,可变 std::stringstream ss; ss <SyncLog(_loginfo); } } private: std::string _curr_time; // 日志时间 Loglevel _level; // 日志状态 pid_t _pid; // 进程pid std::string _src_name; // 文件名称 int _line_number; // 对应的行号 std::string _loginfo; // 合并之后的一条完整信息 Logger &_logger; }; LogMessage operator()(Loglevel level, std::string src_name, int line_number) { return LogMessage(level, src_name, line_number, *this); } ~Logger() {} private: std::unique_ptr _ffush_strategy; }; //全局日志对象 Logger logger; //使用宏,简化用户操作,获取文件名和行号 // __FILE__ 一个宏,替换完成后目标文件的文件名 // __LINE__ 一个宏,替换完成后目标文件对应的行号 #define LOG(level) logger(level,__FILE__,__LINE__) #define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy() #define Enable_File_Log_Strategy() logger.EnableFileLogStrategy() }#endif
Main.cc
#include\"Http.hpp\"// http portint main(int argc,char* argv[]){ if(argc != 2) { std::cout << \"Usage: \"<<argv[0]<<\" port\"<<std::endl; exit(USAGE_ERR); } uint16_t port = std::stoi(argv[1]); std::unique_ptr httpsvr = std::make_unique(port); httpsvr->Start(); return 0;}
Makefile
myhttp:Main.ccg++ -o $@ $^ -std=c++17.PHONY:cleanclean:rm -f myhttp
Mutex.hpp
#pragma once#include #include namespace MutexModule{ class Mutex { public: Mutex() { pthread_mutex_init(&_mutex, nullptr); } void Lock() { int n = pthread_mutex_lock(&_mutex); (void)n; } void Unlock() { int n = pthread_mutex_unlock(&_mutex); (void)n; } ~Mutex() { pthread_mutex_destroy(&_mutex); } pthread_mutex_t *get() { return &_mutex; } private: pthread_mutex_t _mutex; }; class LockGuard { public: LockGuard(Mutex &mutex):_mutex(mutex) { _mutex.Lock(); } ~LockGuard() { _mutex.Unlock(); } private: Mutex &_mutex; };}
Socket.hpp
#pragma once#include #include #include #include #include #include #include #include #include \"Log.hpp\"#include \"Common.hpp\"#include \"InetAddr.hpp\"namespace SocketModule{ using namespace LogModule; const static int gbacklog =16; const static int defaultfd =-1; // 基类socket class Socket { public: virtual ~Socket() {} virtual void SocketOrDie() = 0; virtual void BindOrDie(uint16_t port) = 0; virtual void ListenOrDie(int backlog) = 0; virtual std::shared_ptr Accept(InetAddr * client)= 0; virtual void Close()=0; virtual int Recv(std::string * out) = 0; virtual int Send(std::string &message) = 0; virtual int Connect(const std::string &server_ip ,uint16_t port) =0; public: void BuildTcpClientSocketMethod() { SocketOrDie(); } void BUildTcpLIstenSocketMethod(uint16_t port,int backlog = gbacklog) { SocketOrDie(); BindOrDie(port); ListenOrDie(backlog); } // void BUildUdpSocketMethod() // { // SocketOrDie(); // BindOrDie(); // } }; class TcpSocket : public Socket { public: TcpSocket() :_sockfd(defaultfd) {} TcpSocket(int fd): _sockfd(fd) {} ~TcpSocket() {} void SocketOrDie() override { _sockfd = ::socket(AF_INET, SOCK_STREAM, 0); if (_sockfd < 0) { LOG(Loglevel::FATAL) << \"创建套接字失败!\"; exit(SOCKET_ERR); } LOG(Loglevel::INIF) << \"创建套接字成功!\"; } void BindOrDie(uint16_t port) override { InetAddr localaddr(port); int n = ::bind(_sockfd, localaddr.NetAddrPtr(), localaddr.NetAddrLen()); if (n < 0) { LOG(Loglevel::FATAL) << \"绑定失败!\"; exit(BIND_ERR); } LOG(Loglevel::INIF) << \"绑定成功!\"; } void ListenOrDie(int backlog) override { int n = ::listen(_sockfd, backlog); if (n < 0) { LOG(Loglevel::FATAL) << \"监听失败!\"; exit(LISTEN_ERR); } LOG(Loglevel::INIF) << \"监听成功!\"; } std::shared_ptr Accept(InetAddr * client) override { struct sockaddr_in peer; socklen_t len = sizeof(peer); int fd =::accept(_sockfd,CONV(peer),&len); if (fd < 0) { LOG(Loglevel::WARNING)<<\"连接失败!\"; exit(-1); } LOG(Loglevel::WARNING)<SetAddr(peer); return std::make_shared(fd); } void Close() override { if(_sockfd >=0) { ::close(_sockfd); } } int Recv(std::string * out) override { //流式读取,不关心读到的是什么 char buffer[4096]; ssize_t n =::recv(_sockfd,buffer,sizeof(buffer)-1,0); if (n >0) { buffer[n]=0; *out+=buffer; return n; } return n; } int Send(std::string &message) override { return send(_sockfd,message.c_str(),message.size(),0); } int Connect(const std::string &server_ip ,uint16_t port) override { InetAddr server(server_ip,port); return ::connect(_sockfd,server.NetAddrPtr(),server.NetAddrLen()) ; } private: int _sockfd; // }; // class UdpSocket : public Socket // { // };}
TcpServer.hpp
#include \"Socket.hpp\"#include#include#include #include using namespace LogModule;using namespace SocketModule;using ioservice_t = std::function<void(std::shared_ptr&sock,InetAddr &client)>;class TcpServer{public:TcpServer(uint16_t port):_port(port),_listensockptr(std::make_unique()),_isrunning(false){ _listensockptr->BUildTcpLIstenSocketMethod(_port);}void Start(ioservice_t callback){ _isrunning =true; while(_isrunning) { InetAddr client; auto sock = _listensockptr->Accept(&client); if(sock == nullptr) { continue; } LOG(Loglevel::DEBUG)<<\"accept is running\"<<client.StringAddr(); //sock && client pid_t id =fork(); if (id < 0) { LOG(Loglevel::FATAL)<Close(); if(fork()>0) { exit(0); } //孙子进程是孤儿进程 callback(sock,client); exit(OK); } else { //父进程 关闭sock sock->Close(); pid_t rid = ::waitpid(id,nullptr,0); (void)rid; } } _isrunning =false;}~TcpServer(){}private:uint16_t _port;std::unique_ptr_listensockptr;bool _isrunning;};
HTTP 的状态码
最常见的状态码, 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向), 504(Bad Gateway)
以下是仅包含重定向相关状态码的表格:
关于重定向的验证,以 301 为代表 ,HTTP 状态码 301(永久重定向)和 302(临时重定向)都依赖 Location 选项。以下是关于两者依赖 Location 选项的详细说明:
HTTP 状态码 301(永久重定向):
当服务器返回 HTTP 301 状态码时,表示请求的资源已经被永久移动到新的位置。在这种情况下,服务器会在响应中添加一个 Location 头部,用于指定资源的新位置。这个 Location 头部包含了新的 URL 地址,浏览器会自动重定向到该地址。例如,在 HTTP 响应中,可能会看到类似于以下的头部信息:
HTTP/1.1 301 Moved Permanently\\r\\nLocation: https://www.new-url.com\\r\\n
HTTP 状态码 302(临时重定向):
当服务器返回 HTTP 302 状态码时,表示请求的资源临时被移动到新的位置。同样地,服务器也会在响应中添加一个 Location 头部来指定资源的新位置。浏览器会暂时使用新的 URL 进行后续的请求,但不会缓存这个重定向。例如,在 HTTP 响应中,可能会看到类似于以下的头部信息:
HTTP/1.1 302 Found\\r\\nLocation: https://www.new-url.com\\r\\n
总结:无论是 HTTP 301 还是 HTTP 302 重定向,都需要依赖 Location 选项来指定资源的新位置。这个 Location 选项是一个标准的 HTTP 响应头部,用于告诉浏览器应该将请求重定向到哪个新的 URL 地址。
HTTP 常见 Header
- Content-Type: 数据类型(text/html 等)
- Content-Length: Body 的长度
- Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上
- User-Agent: 声明用户的操作系统和浏览器版本信息
- referer: 当前页面是从哪个页面跳转过来的
- Location: 搭配 3xx 状态码使用, 告诉客户端接下来要去哪里访问
- Cookie: 用于在客户端存储少量信息. 通常用于实现会(session)的功能
HTTPDamon 2
404.html
404 - Page Not Found body { font-family: Arial, sans-serif; background-color: #f4f4f4; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; flex-direction: column; text-align: center; } .error-container { max-width: 600px; text-align: center; } .error-container h1 { font-size: 3em; color: #d9534f; margin-bottom: 10px; } .error-container p { font-size: 1.2em; color: #333; margin-bottom: 20px; } .error-container a { display: inline-block; padding: 10px 20px; background-color: #007bff; color: #fff; text-decoration: none; border-radius: 4px; font-size: 1em; } .error-container a:hover { background-color: #0056b3; }
index.html
Home Page body { font-family: Arial, sans-serif; background-color: #f4f4f4; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; flex-direction: column; } .home-container { text-align: center; } .home-container h1 { font-size: 2.5em; margin-bottom: 20px; } .button-group { display: flex; justify-content: center; gap: 20px; } .button-group a { display: inline-block; padding: 10px 20px; background-color: #007bff; color: #fff; text-decoration: none; border-radius: 4px; font-size: 1em; } .button-group a:hover { background-color: #0056b3; } Welcome to Our Website
Login.html
Login Page body { font-family: Arial, sans-serif; background-color: #f4f4f4; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; } .login-container { background-color: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); width: 300px; text-align: center; } .login-container h2 { margin-bottom: 20px; } .form-group { margin-bottom: 15px; } .form-group label { display: block; margin-bottom: 5px; text-align: left; } .form-group input { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; } .form-group input:focus { outline: none; border-color: #007bff; } .form-group button { width: 100%; padding: 10px; border: none; background-color: #007bff; color: #fff; border-radius: 4px; cursor: pointer; } .form-group button:hover { background-color: #0056b3; } Login
Register.html
Registration Page body { font-family: Arial, sans-serif; background-color: #f4f4f4; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; } .register-container { background-color: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); width: 350px; text-align: center; } .register-container h2 { margin-bottom: 20px; } .form-group { margin-bottom: 15px; } .form-group label { display: block; margin-bottom: 5px; text-align: left; } .form-group input { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; } .form-group input:focus { outline: none; border-color: #007bff; } .form-group button { width: 100%; padding: 10px; border: none; background-color: #007bff; color: #fff; border-radius: 4px; cursor: pointer; } .form-group button:hover { background-color: #0056b3; } .form-group .error { color: red; font-size: 0.8em; text-align: left; margin-top: 5px; } Register
document.getElementById(\'registrationForm\').addEventListener(\'submit\', function(event) { const password = document.getElementById(\'password\').value; const confirmPassword = document.getElementById(\'confirm-password\').value; const passwordError = document.getElementById(\'passwordError\'); if (password !== confirmPassword) { passwordError.textContent = \'Passwords do not match\'; event.preventDefault(); // Prevent form submission } else { passwordError.textContent = \'\'; } });
Common.hpp
#pragma once#include #include#include #include #include #include#include #include #include #include enum ExitCode{ OK = 0, USAGE_ERR, SOCKET_ERR, BIND_ERR, LISTEN_ERR, CONNECT_ERR, FORK_ERR};class NoCopy{public:NoCopy(){}~NoCopy(){}NoCopy(const NoCopy&)=delete;const NoCopy& operator= (const NoCopy &)=delete;};#define CONV(addr) ((struct sockaddr *)&addr)
Http.hpp
#pragma once#include \"TcpServer.hpp\"#include #include #include #include #include #include \"Util.hpp\"using namespace SocketModule;const std::string gspace = \" \";const std::string glinespace = \"\\r\\n\";const std::string glinesep = \": \";const std::string webroot = \"./wwwroot\";const std::string homepage = \"index.html\";const std::string page_404 = \"/404.html\";class HttpRequest{public: std::string Serialize() { return std::string(); } bool Deserialize(std::string &reqstr) { //1.提取请求行 std::string reqline; bool res = Util::ReadOneLine(reqstr,&reqline,glinespace); LOG(Loglevel::DEBUG)<<\"reqline: \"<> _method >> _uri >> _version; } HttpRequest() {} ~HttpRequest() {} std::string Uri() { return _uri; }private: std::string _method; std::string _uri; std::string _version; std::unordered_map _headers; std::string _blankline; std::string _text;};class HttpResponse{ public: HttpResponse() :_version(\"HTTP/1.1\") ,_blankline(glinespace) {} public: std::string Serialize() { std::string status_line = _version +\" \"+gspace+std::to_string(_code)+gspace+_desc+glinespace; std::string resp_header; for(auto &header : _headrs) { std::string line = header.first+glinesep+header.second+glinespace; resp_header+=line; } return status_line+resp_header+_blankline+_text; } void SetHeader(const std::string &key,const std::string &value) { auto iter = _headrs.find(key); if(iter != _headrs.end()) return ; _headrs.insert(std::make_pair(key,value)); } bool MakeResponse() { if(_targetfile== \"./wwwroot/favicon.ico\") { LOG(Loglevel::DEBUG)<<\"用户请求:\"<<_targetfile<<\"忽略他\"; return false; } int filesize=0; bool res = Util::ReadFileContent(_targetfile,&_text); if(!res) { _text = \"\"; LOG(Loglevel::WARNING) << \"client want get : \" << _targetfile << \" but not found\"; SetCode(404); _targetfile = webroot+page_404; filesize = Util::FileSize(_targetfile); Util::ReadFileContent(_targetfile,&_text); std::string suffix = Uri2Suffix(_targetfile); SetHeader(\"Conent-Type\",suffix); SetHeader(\"Content-Length\",std::to_string(filesize)); } else { LOG(Loglevel::DEBUG) << \"读取文件: \" << _targetfile; SetCode(200); filesize = Util::FileSize(_targetfile); std::string suffix = Uri2Suffix(_targetfile); SetHeader(\"Conent-Type\",suffix); SetHeader(\"Content-Length\",std::to_string(filesize)); } return true; } std::string Uri2Suffix(const std::string &targetfile) { // ./wwwroot/a/b/c.html auto pos = targetfile.rfind(\".\"); if(pos == std::string::npos) { return \"text/html\"; } std::string suffix = targetfile.substr(pos); if(suffix == \".html\"||suffix == \".htm\") { return \"text/html\"; } else if(suffix ==\".png\") { return \"image/png\"; } else if (suffix == \".jpg\") { return \"image/jpeg\"; } else return \"\"; } void SetCode(int code) { _code=code; switch (_code) { case 200: _desc=\"OK\"; break; case 404: _desc=\"Not Found\"; break; default: break; } } void SetTargeFile(const std::string &target) { _targetfile = target; } bool Deserialize(std::string &reqstr) { std::string reqline; bool res = Util::ReadOneLine(reqstr,&reqline,glinespace); return true; } ~HttpResponse(){}std::string _version; //版本int _code; //状态码std::string _desc; //状态码描述std::unordered_map _headrs;//相应报头std::string _blankline;//空行std::string _text;//正文std::string _targetfile;};class Http{public: Http(uint16_t port) : tsvrp(std::make_unique(port)) { } void HandlenHttpRequest(std::shared_ptr &sock, InetAddr &client) { std::string httpreqstr; //浏览器发给我的是一个大号的http字符串,其实我们的recv也是有问题的。tcp是面向字节流的 int n = sock->Recv(&httpreqstr); if( n > 0) { HttpRequest req; req.Deserialize(httpreqstr); HttpResponse resp; resp. SetTargeFile(req.Uri()); if(resp.MakeResponse()) { std::string response_str = resp.Serialize(); sock->Send(response_str); } // std::string filename = req.Uri(); // HttpResponse resp; // resp._version = \"HTTP/1.1\"; // resp._code = 200;//正常 // resp._desc =\"OK\"; // LOG(Loglevel::DEBUG)<<\"用户请求: \"<Recv(&httprequest); std::cout<<httprequest<Send(response_str); #endif } void Start() { tsvrp->Start([this](std::shared_ptr &sock, InetAddr &client) { this->HandlenHttpRequest(sock, client); }); } ~Http() {}private: std::unique_ptr tsvrp;};
InetAddr.hpp
#pragma once#include \"Common.hpp\"class InetAddr{public: InetAddr() { } InetAddr(struct sockaddr_in &addr) { SetAddr(addr); } InetAddr(const std::string &ip,uint16_t port) :_ip(ip) ,_port(port) { //主机转网络 memset(&_addr,0,sizeof(_addr)); _addr.sin_family=AF_INET; inet_pton(AF_INET,_ip.c_str(),&_addr.sin_addr); _addr.sin_port=htons(_port); } InetAddr(uint16_t port) :_port(port),_ip(\"0\") { //端口转 memset(&_addr,0,sizeof(_addr)); _addr.sin_family=AF_INET; _addr.sin_addr.s_addr=INADDR_ANY; _addr.sin_port = htons(_port); } uint16_t Port() { return _port; } std::string Ip() { return _ip; } void SetAddr(struct sockaddr_in &addr) { //网络转主机 _addr=addr; _port = ntohs(addr.sin_port); //_ip = inet_ntoa(addr.sin_addr); char ipbuffer[64]; inet_ntop(AF_INET,&_addr.sin_addr,ipbuffer,sizeof(_addr)); _ip=ipbuffer; } const struct sockaddr_in &NetAddr() { return _addr; } const struct sockaddr *NetAddrPtr() { return CONV(_addr); } socklen_t NetAddrLen() { return sizeof(_addr); } bool operator==(const InetAddr &addr) { return addr._ip == _ip && addr._port == _port; } std::string StringAddr() { return _ip + \":\" + std::to_string(_port); } ~InetAddr() {}private: struct sockaddr_in _addr; std::string _ip; uint16_t _port;};
Log.hpp
#ifndef __LOG_HPP__#define __LOG_HPP__#include #include #include \"Mutex.hpp\"#include #include #include #include #include #includenamespace LogModule{ const std::string sep = \"\\r\\n\"; using namespace MutexModule ; // 2.刷新策略 class LogStrategy { public: ~LogStrategy() = default; virtual void SyncLog(const std::string &message) = 0; }; // 显示器刷新日志的策略 class ConsoleLogStrategy : public LogStrategy { public: ConsoleLogStrategy() {} ~ConsoleLogStrategy() {} void SyncLog(const std::string &message) override { LockGuard lockguard(_mutex); std::cout << message << sep; } private: Mutex _mutex; }; // 缺省文件路径以及文件本身 const std::string defaultpath = \"./log\"; const std::string defaultfile = \"my.log\"; // 文件刷新日志的策略 class FileLogStrategy : public LogStrategy { public: FileLogStrategy(const std::string &path = defaultpath, const std::string &file = defaultfile) : _path(path), _file(file) { LockGuard lockguard(_mutex); if (std::filesystem::exists(_path)) // 判断路径是否存在 { return; } try { std::filesystem::create_directories(_path); } catch (const std::filesystem::filesystem_error &e) { std::cerr << e.what() << \'\\n\'; } } void SyncLog(const std::string &message) override { LockGuard lockguard(_mutex); std::string filename = _path + (_path.back() == \'/\' ? \"\" : \"/\") + _file; std::ofstream out(filename, std::ios::app); // 追加写入 if (!out.is_open()) { return; } out << message << sep; out.close(); } ~FileLogStrategy() {} private: Mutex _mutex; std::string _path; // 日志文件的路径 std::string _file; // 要打印的日志文件 }; // 形成日志等级 enum class Loglevel { DEBUG, INIF, WARNING, ERROR, FATAL }; std::string Level2Str(Loglevel level) { switch (level) { case Loglevel::DEBUG: return \"DEBUG\"; case Loglevel::INIF: return \"INIF\"; case Loglevel::WARNING: return \"WARNING\"; case Loglevel::ERROR: return \"ERROR\"; case Loglevel::FATAL: return \"FATAL\"; default: return \"UNKNOWN\"; } } std::string GetTimeStamp() { time_t cuur =time(nullptr); struct tm curr_tm; localtime_r(&cuur,&curr_tm); char buffer[128]; snprintf(buffer,sizeof(buffer),\"%4d-%02d-%02d %02d:%02d:%02d\", curr_tm.tm_year+1900, curr_tm.tm_mon+1, curr_tm.tm_mday, curr_tm.tm_hour, curr_tm.tm_min, curr_tm.tm_sec ); return buffer; } class Logger { public: Logger() { EnableConsoleLogStrategy(); } // 选择某种策略 // 1.文件 void EnableFileLogStrategy() { _ffush_strategy = std::make_unique(); } // 显示器 void EnableConsoleLogStrategy() { _ffush_strategy = std::make_unique(); } // 表示的是未来的一条日志 class LogMessage { public: LogMessage(Loglevel &level, std::string &src_name, int line_number, Logger &logger) : _curr_time(GetTimeStamp()) , _level(level) , _pid(getpid()) , _src_name(src_name) , _line_number(line_number) , _logger(logger) { // 合并左半部分 std::stringstream ss; ss << \"[\" << _curr_time << \"] \" << \"[\" << Level2Str(_level) << \"] \" << \"[\" << _pid << \"] \" << \"[\" << _src_name << \"] \" << \"[\" << _line_number << \"] \" << \"- \"; _loginfo = ss.str(); } template LogMessage &operator<<(const T &info) { // 右半部分,可变 std::stringstream ss; ss <SyncLog(_loginfo); } } private: std::string _curr_time; // 日志时间 Loglevel _level; // 日志状态 pid_t _pid; // 进程pid std::string _src_name; // 文件名称 int _line_number; // 对应的行号 std::string _loginfo; // 合并之后的一条完整信息 Logger &_logger; }; LogMessage operator()(Loglevel level, std::string src_name, int line_number) { return LogMessage(level, src_name, line_number, *this); } ~Logger() {} private: std::unique_ptr _ffush_strategy; }; //全局日志对象 Logger logger; //使用宏,简化用户操作,获取文件名和行号 // __FILE__ 一个宏,替换完成后目标文件的文件名 // __LINE__ 一个宏,替换完成后目标文件对应的行号 #define LOG(level) logger(level,__FILE__,__LINE__) #define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy() #define Enable_File_Log_Strategy() logger.EnableFileLogStrategy() }#endif
Main.cc
#include\"Http.hpp\"// http portint main(int argc,char* argv[]){ if(argc != 2) { std::cout << \"Usage: \"<<argv[0]<<\" port\"<<std::endl; exit(USAGE_ERR); } uint16_t port = std::stoi(argv[1]); std::unique_ptr httpsvr = std::make_unique(port); httpsvr->Start(); return 0;}
Mutex.hpp
#pragma once#include #include namespace MutexModule{ class Mutex { public: Mutex() { pthread_mutex_init(&_mutex, nullptr); } void Lock() { int n = pthread_mutex_lock(&_mutex); (void)n; } void Unlock() { int n = pthread_mutex_unlock(&_mutex); (void)n; } ~Mutex() { pthread_mutex_destroy(&_mutex); } pthread_mutex_t *get() { return &_mutex; } private: pthread_mutex_t _mutex; }; class LockGuard { public: LockGuard(Mutex &mutex):_mutex(mutex) { _mutex.Lock(); } ~LockGuard() { _mutex.Unlock(); } private: Mutex &_mutex; };}
Socket.hpp
#pragma once#include #include #include #include #include #include #include #include #include \"Log.hpp\"#include \"Common.hpp\"#include \"InetAddr.hpp\"namespace SocketModule{ using namespace LogModule; const static int gbacklog =16; const static int defaultfd =-1; // 基类socket class Socket { public: virtual ~Socket() {} virtual void SocketOrDie() = 0; virtual void BindOrDie(uint16_t port) = 0; virtual void ListenOrDie(int backlog) = 0; virtual std::shared_ptr Accept(InetAddr * client)= 0; virtual void Close()=0; virtual int Recv(std::string * out) = 0; virtual int Send(std::string &message) = 0; virtual int Connect(const std::string &server_ip ,uint16_t port) =0; public: void BuildTcpClientSocketMethod() { SocketOrDie(); } void BUildTcpLIstenSocketMethod(uint16_t port,int backlog = gbacklog) { SocketOrDie(); BindOrDie(port); ListenOrDie(backlog); } // void BUildUdpSocketMethod() // { // SocketOrDie(); // BindOrDie(); // } }; class TcpSocket : public Socket { public: TcpSocket() :_sockfd(defaultfd) {} TcpSocket(int fd): _sockfd(fd) {} ~TcpSocket() {} void SocketOrDie() override { _sockfd = ::socket(AF_INET, SOCK_STREAM, 0); if (_sockfd < 0) { LOG(Loglevel::FATAL) << \"创建套接字失败!\"; exit(SOCKET_ERR); } LOG(Loglevel::INIF) << \"创建套接字成功!\"; } void BindOrDie(uint16_t port) override { InetAddr localaddr(port); int n = ::bind(_sockfd, localaddr.NetAddrPtr(), localaddr.NetAddrLen()); if (n < 0) { LOG(Loglevel::FATAL) << \"绑定失败!\"; exit(BIND_ERR); } LOG(Loglevel::INIF) << \"绑定成功!\"; } void ListenOrDie(int backlog) override { int n = ::listen(_sockfd, backlog); if (n < 0) { LOG(Loglevel::FATAL) << \"监听失败!\"; exit(LISTEN_ERR); } LOG(Loglevel::INIF) << \"监听成功!\"; } std::shared_ptr Accept(InetAddr * client) override { struct sockaddr_in peer; socklen_t len = sizeof(peer); int fd =::accept(_sockfd,CONV(peer),&len); if (fd < 0) { LOG(Loglevel::WARNING)<<\"连接失败!\"; exit(-1); } LOG(Loglevel::WARNING)<SetAddr(peer); return std::make_shared(fd); } void Close() override { if(_sockfd >=0) { ::close(_sockfd); } } int Recv(std::string * out) override { //流式读取,不关心读到的是什么 char buffer[4096*2]; ssize_t n =::recv(_sockfd,buffer,sizeof(buffer)-1,0); if (n >0) { buffer[n]=0; *out+=buffer; return n; } return n; } int Send(std::string &message) override { return send(_sockfd,message.c_str(),message.size(),0); } int Connect(const std::string &server_ip ,uint16_t port) override { InetAddr server(server_ip,port); return ::connect(_sockfd,server.NetAddrPtr(),server.NetAddrLen()) ; } private: int _sockfd; // }; // class UdpSocket : public Socket // { // };}
TcpServer.hpp
#include \"Socket.hpp\"#include#include#include #include using namespace LogModule;using namespace SocketModule;using ioservice_t = std::function<void(std::shared_ptr&sock,InetAddr &client)>;class TcpServer{public:TcpServer(uint16_t port):_port(port),_listensockptr(std::make_unique()),_isrunning(false){ _listensockptr->BUildTcpLIstenSocketMethod(_port);}void Start(ioservice_t callback){ _isrunning =true; while(_isrunning) { InetAddr client; auto sock = _listensockptr->Accept(&client); if(sock == nullptr) { continue; } LOG(Loglevel::DEBUG)<<\"accept is running\"<<client.StringAddr(); //sock && client pid_t id =fork(); if (id < 0) { LOG(Loglevel::FATAL)<Close(); if(fork()>0) { exit(0); } //孙子进程是孤儿进程 callback(sock,client); exit(OK); } else { //父进程 关闭sock sock->Close(); pid_t rid = ::waitpid(id,nullptr,0); (void)rid; } } _isrunning =false;}~TcpServer(){}private:uint16_t _port;std::unique_ptr_listensockptr;bool _isrunning;};
Util.hpp
#pragma once#include #include #include class Util{ public: //打开指定文件static bool ReadFileContent(const std::string &filename,std::string *out) { //version1 默认文本方式读取 // std::ifstream in(filename); // if(!in.is_open()) // { // return false; // } // std::string line; // while(std::getline(in,line)) // { // *out+=line; // } // in.close(); //version2 二进制读取 图片是二进制 int filesize = FileSize(filename); if(filesize > 0) { std::ifstream in(filename); if(!in.is_open()) { return false; } out->resize(filesize); in.read((char *)(out->c_str()),filesize); in.close(); } else{ return false; } return true; } static bool ReadOneLine(std::string &bigstr,std::string *out,const std::string &sep) { auto pos = bigstr.find(sep); if(pos == std::string::npos) { return false; } *out = bigstr.substr(0,pos); bigstr.erase(0,pos+sep.size()); return true; }static int FileSize(const std::string &filename){ std::ifstream in(filename,std::ios::binary); if(!in.is_open()) return -1; in.seekg(0,in.end); int filesize = in.tellg(); in.seekg(0,in.beg); in.close(); return filesize;}};
Makefile
myhttp:Main.ccg++ -o $@ $^ -std=c++17.PHONY:cleanclean:rm -f myhttp
游览器首页效果
查看首页
查看图片
查看不存在资源
其他前端效果不展示,主要学习http相关知识