【仿Mudou库one thread per loop式并发服务器实现】HTTP协议模块实现
HTTP协议模块实现
- 1. Util模块
- 2. HttpRequest模块
- 3. HttpResponse模块
- 4. HttpContext模块
- 5. HttpServer模块
1. Util模块
这个模块是一个工具模块,主要提供HTTP协议模块所用到的一些工具函数,比如url编解码,文件读写…等。
#include "server.hpp"
#include <fstream>
#include <sys/stat.h>
#include <regex>static std::unordered_map<int,std::string> _status_msg = {{100, "Continue"},{101, "Switching Protocol"},{102, "Processing"},{103, "Early Hints"},{200, "OK"},{201, "Created"},{202, "Accepted"},{203, "Non-Authoritative Information"},{204, "No Content"},{205, "Reset Content"},{206, "Partial Content"},{207, "Multi-Status"},{208, "Already Reported"},{226, "IM Used"},{300, "Multiple Choice"},{301, "Moved Permanently"},{302, "Found"},{303, "See Other"},{304, "Not Modified"},{305, "Use Proxy"},{306, "unused"},{307, "Temporary Redirect"},{308, "Permanent Redirect"},{400, "Bad Request"},{401, "Unauthorized"},{402, "Payment Required"},{403, "Forbidden"},{404, "Not Found"},{405, "Method Not Allowed"},{406, "Not Acceptable"},{407, "Proxy Authentication Required"},{408, "Request Timeout"},{409, "Conflict"},{410, "Gone"},{411, "Length Required"},{412, "Precondition Failed"},{413, "Payload Too Large"},{414, "URI Too Long"},{415, "Unsupported Media Type"},{416, "Range Not Satisfiable"},{417, "Expectation Failed"},{418, "I'm a teapot"},{421, "Misdirected Request"},{422, "Unprocessable Entity"},{423, "Locked"},{424, "Failed Dependency"},{425, "Too Early"},{426, "Upgrade Required"},{428, "Precondition Required"},{429, "Too Many Requests"},{431, "Request Header Fields Too Large"},{451, "Unavailable For Legal Reasons"},{501, "Not Implemented"},{502, "Bad Gateway"},{503, "Service Unavailable"},{504, "Gateway Timeout"},{505, "HTTP Version Not Supported"},{506, "Variant Also Negotiates"},{507, "Insufficient Storage"},{508, "Loop Detected"},{510, "Not Extended"},{511, "Network Authentication Required"}
};static std::unordered_map<std::string,std::string> _mine_msg = {{".aac", "audio/aac"},{".abw", "application/x-abiword"},{".arc", "application/x-freearc"},{".avi", "video/x-msvideo"},{".azw", "application/vnd.amazon.ebook"},{".bin", "application/octet-stream"},{".bmp", "image/bmp"},{".bz", "application/x-bzip"},{".bz2", "application/x-bzip2"},{".csh", "application/x-csh"},{".css", "text/css"},{".csv", "text/csv"},{".doc", "application/msword"},{".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"},{".eot", "application/vnd.ms-fontobject"},{".epub", "application/epub+zip"},{".gif", "image/gif"},{".htm", "text/html"},{".html", "text/html"},{".ico", "image/vnd.microsoft.icon"},{".ics", "text/calendar"},{".jar", "application/java-archive"},{".jpeg", "image/jpeg"},{".jpg", "image/jpeg"},{".js", "text/javascript"},{".json", "application/json"},{".jsonld", "application/ld+json"},{".mid", "audio/midi"},{".midi", "audio/x-midi"},{".mjs", "text/javascript"},{".mp3", "audio/mpeg"},{".mpeg", "video/mpeg"},{".mpkg", "application/vnd.apple.installer+xml"},{".odp", "application/vnd.oasis.opendocument.presentation"},{".ods", "application/vnd.oasis.opendocument.spreadsheet"},{".odt", "application/vnd.oasis.opendocument.text"},{".oga", "audio/ogg"},{".ogv", "video/ogg"},{".ogx", "application/ogg"},{".otf", "font/otf"},{".png", "image/png"},{".pdf", "application/pdf"},{".ppt", "application/vnd.ms-powerpoint"},{".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"},{".rar", "application/x-rar-compressed"},{".rtf", "application/rtf"},{".sh", "application/x-sh"},{".svg", "image/svg+xml"},{".swf", "application/x-shockwave-flash"},{".tar", "application/x-tar"},{".tif", "image/tiff"},{".tiff", "image/tiff"},{".ttf", "font/ttf"},{".txt", "text/plain"},{".vsd", "application/vnd.visio"},{".wav", "audio/wav"},{".weba", "audio/webm"},{".webm", "video/webm"},{".webp", "image/webp"},{".woff", "font/woff"},{".woff2", "font/woff2"},{".xhtml", "application/xhtml+xml"},{".xls", "application/vnd.ms-excel"},{".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},{".xml", "application/xml"},{".xul", "application/vnd.mozilla.xul+xml"},{".zip", "application/zip"},{".3gp", "video/3gpp"},{".3g2", "video/3gpp2"},{".7z", "application/x-7z-compressed"},
};class Until
{public://字符串分割函数,将src字符串按照sep字符进⾏分割,得到的各个字串放到arry中,最终返回字串的数量static int Split(const std::string& src,const std::string& sep,std::vector<std::string>* array){size_t offset = 0;// 有10个字符,offset是查找的起始位置,范围应该是0~9,offset==10就代表已经越界了while(offset < src.size()){size_t pos = src.find(sep,offset);if(pos == std::string::npos)//没有找到特定的字符{//将剩余的部分当作⼀个字串,放⼊arry中array->push_back(src.substr(offset));return array->size();}//abc....deif(pos == offset){offset = pos + sep.size();continue;//当前字串是⼀个空的,没有内容}array->push_back(src.substr(offset,pos - offset));offset = pos + sep.size();}return array->size();}//读取⽂件的所有内容,将读取的内容放到⼀个Buffer中static bool ReadFile(const std::string& filename,std::string* buf){std::ifstream ifs(filename,std::ios::binary);if(ifs.is_open() == false){LOG_FATAL("OPEN %s FILE FAILED",filename.c_str());return false;}size_t fsize = 0;ifs.seekg(0,ifs.end);//跳转读写位置到末尾fsize = ifs.tellg();//获取当前读写位置相对于起始位置的偏移量,从末尾偏移刚好就是⽂件大小ifs.seekg(0,ifs.beg);//跳转到起始位置buf->resize(fsize);//开辟文件大小的空间ifs.read(&(*buf)[0],fsize);if(ifs.good() == false){LOG_FATAL("READ %s FILE FAILED",filename.c_str());ifs.close();return false;}ifs.close();return true;}//向文件写入数据static bool WriteFile(const std::string filename,const std::string& in){std::ofstream ofs(filename,std::ios::binary | std::ios::trunc);if(ofs.is_open() == false){LOG_FATAL("OPEN %s FILE FAILED",filename.c_str());return false;}ofs.write(in.c_str(),in.size());if(ofs.good() == false){LOG_FATAL("Write %s FILE FAILED",filename.c_str());ofs.close();return false;}ofs.close();return true;}//URL编码,避免URL中资源路径与查询字符串中的特殊字符与HTTP请求中特殊字符产⽣歧义//编码格式:将特殊字符的ascii值,转换为两个16进制字符,前缀%, C++ -> C%2B%2B//不编码的特殊字符: RFC3986⽂档规定 . - _ ~ 字⺟,数字属于绝对不编码字符//RFC3986⽂档规定,编码格式 %HH //W3C标准中规定,查询字符串中的空格,需要编码为+, 解码则是+转空格static std::string UrlEncode(const std::string& url,bool convert_space_to_plus){std::string res;for(auto& c : url){if(c == '.' || c == '-' || c == '_' || c == '~' || isalnum(c)){res += 'c';continue;}if(c == ' ' && convert_space_to_plus){res += '+';continue;}//剩下的字符都是需要编码成为 %HH 格式char tmp[4] = {0};//snprintf 与 printf⽐较类似,都是格式化字符串,只不过⼀个是打印,⼀个是放到⼀块空间中snprintf(tmp,4,"%%%02X",c);res += tmp;}return res;}static char HEXTOI(char c){if(c > '0' && c < '9')return c - '0';if(c > 'A' && c < 'Z')return c - 'A' + 10;if(c > 'a' && c < 'z')return c - 'a' + 10;return -1;}//URL解码static std::string UrlDecode(const std::string& url,bool convert_space_to_plus){//遇到了%,则将紧随其后的2个字符,转换为数字,第⼀个数字左移4位,然后加上第二个数字 + -> 2b %2b->2 << 4 + 11std::string res;for(int i = 0; i < url.size(); ++i){if(url[i] == '+' && convert_space_to_plus){res += ' ';continue;}if(url[i] == '%' && i + 2 < url.size()){//字符是以整数形成存储的char v1 = HEXTOI(url[i + 1]) << 4;char v2 = HEXTOI(url[i + 2]);char c = v1 + v2;res += c;i += 2;continue;}res += url[i];}return res;}//获取响应状态码的描述信息static std::string StatusDesc(int statu){auto it = _status_msg.find(statu);if(it != _status_msg.end()){return it->second;}return "Unknow";}//根据文件后缀名获取文件mimestatic std::string ExMime(const std::string& filename){ // a.b.txt 先获取⽂件扩展名size_t pos = filename.rfind('.');if(pos == std::string::npos){return "application/octet-stream";}//根据扩展名,获取mimstd::string ext = filename.substr(pos);auto it = _mine_msg.find(ext);if(it != _mine_msg.end()){return it->second;}return "application/octet-stream";}//判断一个文件是否是一个目录static bool IsDirectory(const std::string& filename){struct stat st;int ret = stat(filename.c_str(),&st);if(ret < 0){return false;}return S_ISDIR(st.st_mode);}//判断一个文件是否是一个普通文件static bool IsRegular(const std::string& filename){struct stat st;int ret = stat(filename.c_str(),&st);if(ret < 0){return false;}return S_ISREG(st.st_mode);}//http请求的资源路径有效性判断// /index.html --- 前边的/叫做相对根目录 映射的是某个服务器上的⼦目录// 想表达的意思就是,客⼾端只能请求相对根⽬录中的资源,其他地⽅的资源都不予理会// /../login, 这个路径中的..会让路径的查找跑到相对根⽬录之外,这是不合理的,不安全的static bool ValidPath(const std::string& path){//思想:按照/进⾏路径分割,根据有多少⼦目录,计算目录深度,有多少层,深度不能⼩于0std::vector<std::string> res;Split(path,"/",&res);int level = 0;for(auto& s : res){if(s == ".."){--level;//任意⼀层⾛出相对根目录,就认为有问题if(level < 0){return false;}continue;}++level;}return true;}
};
2. HttpRequest模块
这个模块是HTTP请求数据模块,用于保存HTTP请求数据被解析后的各项请求元素信息。
HttpRequest模块:
http请求信息模块:存储HTTP请求信息要素,提供简单的功能性接口
请求信息要素:
请求行:请求方法,URL,协议版本
URL:资源路径,查询字符串
GET /search/1234?word=C++&en=utf8 HTTP/1.1
请求头部:key: value\r\nkey: value\r\n…
Content-Length: 0\r\n
正文
要素:请求方法,资源路径,查询字符串,头部字段,正文,协议版本
std:smatch保存首行使用regex正则进行解析后,所提取的数据,比如提取资源路径中的数字…
功能性接口:
- 将成员变量设置为公有成员,便于直接访问
- 提供查询字符串,以及头部字段的单个查询和获取,插入功能
- 获取正文长度
- 判断长连接&短链接Connection:close/keep-alive
//HttpRequest模块,存储Http请求信息要素,提供简单的功能性接口
class HttpRequest
{public:std::string _method;//请求方法std::string _path;//资源路径std::string _version;//协议版本std::string _body;//请求正文std::smatch _matches;//资源路径的正则提取数据std::unordered_map<std::string,std::string> _headers;//头部字段std::unordered_map<std::string,std::string> _params;//查询字符串public:HttpRequest():_version("Http/1.1"){}void ReSet(){_method.clear();_path.clear();_version = "Http/1.1";_body.clear();std::smatch newmatches;_matches.swap(newmatches);_headers.clear();_params.clear();}//插入头部字段void SetHeader(const std::string& key,const std::string& val){_headers.insert({key,val});}//判断是否存在指定头部字段bool HasHeader(const std::string& key) const{auto it = _headers.find(key);if(it == _headers.end()){return false;}return true;}//获取指定头部字段的值std::string GetHeader(const std::string& key) const{auto it = _headers.find(key);if(it == _headers.end()){return "";}return it->second;}//插入查询字符串void SetParam(const std::string& key,const std::string& val){_params.insert({key,val});}//判断是否有某个指定的查询字符串bool HasParam(const std::string& key){auto it = _params.find(key);if(it == _params.end()){return false;}return true;}//获取指定的查询字符串std::string GetParamr(const std::string& key){auto it = _params.find(key);if(it == _params.end()){return "";}return it->second;}//获取正文长度size_t ContentLength(){// Content-Length: 1234\r\nbool ret = HasHeader("Content-Length");if(ret == false){return 0;}return std::stol(GetHeader("Content-Length"));}//判断是否是短连接bool Close() const{// 没有Connection字段,或者有Connection但是值是close,则都是短链接,否则就是长连接if(HasHeader("Connection") == true && GetHeader("Connection") == "keep-alive"){return false;}return true;}
};
3. HttpResponse模块
这个模块是HTTP响应数据模块,用于业务处理后设置并保存HTTP响应数据的的各项元素信息,最终会被按照HTTP协议响应格式组织成为响应信息发送给客户端。
HttpResponse模块:
功能:存储HTTP响应信息要素,提供简单的功能性接口
响应信息要素:
- 响应状态码
- 头部字段
- 响应正文
- 重定向信息(是否进行了重定向的标志,重定向的路径)
功能性接口:
- 为了便于成员的访问,因此将成员设置为公有成员
- 头部字段的新增,查询,获取
- 正文的设置
- 重定向的设置
- 长短连接的判断
//HttpResponse模块,存储Http响应信息要素,提供简单的功能性接口
class HttpResponse
{public:int _status;//响应码bool _redirect_flag;//是否重定向std::string _body;//正文std::string _redirect_url;//重定向urlstd::unordered_map<std::string,std::string> _headers;//头部字段public:HttpResponse():_status(200),_redirect_flag(false){}HttpResponse(int status):_status(status),_redirect_flag(false){}void ReSet() {_status = 200;_redirect_flag = false;_body.clear();_redirect_url.clear();_headers.clear();}//插入头部字段void SetHeader(const std::string& key,const std::string& val){_headers.insert({key,val});}//判断是否存在指定头部字段bool HasHeader(const std::string& key){auto it = _headers.find(key);if(it == _headers.end()){return false;}return true;}//获取指定头部字段的值std::string GetHeader(const std::string& key){auto it = _headers.find(key);if(it == _headers.end()){return "";}return it->second;}//设置正文以及正文类型void SetContent(const std::string& body,const std::string& type = "text/html"){_body = body;SetHeader("Content-Type",type);}//设置重定向以及重定向状态码void SetRedirect(const std::string& url,int status = 302){_redirect_flag = true;_redirect_url = url;_status = status;}//判断是否是短连接bool Close(){// 没有Connection字段,或者有Connection但是值是close,则都是短链接,否则就是长连接if(HasHeader("Connection") == true && GetHeader("Connection") == "keep-alive"){return false;}return true;}
};
4. HttpContext模块
这个模块是一个HTTP请求接收的上下文模块,主要是为了防支在一次接收的数据中,不是一个完整的HTTP请求,则解析过程并未完成,无法进行完整的请求处理,需要在下次接收到新数据后继续根据上下文进行解析,最终得到一个HttpRequest请求信息对象,因此在请求数据的接收以及解析部分需要一个上下文来进行控制接收和处理节奏。
typedef enum{RECV_HTTP_ERROR,RECV_HTTP_LINE,RECV_HTTP_HEAD,RECV_HTTP_BODY,RECV_HTTP_OVER
}HttpRecvStatus;#define MAX_BYTE 8 * 1024//HttpContext请求接收上下文模块,记录HTTP请求的接收以及处理进度
class HttpContext
{private:int _resp_status;//响应状态码,解析请求出错时设置HttpRecvStatus _recv_status;//当前接收及解析的阶段状态HttpRequest _request;//已经解析得到的请求信息private://接收请求行bool RecvHttpLine(Buffer* buf){if (_recv_status != RECV_HTTP_LINE) return false;//1. 获取一行数据,带有末尾的换行 \n \r\nstd::string line = buf->GetLineAndPop();//2. 需要考虑的⼀些要素:缓冲区中的数据不足一行, 获取的一行数据超大if(line.size() == 0){//缓冲区中的数据不足一行,则需要判断缓冲区的可读数据⻓度,如果很长了都不足一行,这是有问题的if(buf->ReadAbleSize() > MAX_BYTE){_recv_status = RECV_HTTP_ERROR;_resp_status = 414 ;//"URI Too Long"return false;}//缓冲区中数据不足一行,但是也不多,就等等新数据的到来return true;}if(line.size() > MAX_BYTE){if(buf->ReadAbleSize() > MAX_BYTE){_recv_status = RECV_HTTP_ERROR;_resp_status = 414 ;//"URI Too Long"return false;}}bool ret = ParseHttpLine(line);if(ret == false){return false;}//首行处理完毕,进⼊头部获取阶段_recv_status = RECV_HTTP_HEAD;return true;}//解析请求行bool ParseHttpLine(const std::string& line){std::smatch matches;//std::regex::icase忽略大小写std::regex e("(GET|HEAD|POST|PUT|DELETE) ([^?]*)(?:\\?(.*))? (HTTP/1\\.[01])(?:\n|\r\n)?", std::regex::icase);bool ret = std::regex_match(line, matches, e);if (ret == false) {_recv_status = RECV_HTTP_ERROR;_resp_status = 400 ;//"Bad Request"return false;}//0 : GET /bitejiuyeke/login?user=xiaoming&pass=123123 HTTP/1.1//1 : GET//2 : /bitejiuyeke/login//3 : user=xiaoming&pass=123123//4 : HTTP/1.1//请求⽅法的获取_request._method = matches[1];//小写转成大写,为了下面HttpServer模块中用大写的请求方法进行判断时不会因为大小写出现不匹配的情况std::transform(_request._method.begin(), _request._method.end(), _request._method.begin(), ::toupper);//资源路径的获取,需要进⾏URL解码操作,但是不需要+转空格_request._path = Until::UrlDecode(matches[2],false);//协议版本的获取_request._version = matches[4];//查询字符串的获取与处理std::vector<std::string> query_string_arry;std::string query_string = matches[3];//查询字符串的格式 key=val&key=val....., 先以 & 符号进⾏分割,得到各个字串Until::Split(query_string,"&",&query_string_arry);//针对各个字串,以 = 符号进⾏分割,得到key 和val, 得到之后也需要进⾏URL解码for(auto& str : query_string_arry){size_t pos = str.find("=");if(pos == std::string::npos){_recv_status = RECV_HTTP_ERROR;_resp_status = 400 ;//"Bad Request"return false;}std::string key = Until::UrlDecode(str.substr(0,pos),true);std::string val = Until::UrlDecode(str.substr(pos + 1),true);_request._params.insert({key,val});}return true;}//接收请求头部bool RecvHttpHead(Buffer* buf){if(_recv_status != RECV_HTTP_HEAD) return false;//1. 一⾏一行取出数据,直到遇到空行为⽌, 头部的格式 key: val\r\nkey: val\r\n\r\nwhile(1){std::string line = buf->GetLineAndPop();//2. 需要考虑的⼀些要素:缓冲区中的数据不足一行, 获取的一行数据超大if(line.size() == 0){//缓冲区中的数据不足一行,则需要判断缓冲区的可读数据⻓度,如果很长了都不足一行,这是有问题的if(buf->ReadAbleSize() > MAX_BYTE){_recv_status = RECV_HTTP_ERROR;_resp_status = 414;//"URI Too Long"return false;}//缓冲区中数据不足一行,但是也不多,就等等新数据的到来return true;}if(line.size() > MAX_BYTE){_recv_status = RECV_HTTP_ERROR;_resp_status = 414;return false;}//遇到空行头部提取结束if(line == "\n" || line == "\r\n"){break;}bool ret = ParseHttpHead(line);if(ret == false){return false;}}//头部处理完毕,进入正文获取阶段_recv_status = RECV_HTTP_BODY;return true;}//解析请求头部bool ParseHttpHead(std::string& line){//key: val\r\n//末尾是换行则去掉换行字符if(line.back() == '\n') line.pop_back();//末尾是回⻋则去掉回⻋字符if(line.back() == '\r') line.pop_back();size_t pos = line.find(": ");if(pos == std::string::npos){_recv_status = RECV_HTTP_ERROR;_resp_status = 400;return false;}std::string key = line.substr(0,pos);std::string val = line.substr(pos + 2);_request.SetHeader(key,val);return true;}//接收请求正文bool RecvHttpBody(Buffer* buf){if(_recv_status != RECV_HTTP_BODY) return false;//1. 获取正文长度size_t content_length = _request.ContentLength();if(content_length == 0){//没有正⽂,则请求接收解析完毕_recv_status = RECV_HTTP_OVER;return true;}//2. 当前已经接收了多少正文,其实就是往 _request._body 中放了多少数据了size_t real_len = content_length - _request._body.size();//实际还需要接收的正⽂长度//3. 接收正文放到body中,但是也要考虑当前缓冲区中的数据,是否是全部的正⽂// 3.1 缓冲区中数据,包含了当前请求的所有正文,则取出所需的数据if(buf->ReadAbleSize() >= real_len){_request._body.append(buf->ReadPosition(),real_len);buf->MoveReadOffset(real_len);_recv_status = RECV_HTTP_OVER;return true;}// 3.2 缓冲区中数据,⽆法满⾜当前正文的需要,数据不足,取出数据,后续等待新数据到来_request._body.append(buf->ReadPosition(),buf->ReadAbleSize());buf->MoveReadOffset(buf->ReadAbleSize());return true;}public:HttpContext():_recv_status(RECV_HTTP_LINE),_resp_status(200){}void ReSet(){_recv_status = RECV_HTTP_LINE;_resp_status = 200;_request.ReSet();}//接收解析遇到错误时返回对应的错误码int RespStatus(){return _resp_status;}//当前请求接收解析到那个阶段HttpRecvStatus RecvStatus(){return _recv_status;}//返回请求解析后的HttpRequest对象HttpRequest &Request() { return _request; }//接收并解析HTTP请求void RecvHttpRequest(Buffer* buf){//不同的状态,做不同的事情,但是这⾥不要break, 因为处理完请求⾏后,应该⽴即处理头部,⽽不是退出等新数据switch(_recv_status){case RECV_HTTP_LINE: RecvHttpLine(buf);case RECV_HTTP_HEAD: RecvHttpHead(buf);case RECV_HTTP_BODY: RecvHttpBody(buf);}return;}};
5. HttpServer模块
这个模块是最终给组件使用者提供的HTTP服务器模块了,用于以简单的接口实现HTTP服务器的搭建。
HttpServer模块内部包含有一个TcpServer对象:TcpServer对象实现服务器的搭建
HttpServer模块内部包含有两个提供给TcpServer对象的接口:连接建立成功设置上下文接口,数据处理接口。
HttpServer模块内部包含有一个hash-map表存储请求与处理函数的映射表:组件使用者向HttpServer设置哪些请求应该使用哪些函数进行处理,等TcpServer收到对应的请求就会使用对应的函数进行处理。
HttpServer模块:用于实现Http服务器的搭建
首先要给不同的请求方法的请求路径设置对应的回调函数。也就是设计一张请求路由表。
设计一张请求路由表:
表中记录了针对哪个请求,应该使用哪个函数来进行业务处理的映射关系。
当服务器收到了一个请求,就在请求路由表中,查找有没有对应请求的处理函数,如果有,则执行对应的处理函数即可。说白了,什么请求,怎么处理,由用户来设定,服务器收到了请求只需要执行函数即可。
这样做的好处:用户只需要实现业务处理函数,然后将请求与处理函数的映射关系,添加到服务器中。
而服务器只需要接收数据,解析数据,查找路由表映射关系,执行业务处理函数。
要实现简便的搭建HTTP服务器,所需要的要素和提供的功能。
要素:
- GET请求的路由映射表
- POST请求的路由映射表
- PUT请求的路由映射表
- DELETE请求的路由映射表—路由映射表记录对应请求方法的请求资源路径与对应业务处理函数映射关系–更多是功能性请求的处理
- 静态资源相对根目录-实现静态资源请求的处理
- 高性能TCP服务器—进行连接的IO操作
公有接口:
- 添加请求—处理函数映射信息(GET/POST/PUT/DELETE)
- 设置静态资源根目录
- 设置是否启动超时连接关闭
- 设置线程池中线程数量
- 启动服务器
私有接口:
- OnConnected—用于给TcpServer设置协议上下文
- OnMessage----用于进行缓冲区数据解析处理
- 请求的路由查找
- 静态资源请求查找和处理
- 功能性请求的查找和处理
- 组织响应进行回复
服务器处理流程:
- 从socket接收数据,放到接收缓冲区
- 调用OnMessage回调函数进行业务处理
- 对请求进行解析,得到了一个HttpRequest结构,包含了所有的请求要素
- 进行请求的路由查找-找到对应请求的处理方法
a. 静态资源请求–一些实体文件资源的请求,html,image.…
将静态资源文件的数据读取出来,填充到HttpResponse结构中
b. 功能性请求—在请求路由映射表中查找处理函数,找到了则执行函数
具体的业务处理,并进行HttpResponse结构的数据填充 - 对静态资源请求/功能性请求进行处理完毕后,得到了一个填充了响应信息的HttpResponse对象,组织http格式响应,进行发送。
//HttpServer模块,用于实现HttpServer服务器的搭建
const std::string html_404 = "/404.html";
const std::string home_page = "index.html";
#define DEFALT_TIMEOUT 10
class HttpServer
{private://请求路由表using Handler = std::function<void(const HttpRequest&,HttpResponse*)>;//请求资源路径我们用正则表达式//比如 /number/1、/number/2、/number/3 这样的资源路径//对应的业务处理函数都是一样的。如果一个路径给没有必要。//因此请求路径用正则表达式,比如number/d+ 这个匹配上面三个//正则表达式可以用来判断字符串中有没有某个子串using Handlers = std::vector<std::pair<std::regex,Handler>>;Handlers _get_route;Handlers _post_route;Handlers _put_route;Handlers _delete_route;std::string _basedir;//静态资源根⽬录TcpServer _server;private:void ErrHandler(const HttpRequest& req,HttpResponse* rsp){//1. 组织⼀个错误展示页⾯std::string body;body += "<html>";body += "<head>";body += "<meta http-equiv='Content-Type' content='text/html;charset=utf-8'>";body += "</head>";body += "<body>";body += "<h1>";body += std::to_string(rsp->_status);body += " ";body += Until::StatusDesc(rsp->_status);body += "</h1>";body += "</body>";body += "</html>";//2. 将页⾯数据,当作响应正⽂,放⼊rsp中rsp->SetContent(body, "text/html"); }//将HttpResponse中的要素按照http协议格式进行组织,发送void WriteResponse(const PtrConnection& conn,const HttpRequest& req,HttpResponse* rsp){//1. 先完善头部字段if(req.Close() == true){rsp->SetHeader("Connection","close"); }else{rsp->SetHeader("Connection","keep-alive"); }if(!rsp->_body.empty() && rsp->HasHeader("Content-Length") == false){rsp->SetHeader("Content-Length",std::to_string(rsp->_body.size()));}if(!rsp->_body.empty() && rsp->HasHeader("Content-Typy") == false){rsp->SetHeader("Content-Type","application/octet-stream");}if(rsp->_redirect_flag == true){rsp->SetHeader("Location",rsp->_redirect_url);}//2. 将rsp中的要素,按照http协议格式进⾏组织std::stringstream rsp_string;rsp_string << req._version << " " << std::to_string(rsp->_status) << " " << Until::StatusDesc(rsp->_status) << "\r\n";for(auto& header : rsp->_headers){rsp_string << header.first << ": " << header.second << "\r\n";}rsp_string << "\r\n";rsp_string << rsp->_body;//3. 发送数据conn->Send(rsp_string.str().c_str(),rsp_string.str().size());}bool IsFileHandler(const HttpRequest& req){// 1. 必须设置了静态资源根目录if(_basedir.empty()){return false;}// 2. 请求⽅法,必须是GET / HEAD请求⽅法if(req._method != "GET" && req._method != "HEAD"){return false;}// 3. 请求的资源路径必须是一个合法路径if(Until::ValidPath(req._path) == false){return false;}// 4. 请求的资源必须存在,且是⼀个普通⽂件// 有⼀种请求⽐较特殊 -- ⽬录:/, 这种情况给后边默认追加⼀个index.html// /index.html /image/a.png// 不要忘了前缀的相对根⽬录,也就是将请求路径转换为实际存在的路径 /image/a.png -> ./wwwroot/image/a.pngstd::string req_path = _basedir + req._path;//为了避免直接修改请求的资源路径,因此定义⼀个临时对象if(req._path.back() == '/'){//默认首页req_path += home_page;}if (Until::IsRegular(req_path) == false) {return false;}return true;}//静态资源的请求处理 --- 将静态资源⽂件的数据读取出来,放到rsp的_body中, 并设置mimevoid FileHandler(const HttpRequest& req,HttpResponse* rsp){std::string req_path = _basedir + req._path;if(req._path.back() == '/'){//默认首页req_path += home_page;}bool ret = Until::ReadFile(req_path,&rsp->_body);if(ret == false)//进来这里已经判断过资源肯定是存在的,否则就不会进入静态资源的处理{return;}std::string mime = Until::ExMime(req_path);rsp->SetHeader("Content-Type",mime);return;}//功能性请求的的分类处理void Dispatcher(HttpRequest& req,HttpResponse* rsp,const Handlers& handlers){//在对应请求⽅法的路由表中,查找是否含有对应资源的对应请求的处理函数,有则调⽤,没有则返回404//思想:路由表存储的时键值对 -- 正则表达式 & 处理函数//使⽤正则表达式,对请求的资源路径进⾏正则匹配,匹配成功就使⽤对应函数进⾏处理// /numbers/(\d+) /numbers/12345for(auto& handler : handlers){const std::regex& re = handler.first;const Handler& functor = handler.second;bool ret = std::regex_match(req._path,req._matches,re);if(ret == false){continue;}//传⼊请求信息,和空的rsp,执⾏处理函数return functor(req,rsp);}//返回错误码404页面rsp->_status = 404;// std::string path = _basedir + html_404;// bool ret = Until::ReadFile(path,&rsp->_body);// if(ret == false)//错误路径是我们自己设置肯定是存在的// {// return;// }// return rsp->SetHeader("Content-Type","text/html");}//路由选择void Route(HttpRequest& req,HttpResponse* rsp){//1. 对请求进⾏分辨,是⼀个静态资源请求,还是⼀个功能性请求// 静态资源请求,则进⾏静态资源的处理// 功能性请求,则需要通过⼏个请求路由表来确定是否有处理函数// 既不是静态资源请求,也没有设置对应的功能性请求处理函数,就返回405if(IsFileHandler(req)){//是⼀个静态资源请求, 则进⾏静态资源请求的处理return FileHandler(req,rsp);}if(req._method == "GET" || req._method == "HEAD"){return Dispatcher(req,rsp,_get_route);}else if(req._method == "POST"){return Dispatcher(req,rsp,_post_route);}else if(req._method == "PUT"){return Dispatcher(req,rsp,_put_route);}else if(req._method == "DELETE"){return Dispatcher(req,rsp,_delete_route);}rsp->_status = 405;// Method Not Allowedreturn;}//连接建立后给对应Connection对象设置一个协议上下文void OnConnected(const PtrConnection& conn){ conn->SetContent(HttpContext());}//缓存区数据解析+处理void OnMessage(const PtrConnection& conn,Buffer* buf){while(buf->ReadAbleSize() > 0){//1. 获取上下⽂//获取在连接建立好就给每个Connection设置HttpContext上下文HttpContext* context = conn->GetContent()->Get<HttpContext>();//2. 通过上下⽂对缓冲区数据进⾏解析,得到HttpRequest对象// 2.1 如果缓冲区的数据解析出错,就直接回复出错响应// 2.2 如果解析正常,且请求已经获取完毕,才开始去进⾏处理context->RecvHttpRequest(buf);HttpRequest& rep = context->Request();HttpResponse rsp(context->RespStatus());//if(context->RecvStatus() == RECV_HTTP_ERROR)if (context->RespStatus() >= 400){//进⾏错误响应,关闭连接//填充⼀个错误显⽰页⾯数据到rsp中ErrHandler(rep,&rsp);//组织响应发送给客户端WriteResponse(conn,rep,&rsp);//一定要做下面两步,不然出错了,关闭连接时,接收缓存区还有数据关闭连接的时候先去先处理接收缓存区数据//但是当前上下文状态一直是RECV_HTTP_ERROR,因此每次去接收缓存区根本拿不到数据,所有在这里死循环//造成内存资源不足,服务器奔溃退出//因此在这里把上下文状态重置RECV_HTTP_LINE可以每次都从接收缓存区拿到数据//直到最后接收缓存区数据不足一行,从下面退出,然后真正的去关闭连接context->ReSet();//这里也可以,出错了就把接收缓冲区数据清空,也就不会在多次调用了buf->MoveReadOffset(buf->ReadAbleSize());//关闭连接conn->Shutdown();return;}//当前请求还没有接收完整,则退出,等新数据到来再重新继续处理if(context->RecvStatus() != RECV_HTTP_OVER){return;}//3. 请求路由 + 业务处理Route(rep,&rsp);//4. 对HttpResponse进⾏组织发送WriteResponse(conn,rep,&rsp);//5. 重置上下⽂,避免影响下次解析context->ReSet();//6. 根据⻓短连接判断是否关闭连接或者继续处理if(rsp.Close() == true){//短链接则直接关闭return conn->Shutdown();}}return;}public:HttpServer(uint16_t port,int timeout = DEFALT_TIMEOUT):_server(port){_server.EnableInactiveRelease(timeout);_server.SetConnectedCallback(std::bind(&HttpServer::OnConnected,this,std::placeholders::_1));_server.SetMessageCallback(std::bind(&HttpServer::OnMessage,this,std::placeholders::_1,std::placeholders::_2));}void SetBaseDir(const std::string& path){assert(Until::IsDirectory(path) == true);_basedir = path;}void Get(const std::string& parttern,const Handler& handler){_get_route.push_back(std::make_pair(std::regex(parttern),handler));}void Post(const std::string& parttern,const Handler& handler){_post_route.push_back(std::make_pair(std::regex(parttern),handler));}void Put(const std::string& parttern,const Handler& handler){_put_route.push_back(std::make_pair(std::regex(parttern),handler));}void Delete(const std::string& parttern,const Handler& handler){_delete_route.push_back(std::make_pair(std::regex(parttern),handler));}void SetThreadCount(int count){_server.SetThreadCount(count);}void Start(){_server.Start();}
};
相关文章:
【仿Mudou库one thread per loop式并发服务器实现】HTTP协议模块实现
HTTP协议模块实现 1. Util模块2. HttpRequest模块3. HttpResponse模块4. HttpContext模块5. HttpServer模块 1. Util模块 这个模块是一个工具模块,主要提供HTTP协议模块所用到的一些工具函数,比如url编解码,文件读写…等。 #include "s…...
教育行业网络安全:守护学校终端安全,筑牢教育行业网络安全防线!
教育行业面临的终端安全问题日益突出,主要源于教育信息化进程的加速、终端设备多样化以及网络环境的开放性。 以下是教育行业终端安全面临的主要挑战: 1、设备类型复杂化 问题:教育机构使用的终端设备包括PC、服务器等,操作系统…...
【网工第6版】第5章 网络互联②
目录 ■ IPV6 ▲ IPV6报文格式 ◎ IPV6扩展报头(RFC2460) ◎ IPv6相关协议 ▲ IPV6地址分类 ◎ IPv6地址基础 ◎ IPv6地址举例 ◎ IPv6地址分类 ◎ 特殊地址对比IPv4 vs IPv6 ▲ 过渡技术 本章重要程度:☆☆☆☆☆ ■ IPV6 与IPv4…...
ASP.NET Core 分层项目中EFCore的使用
文章目录 前言一、核心二、项目分层结构1)安装 NuGet 包Web 项目InfrastructureLibrary项目 2)领域模型和仓储接口 (Domain 层)3)基础设施层实现 (Infrastructure 层)4)应用层服务 (Application 层)5)Web API 配置6&am…...
.net core 中directory , directoryinfo ,file, fileinfo区别,联系,场景
一、类定义及核心功能 Directory类 类型:静态类 功能:提供目录操作的静态方法,包括创建、删除、移动目录,以及获取子目录或文件列表等。例如Directory.CreateDirectory()、Directory.GetFiles()。 适用场景&…...
jvm-获取方法签名的方法
在Java中,获取方法签名的方法可以通过以下几种方式实现,具体取决于你的需求和使用场景。以下是详细的介绍: 1. 使用反射 API Java 提供了 java.lang.reflect.Method 类来获取方法的相关信息,包括方法签名。 示例代码:…...
three.js中的instancedMesh类优化渲染多个同网格材质的模型
three.js小白的学习之路。 在上上一篇博客中,简单验证了一下three.js中的网格共享。写的时候就有一些想法,如果说某个场景中有一万棵树,这些树共享一个geometry和material,有没有好的办法将其进行一定程度上的渲染优化࿰…...
2025年一站式AI创作平台主要功能介绍及使用教程
在当今迅速发展的数字时代,人工智能(AI)已成为推动创新和提升工作效率的关键工具。今天给大家分享一个全面的一站式AIGC内容创作平台,对其主要功能及使用教程进行讲解,旨在帮助用户显著提升工作和学习效率。无论您需要…...
YOLO11改进,尺度动态损失函数Scale-based Dynamic Loss,减少标签不准确对损失函数稳定性的影响
在目标检测领域,标签噪声与尺度敏感问题始终是制约模型性能提升的"阿喀琉斯之踵"。2025年CVPR最佳论文提出的尺度动态损失函数(Scale-based Dynamic Loss, SDL),通过构建自适应损失调节机制,不仅实现了对YOLOv11检测精度的指数级提升,更重新定义了损失函数的设…...
<项目代码>YOLO小船识别<目标检测>
项目代码下载链接 YOLOv8是一种单阶段(one-stage)检测算法,它将目标检测问题转化为一个回归问题,能够在一次前向传播过程中同时完成目标的分类和定位任务。相较于两阶段检测算法(如Faster R-CNN)࿰…...
我用deepseek做了一个提取压缩文件夹下pdf和word文件工具
由于最近需要把大量的压缩文件的pdf和word文件统一复制到一个文件夹中。 我们一般正常操作方式的是把一个压缩文件一个一个解压,然后在把一个的解压好的文件夹下文件复制到另外一个文件夹中。 这个也需太繁琐了,从以往统计的需要花费两个小时间&#x…...
单例模式 (Singleton Pattern)
单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来访问该实例。 核心特点 唯一性:一个类只能有一个实例 全局访问:提供全局访问该实例的方式 延迟初始化:通常在第一次被请求时才创建实…...
01-初识前端
一、邂逅前端开发 1.1. 软件开发、软件开发体系 这儿放个图~ 1.2.完善的应用程序包括哪些? 服务器开发 iOS开发、Android开发 Web开发 桌面开发(windows,mac os) iOS、mac os(OC,swift)&am…...
【JavaWeb后端开发03】MySQL入门
文章目录 1. 前言1.1 引言1.2 相关概念 2. MySQL概述2.1 安装2.2 连接2.2.1 介绍2.2.2 企业使用方式(了解) 2.3 数据模型2.3.1 **关系型数据库(RDBMS)**2.3.2 数据模型 3. SQL语句3.1 DDL语句3.1.1 数据库操作3.1.1.1 查询数据库3.1.1.2 创建数据库3.1.1…...
使用纯前端技术html+css+js实现一个蔬果商城的前端模板!
当我们刚开始学习前端的时候,我们都会先学习一些基础的编程知识点。对于网站开发前端学习,我们就会学习 html css js 等基础的前端技术,我们学习了基础编程知识后,肯定是需要一些项目,或者一些练习题,巩固一…...
SAP系统生产跟踪报表入库数异常
生产跟踪报表入库数异常 交库21820,入库43588是不可能的 原因排查: 报表的入库数取值,是取移动类型321 (即系检验合格后过账到非限制使用)的数. 查凭证,101过账2次21807,321过账了2次21794,然后用102退1次21794.就是说这批物料重复交库了. 解决: 方案一:开发增强设…...
mac 本地 docker 部署 nacos
标题查看 docker 的 nacos 版本 查看可用的Nacos版本,以最新版为例. 指定版本 自己修改即可. 访问Nacos镜像库地址:https://hub.docker.com/r/nacos/nacos-server/tags?page1&orderinglast_updated 标题二、挂载目录配置步骤 标题创建本地目录 按用户要…...
cgroup threaded功能例子
一、背景 cgroup在如今的系统里基本都是默认打开的一个功能。对于cgroup的cpu子系统,默认的颗粒度是进程为维度进行cgroup的cpu及cpuset的控制。而对于一些复杂进程,可能的需求是进程里一些个别线程要绑定在X1-Xn这些cpu核上,而除了这些个别…...
Elasticsearch插件:IDEA中的Elasticsearch开发利器
Elasticsearch插件:IDEA中的Elasticsearch开发利器 一、插件概述 Elasticsearch插件是为IntelliJ IDEA设计的专业工具,它让开发者能在IDE内直接与Elasticsearch集群交互,提供了查询编写、索引管理、数据分析等全方位支持。 核心价值&#…...
electron从安装到启动再到打包全教程
目录 介绍 安装 修改npm包配置 执行安装命令 源代码 运行 打包 先安装git, 安装打包工具 导入打包工具 执行打包命令 总结 介绍 electron确实好用,但安装是真的要耗费半条命。每次安装都会遇到各种问题,然后解决了之后。后面就不需要安装了,但有时候比如电脑重装…...
【Linux】轻量级命令解释器minishell
Minishell 一、项目背景 在linux操作系统中,用户对操作系统进行的一系列操作都不能直接操作内核,而是通过shell间接对内核进行操作。 Shell 是操作系统中的一种程序,它为用户提供了一种与操作系统内核和计算机硬件进行交互的界面。用户可以通…...
KEIL报错解决方案:No Algorithm found for: 08001000H - 080012EBH?
改这里: Cortex JLink/JTrace Target Drive - Flash Download - Size: 配好你这款芯片应该用的空间大小...
用银河麒麟 LiveCD 快速查看原系统 IP 和打印机配置
原文链接:用银河麒麟 LiveCD 快速查看原系统 IP 和打印机配置 Hello,大家好啊!今天给大家带来一篇在银河麒麟操作系统的 LiveCD 或系统试用镜像环境下,如何查看原系统中电脑的 IP 地址与网络打印机 IP 地址的实用教程。在系统损坏…...
DeepseekV3MLP 模块
目录 代码代码解释导入和激活函数配置类初始化方法前向传播方法计算流程 代码可视化 代码 import torch import torch.nn as nn import torch.nn.functional as F# 定义激活函数字典 ACT2FN {"relu": F.relu,"gelu": F.gelu,"silu": F.silu,&q…...
Ubuntu 系统下安装和使用性能分析工具 perf
在 Ubuntu 系统下安装和使用性能分析工具 perf 的步骤如下: 1. 安装 perf perf 是 Linux 内核的一部分,通常通过安装 linux-tools 包获取: # 更新软件包列表 sudo apt update# 安装 perf(根据当前内核版本自动匹配) …...
安恒Web安全面试题
《网安面试指南》https://mp.weixin.qq.com/s/RIVYDmxI9g_TgGrpbdDKtA?token1860256701&langzh_CN 5000篇网安资料库https://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247486065&idx2&snb30ade8200e842743339d428f414475e&chksmc0e4732df793fa3bf39…...
OSPF --- LSA
文章目录 一、OSPF LSA(链路状态通告)详解1. LSA通用头部2. OSPFv2 主要LSA类型a. Type 1 - Router LSAb. Type 2 - Network LSAc. Type 3 - Summary LSAd. Type 4 - ASBR Summary LSAe. Type 5 - AS External LSAf. Type 7 - NSSA External LSA 3. LSA泛…...
IDEA/WebStorm中Git操作缓慢的解决方案
问题描述 在WebStorm中进行前端开发时,发现Git操作(如push、checkout、pull等)特别缓慢,而在命令行(cmd)中执行相同的Git命令却很快,排除了网络问题。 解决方案 通过修改WebStorm安装目录下的runnerw.exe文件名可以…...
网络威胁情报 | Yara
Yara 是一个在威胁情报、数字取证和威胁猎取方面较为常用的语言。本文并非是Yara语言的教程,更多的是希望可以让大家知道这个语言的神奇之处及其在当今信息安全领域的重要性。 Yara 是什么? “恶意软件研究人员(以及其他所有人)…...
12.QT-Combo Box|Spin Box|模拟点餐|从文件中加载选项|调整点餐份数(C++)
Combo Box QComboBox 表⽰下拉框 核⼼属性 属性说明currentText当前选中的⽂本currentIndex当前选中的条⽬下标.从0开始计算.如果当前没有条⽬被选中,值为-1editable是否允许修改设为true时, QComboBox 的⾏为就⾮常接近 QLineEdit ,也可以 设置 validatoriconSize下拉框图标…...
FTTR 全屋光纤架构分享
随着光纤网络技术的发展,FTTR 技术逐步普及到千家万户,为了战未来,从现在开始构建并铺设 FTTR 全屋光纤是非常有必要的。 在前期 FTTR 全屋光纤网络的载荷搭建,可以额定为千兆网络或者2.5GE光纤网络,万兆光网最大的成本…...
内网穿透快解析免费开放硬件集成SDK
一、行业问题 随着物联网技术的发展,符合用户需求的智能硬件设备被广泛的应用到各个领域,而智能设备的远程运维管理也是企业用户遇到的问题 二、快解析内网穿透解决方案 快解析是一款内网穿透产品,可以实现内网资源在外网访问,…...
实验八 版本控制
实验八 版本控制 一、实验目的 掌握Git基本命令的使用。 二、实验内容 1.理解版本控制工具的意义。 2.安装Windows和Linux下的git工具。 3.利用git bash结合常用Linux命令管理文件和目录。 4.利用git创建本地仓库并进行简单的版本控制实验。 三、主要实验步骤 1.下载并安…...
《马尼拉》桌游期望计算器
《马尼拉》桌游期望计算器:做出最明智的决策 注:本项目仍在开发验证中,计算结果可能不够准确,欢迎游戏爱好者提供协助! 在线使用 | GitHub 项目简介 马尼拉期望计算器是一个基于 Vue 3 Vite 开发的网页应用ÿ…...
VLAN间通讯技术
多臂路由 路由器使用多条物理线路,每条物理线路充当一个 VLAN 的网管 注意:路由器对端的交换机接口,需要设定 Access 类型,因为路由器的物理接口无法处理 VLAN 标签 。 单臂路由 使用 以太网子接口 (sub-interface) 实现。 …...
linux基础学习--linux文件与目录管理
linux文件与目录管理 1. 目录与路径 1.1 相对路径与绝对路径 绝对路径:路径写法一定从根目录/写起。 绝对路径的正确度要高。 相对路径:路径写法不是由/写起。 1.2 目录的相关操作 切换目录的命令是cd,下面是比较特殊的目录:…...
云原生--基础篇-2--云计算概述(云计算是云原生的基础,IaaS、PaaS和SaaS服务模型)
1、云计算概念 云计算是一种通过互联网提供计算资源(包括服务器、存储、数据库、网络、软件等)和服务的技术模式。用户无需拥有和维护物理硬件,而是可以根据需要租用这些资源,并按使用量付费。 2、云计算特点 (1&am…...
存储器综合:内存条
一、RW 1000题刷题 1、计算Cache缺失率 2、 二、前提回顾 1、CPU从单个DRAM芯片中取地址 注意:Cache与主存的交互以“主存块”为单位,当出现Cache Miss时,主存以“主存块”为单位传输至Cache中。 2、内存条编址 多个DRAM芯片组成内存条&a…...
树莓派超全系列教程文档--(38)config.txt视频配置
config.txt视频配置 视频选项HDMI模式树莓派4-系列的HDMI树莓派5-系列的HDMI 复合视频模式enable_tvout LCD显示器和触摸屏ignore_lcddisable_touchscreen 通用显示选项disable_fw_kms_setup 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 视频选…...
pytest-项目结构
项目结构 api_test_project/ ├── config/ │ └── config.py # 配置文件,存储接口的基本信息,如 URL、请求头、认证信息等 ├── data/ │ └── test_data.json # 测试数据文件,存储接口的请求参数、预期结果等 ├── tests/…...
几何编码:启用矢量模式地理空间机器学习
在 ML 模型中使用点、线和多边形,将它们编码为捕捉其空间属性的向量。 自地理信息系统 (GIS) 诞生之初,“栅格模式”和“矢量模式”之间就存在着显著的区别。在栅格模式下,数据以值的形式呈现在规则的网格上。这包括任何形式的图像࿰…...
什么是SPA,SPA与MAP区别
什么是SPA,SPA与MAP区别 文章目录 什么是SPA,SPA与MAP区别一、什么是SPA二、SPA和MPA的区别一、单页应用与多页应用的区别**二、SPA 的优缺点对比**三、WPA的优缺点 **三、SPA 实现关键技术**hash 模式模式history模式 四、SPA 的适用场景与原因**适用场…...
计算机前沿技术课程论文 K-means算法在图像处理的应用
K-means算法在图像处理的应用 这是本人在计算机前沿技术课程中的课程论文文章,为了方便大家参考学习,我把完整的论文word文档发到了我的资源里,有需要的可以自取。 点击完整资源链接 目录 K-means算法在图像处理的应用摘要:引言1…...
第十四届蓝桥杯 2023 C/C++组 平方差
目录 题目: 题目描述: 题目链接: 思路: 核心思路: 第一种思路: 第二种思路: 坑点: 代码: 数学找规律 O(n) 50分代码详解: O(1)满分代码详解&#x…...
【数学建模】随机森林算法详解:原理、优缺点及应用
随机森林算法详解:原理、优缺点及应用 文章目录 随机森林算法详解:原理、优缺点及应用引言随机森林的基本原理随机森林算法步骤随机森林的优点随机森林的缺点随机森林的应用场景Python实现示例超参数调优结论参考文献 引言 随机森林是机器学习领域中一种…...
计算机组成与体系结构:存储器(Memory)
目录 📁 当你打开一个文件,计算机会做什么? ⚡ 越大的 memory,访问速度越快吗? 🧠 那么,我们是怎么设计存储器的呢? Primary Memory(主存)登场ÿ…...
MyBatis框架—xml映射
目录 一.为什么需要进行手动映射? 二.关联查询 1.使用resultMap进行映射 2.使用Connection进行映射 一.为什么需要进行手动映射? 当我们设计多表查询或关联查询时,表中含有相同的字段名或要进行关联查询时,MyBatis无法智能识别如何处理映射结果&…...
Vue接口平台学习十——接口用例页面2
效果图及简单说明 左边选择用例,右侧就显示该用例的详细信息。 使用el-collapse折叠组件,将请求到的用例详情数据展示到页面中。 所有数据内容,绑定到caseData中 // 页面绑定的用例编辑数据 const caseData reactive({title: "",…...
Visual Studio 2022 运行一个后台程序而不显示控制台窗口
在 Visual Studio 2022 中,希望运行一个后台程序而不显示控制台窗口(黑色命令框),可以通过以下方法实现: 修改项目输出类型为 Windows 应用程序 右键项目 → 选择 属性 (Properties)在 配置属性 → 链接器 → 系统 (…...
剑指Offer(数据结构与算法面试题精讲)C++版——day17
剑指Offer(数据结构与算法面试题精讲)C版——day17 题目一:节点值之和最大的路径题目二:展平二叉搜索树题目三:二叉搜索树的下一个节点附录:源码gitee仓库 题目一:节点值之和最大的路径 题目&am…...