Muduo网络库实现 [十六] - HttpServer模块

目录

设计思路

类的设计

模块的实现

公有接口

私有接口

疑问点


设计思路

本模块就是设计一个HttpServer模块,提供便携的搭建http协议的服务器的方法。那么这个模块需要如何设计呢? 这还需要从Http请求说起。

首先从http请求的请求行开始分析,请求行里面有个方法。分为静态资源请求功能性请求的。

静态资源请求顾名思义就是用来获取服务器中的某些路径下的实体资源,比如文件的内容等,这一类请求中,url 中的资源路径必须是服务器中的一个有效的存在的文件路径。比如:

  • HTML/CSS/JavaScript文件
  • 图片(JPG、PNG、GIF等)
  • 视频和音频文件
  • PDF、文档等静态文件
  • 字体文件

而如果提取出来的资源路径并不是一个实体文件的路径,那么他大概率是一个功能性请求,这时候就有用户来决定如何处理这个请求了,也就是我们前面说过的 请求路径 和 处理方法的路由表。

功能性请求如下

  • 用户登录/注册
  • 商品搜索结果
  • 个人资料页面
  • 订单处理
  • API接口调用
  • 数据统计和报表生成

但是还有一种特殊的情况就是资源路径是一个目录,比如 / ,这时候有可能是一个访问网站首页的请求,所以我们需要判断在这个路径后面加上 index.html (也可以是其他的文件名,取决于你的网站的首页的文件名) ,如果加上之后,路径有效且存在实体文件,那么就是一个静态资源请求,如果还是无效,那么就是一个功能性请求。

功能性请求如何处理呢?这是由使用或者说搭建服务器的人来决定的。 用户未来想要提供某些功能,可以让他和某个虚拟的目录或者说特定的路径绑定起来。 比如提供一个登录功能,那么用户可以规定  /login 这个路径就代表登录的功能,未来如果收到了一个请求资源路径是 /login ,那么就不是请求实体资源,而是调用网站搭建者提供的登录的方法进行验证等操作。 一般来说这些虚拟路径不会和实体资源路径冲突。

同时,对于这种功能性请求对应的路径,他并不是说一个路径只能有一个功能,不同的请求方法,同一个路径,最终执行的方法也可以是不同的,这具体还是要看使用者的设定。

所以为了维护这样的功能性路径和需要执行的方法之间的映射关系,我们需要为每一种请求方法都维护一张路由表,路由表中其实就是保存了路径和所需要执行的方法之间的映射关系。

在我们这里,就只考虑常用的五种方法,get,post,delete,head,put,其他的暂时就不提供支持了

    //五张路由表using Handler = std::function<void(const HttpRequest&,HttpResponse*)>; using HandlerTable = std::unordered_map<std::string,Handler>;HandlerTable _get_route; HandlerTable _post_route; HandlerTable _head_route; HandlerTable _put_route; HandlerTable _delete_route;

这是交给用户进行设置的,我们也会提供五个接口给用户用来添加处理方法。

但是,这样的表真的好吗? 

在实际的应用中,比如有以下的功能性请求的请求路径 , /login1213 , /login12124 , /login1213626 , /login12152 , /login1295 , /login1275 ,对于这样的一类路径,他们其实需要执行的是同一个方法,而并不需要为每一个类似的路径设置一个方法,而路径后半部分的数字其实后续可以当成参数来用。

那么综上所述,我们的路由表中作为 key 值的并不是 std::string ,而是只需要满足某一种匹配要求的路径,都可以执行某一方法,那么作为 key 值的其实是正则表达式。

    using HandlerTable = std::unordered_map<std::regex,Handler>;

但是如果我们编译一下就会发现,正则表达式是不能作为哈希的 key 值的,或者说不匹配默认的哈希函数。 

我们可以思考一下,我们用正则表达式作为 key 了,那么后面不管使用何种数据结构来存储正则表达式和操作方法的映射关系,我们都是要遍历整个路由表的,需要遍历表中的所有的正则表达式,然后拿着我们的路径来进行正则匹配,匹配上了就说明这是我们要找的方法,如果匹配不上就说明不是,不管怎么样,都是要进行遍历,那么其实我们直接用数组来存储也是一样的。

所以最终我们使用 vector 来存储用户方法。

    using HandlerTable = std::vector<std::pair<std::regex,Handler>>;

而HttpServer模块中除了五张路由表,还需要一个TcpServer对象,这是毋庸置疑的。 同时还需要保存一个网页根目录,这个根目录是要交给用户设置的,由使用者决定。

 
//支持Http协议的服务器
class HttpServer
{
private:TcpServer _server;std::string _base_path; //网页根目录//五张路由表using Handler = std::function<void(const HttpRequest&,HttpResponse*)>; using HandlerTable = std::vector<std::pair<std::regex,Handler>>;HandlerTable _get_route; HandlerTable _post_route; HandlerTable _head_route; HandlerTable _put_route; HandlerTable _delete_route; public:
};

类的设计

那么需要哪些接口呢?

首先需要提供给用户的五个设置功能方法的接口,以及设置网页根目录和服务器线程数的接口。

还需要提供给用户是否开启超时释放,以及启动服务器的接口。

提供给用户的接口就这么多,其实都很简单,难的是私有的一些接口:

首先,未来拿到一个完整请求之后,我们需要能够判断这个请求是静态资源请求还是功能性请求。

  • 如果是资源性请求我们需要怎么做?
  • 如果是功能性请求我们有需要怎么做?

最后还需要将相应组织成一个tcp报文进行回复。

同时还需要提供未来设置给TcpServer的连接建立和新数据到来的回调方法,这两个方法是必需的,因为在连接建立时我们必须要设置上下文,在新数据到来时必须要有逻辑来决定怎么处理。

class HttpServer {
public://构造函数HttpServer();// 配置设置类方法void SetBaseDir();         // 设置静态资源路径的根目录void SetThreadCount();      // 设置线程数量void Listen();  //开始监听// 路由设置方法void Get();                 // 设置GET方法void Post();                // 设置Post方法void Put();                 // 设置Put方法void Delete();              // 设置Delete方法void Head();                // 设置Head方法private:// 连接和消息处理回调void OnConnected();         // 设置一个上下文void OnMessage();           // 把请求报文从应用层缓冲区提取出来// 请求处理和路由bool IsFileHandler();       // 判断是否是静态资源请求void Route();               // 通过请求类型分别进行处理void Dispatcher();          // 功能性请求处理void FileHandler();         // 静态资源请求处理void ErrorHandler();        // 错误信息填充void WriteResponse(); //把响应对象转换成响应报文并发送
};

模块的实现

下面我将详细的讲解,这些模块都是干嘛的,让各位能有个更清晰的思路

公有接口

首先是设置静态资源路径根目录,这个是为了开发者设定的,因为用户在访问的时候,很大概率是不会加上根目录的,就好比你要在图片网站上找一个苹果的图片,对于网站来说他这个苹果肯定是分类在水果目录下面的,你在输入的时候大概率是直接apple.png,如果不进行设置默认根目录的话,那么肯定是找不到的,当你设置了根目录之后,你再输入apple.png的时候,它就会默认的变成fruit/apple.png了。

而开发者在进行设置的情况下,可能会出现粗心,比如说这个目录名字我少写了一个字母,所以这个函数需要先去寻找是否存在这个目录,要是不存在那就是开发者写错了,我要反馈给开发者,要是没写错我就设置传入的dir为根目录

void SetBasedir(const string &dir)
{assert(Util::IsDirectory(dir) == true);_basedir = dir;
}

接着就是开发者设置下线程的数量了,直接就是传入要设置的count就行

void SetThreadPoolCount(int count)
{_server.SetThreadCount(count);
}

然后就是开始监听的接口了

    void Listen() // 开始监听{_server.Start();}

接下来就是路由表的使用了,对不同的方法的路由表都设置相应的正则表达式模式字符串和对应的回调函数,当用户输入了对应的请求方法的时候就去对应的路由表里查找。至于如何定义,那就是开发者来自定义了

void Get(const string &pattern, const Handler &handler)
{_get_route.push_back({regex(pattern), handler});
}void Post(const string &pattern, const Handler &handler)
{_post_route.push_back({regex(pattern), handler});
}void Put(const string &pattern, const Handler &handler)
{_put_route.push_back({regex(pattern), handler});
}void Delete(const string &pattern, const Handler &handler)
{_delete_route.push_back({regex(pattern), handler});
}

最后就是构造函数的实现了,需要传入一个端口号来对我们内部的TcpServer对象进行初始化,然后包括三个内容,启动非活跃连接销毁,设置当连接来到的时候的回调函数用来设置上下文,以及把消息从缓冲区获取的回调函数

HttpServer(int port, int timeout = DEFAULT_TIMEOUT): _server(port)
{// 启用非活跃连接释放_server.EnableInactiveRelease(timeout);// 设置连接回调函数_server.SetConnectedCallback(bind(&HttpServer::OnConnected, this, placeholders::_1));// 设置消息回调函数_server.SetMessageCallback(bind(&HttpServer::OnMessage, this, placeholders::_1, placeholders::_2));
}

私有接口

这些私有接口是由开发者去调用的,来设置一些信息,让使用者去更好的使用

首先是设置给新连接设置一个上下文,用于当连接被切换的时候保存其中的数据,当下次再切换回来的时候,能接着当前的数据继续进行操作,所以需要的参数就是一个新连接的引用,因为连接是会被放在TCP的全连接队列中的

    void OnConnected(const PtrConnection &conn) // 设置一个上下文{conn->SetContext(HtppContext());DBG_LOG("new connection %p", conn.get());}

接着是对于该新连接数据的提取,因为这些数据从TCP的接收缓冲区是先提取到用户态中的缓冲区的,但是因为TCP是面向字节流的,也就是在用户态的缓冲区存储的数据都是以字节的形式,但是我Http要的是报头,正文,请求行,这种类型的呀,所以肯定也是需要转换的也就是用到了context的模块,接着就是判断请求的状态码,如果是状态码大于400了,就说明错误了,此时就需要进行处理了,可能你开发者想这个时候给用户弹出一个错误网页,所以接下来就调用ErrorHandler函数,然后把这个错误响应回去,再重置下HTTP上下文,准备处理新的请求,然后把缓冲区更新下也就是清空缓冲区,接着就是关闭连接了。如果是正常的状态码就返回了

    void OnMessage(const PtrConnection &conn, Buffer *buf)   // 处理从客户端接收到的HTTP请求消息{while(buf->ReadAbleSize() > 0)         // 只要缓冲区中还有可读数据就继续处理{HttpContext *context = conn->GetContext()->get<HttpContext>();  // 从连接上下文中获取HTTP上下文对象context->RecvHttpRequest(buf);      // 从缓冲区中解析HTTP请求数据HttpRequest &req = context->GetRequest();  // 获取解析后的HTTP请求对象HttpResponse rsp;                   // 创建HTTP响应对象if(context->StatusCode() >=400)     // 检查HTTP状态码,如果大于等于400表示出错{ErrorHandler(req,&rsp);         // 调用错误处理函数生成错误响应WriteResponse(conn,req,rsp);    // 将错误响应写回客户端context->Reset();               // 重置HTTP上下文,准备处理新的请求buf->MoveReadIndex(buf->ReadAbleSize());  // 清空缓冲区中剩余的所有数据conn->ShutDown();               // 关闭连接}return;}}

可能会有同学问了,这里的context->Reset();   buf->MoveReadIndex(buf->ReadAbleSize()); 的是不是可以丢弃了,因为后面直接就是shutdown了

可以是可以但是这样是不规范的,释放链接是释放链接,在释放链接之前,我们正常去处理错误逻辑,将各个环节该置空的置空,该清理的清理,这是我们必须在代码中进行体现的,这才是一个好的代码习惯。并且来说,conn->shutdow还不是实际发送的逻辑,在后续还会进行一些判断的,所以上层该做的工作还是要做的


这个操作之后,我们也就解析了二进制的数据了,然后判断请求行的方法是什么类型的,是不是静态资源的请求。需要四步:首先需要判断有没有设置资源根目录。静态资源请求的方法必须是 GET 或者 HEAD ,因为其他的方法不是用来获取资源的。然后静态资源请求的路径必须是一个合法的路径。最后就需要判断请求的路径的资源是否存在。但是我们需要考虑路径是目录的时候,给它加上一个 index.html。最后就是判断文件是否存在

bool IsFileHandler(const HttpRequest &req)
{// 1.查看设置了静态资源根目录if(_basedir.empty())return false;// 2.请求方法必须是GET或HEADif(req._method != "GET" && req._method != "HEAD")return false;// 3.请求资源路径必须合法if(Util::ValidPath(req._path) == false)return false;// 4.请求资源必须存在,且是一个普通文件// 为防止修改路径,先复制一份string req_path = _basedir + req._path;// 先处理特殊情况:如果请求的是目录,就换成请求首页if(req._path.back() == '/')req_path += "index.html";// 如果不是普通文件就错误if(Util::IsRegular(req_path) == false)return false;return true;
}

接下来我们举二个例子来更好的帮我们捋一捋思路 

例子1: 正常的图片请求

  • 用户访问 http://example.com/images/logo.png
  • 浏览器发送 GET 请求,路径为 /images/logo.png
  • 函数检查:
    • 静态根目录已设置为 /var/www/html/
    • 请求方法是 GET ✓
    • 路径 /images/logo.png 是合法的 ✓
    • 文件 /var/www/html/images/logo.png 存在且是普通文件 ✓
  • 函数返回 true,服务器会提供这个图片文件

例子2: 请求目录

  • 用户访问 http://example.com/blog/
  • 浏览器发送 GET 请求,路径为 /blog/
  • 函数检查:
    • 静态根目录已设置 ✓
    • 请求方法是 GET ✓
    • 路径 /blog/ 是合法的 ✓
    • 检测到路径以 / 结尾,自动添加 index.html
    • 检查 /var/www/html/blog/index.html 是否存在
    • 如果存在,返回 true;如果不存在,返回 false

接下来就是开发者对于路由表的规则的设置了,首先就是判断是什么类型的请求,如果是静态资源请求就调用静态资源的处理方法,如果是功能性的请求就匹配路由表中的方法,并且对不同方法设置不同的请求,如果两种请求都不是就说明是错的了

// 通过请求类型分别处理
void Route(HttpRequest &req, HttpResponse *rsp)
{// 是静态资源请求就静态资源处理if(IsFileHandler(req) == true)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);// 两个都不是就返回405 METHOD NOT ALLOWEDrsp->_status = 405;return;
}

接下来就是对静态资源请求的处理,首先构造一个完整的文件路径,然后如果结尾是/就说明是访问的首页,然后把路径读响应到正文中,如果文件读取失败就说明错误了。然后用ExtMime把路径中的最后一部分给分离出来,然后填入到响应报头的Content-type中

// 静态资源请求处理
void FileHandler(const HttpRequest &req, HttpResponse *rsp)  // 处理静态文件请求的函数
{// 读取静态文件资源,放到rsp的body,并设置mime// 判断里面没有修改资源路径,所以在这里要修改string req_path = _basedir + req._path;  // 构造完整的文件路径if(req._path.back() == '/')  // 如果请求路径以'/'结尾(请求的是目录)req_path += "index.html";  // 自动添加index.html作为默认页面bool ret = Util::ReadFile(req_path, &rsp->_body);  // 读取文件内容到响应体if(ret == false)  // 如果文件读取失败return;  // 直接返回,不设置任何响应内容string mime = Util::ExtMime(req_path);  // 根据文件扩展名获取MIME类型rsp->SetHeader("Content-type", mime);  // 设置Content-type响应头return;  // 处理完成,返回
}

接下来几个例子,帮助大家理解

假设用户通过浏览器访问你的网站,请求了以下几个不同的资源:

  • HTML页面请求:
    • 用户访问 http://yourwebsite.com/about.html
    • 服务器找到 about.html 文件
    • ExtMime 函数确定这是HTML文件,返回 text/html
    • 服务器在响应头中设置 Content-type: text/html
    • 浏览器收到响应后,看到这个MIME类型,知道应该将内容解析为HTML并渲染网页
  • 图片请求:
    • 当HTML页面中引用了图片 <img src="logo.png">
    • 浏览器请求 http://yourwebsite.com/logo.png
    • 服务器找到 logo.png 文件
    • ExtMime 函数确定这是PNG图片,返回 image/png
    • 服务器在响应头中设置 Content-type: image/png
    • 浏览器看到这个MIME类型,知道应该将内容解析为PNG图片并显示

接着就是对动态请求的处理。先循环遍历路由表表里面正则表达式是否匹配请求对象的资源路径,以找到对应的处理函数填充响应对象,如果找不到就是404

// 功能性请求处理
void Dispatcher(HttpRequest &req, HttpResponse *rsp, Handlers &handlers)  // 处理动态请求的分发器函数
{// 循环遍历路由表表里面正则表达式是否匹配请求对象的资源路径,以找到对应的处理函数填充响应对象for(auto &handler :handlers)  // 遍历所有注册的处理器{const regex &e = handler.first;  // 获取当前处理器的正则表达式const Handler &func = handler.second;  // 获取当前处理器的处理函数bool ret = regex_match(req._path, req._matches, e);  // 尝试匹配请求路径与正则表达式if(ret == false)  // 如果不匹配continue;  // 继续检查下一个处理器return func(req, rsp);  // 找到匹配的处理器,调用对应的处理函数并返回}// 找不到就是404rsp->_status = 404;  // 设置HTTP状态码为404(未找到)
}

接着就是把响应对象转换成响应报文并发送。思路就是先把报头完善一下,也就是进行判断是短链接就把字段的Connection设置为close,否则就设置成keep-alive,然后判断Content-Length有没有填,ContentType应该填什么。要是重定向的话,就更新Location字段为重定向的url,这些都是必要的条件。当完成了这些之后,就可以构建响应报文了完善响应行,响应报头,添加空行,添加响应正文,最后就是发送响应报文

// 把响应对象转化成响应报文并发送
void WriteResponse(const PtrConnection &conn, const HttpRequest &req, HttpResponse &rsp)  // 将HTTP响应对象序列化为HTTP报文并发送
{// 1.完善报头if(req.Close() == true)  // 如果请求要求关闭连接rsp.SetHeader("Connection", "close");  // 设置Connection头为closeelsersp.SetHeader("Connection", "keep-alive");  // 否则设置为keep-alive保持连接if(rsp._body.empty() == false && rsp.HasHeader("Content-Length") == false)  // 如果响应体不为空且没有设置Content-Length头rsp.SetHeader("Content-Length", to_string(rsp._body.size()));  // 设置Content-Length头为响应体大小if(rsp._body.empty() == false && rsp.HasHeader("Content-Type") == false)  // 如果响应体不为空且没有设置Content-Type头rsp.SetHeader("Content-Type", "application/octet-stream");  // 设置默认的Content-Type为二进制流if(rsp._redirect_flag = true)  // 如果需要重定向rsp.SetHeader("Location", rsp._redirect_url);  // 设置Location头指向重定向URL// 2.组织响应报文stringstream rsp_str;  // 创建字符串流用于构建HTTP响应// 响应行rsp_str << req._version << " " << to_string(rsp._status) << " " << Util::StatusDesc(rsp._status) << "\r\n";  // 构建状态行// 响应报头for(auto &head : rsp._headers)  // 遍历所有响应头{rsp_str << head.first << ": " << head.second << "\r\n";  // 添加每个响应头到响应中}// 一个空行rsp_str << "\r\n";  // 添加空行分隔响应头和响应体// 响应正文rsp_str << rsp._body;  // 添加响应体// 3.发送响应报文conn->Send(rsp_str.str().c_str(), rsp_str.str().size());  // 发送完整的HTTP响应
}

最后就是设置错误信息,如果这个路径是错误的,应该给用户返回什么

void ErrorHandler(const HttpRequest &req, HttpResponse *rsp)  // 处理HTTP错误的函数
{string body;  // 创建一个字符串用于构建HTML错误页面body += "<html>";  // 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);  // 添加HTTP状态码body += " ";  // 添加空格body += Util::StatusDesc(rsp->_status);  // 添加HTTP状态码的描述文本body += "</h1>";  // 一级标题结束标签body += "</body>";  // 正文结束标签body += "</html>";  // HTML文档结束标签// 响应正文类型是htmlrsp->SetContent(body, "text/html");  // 设置响应内容为HTML并指定MIME类型
}

疑问点

std::regex确实不能直接用作std::unordered_map的键

http不是管理的是协议的请求和响应吗 这个 void OnConnect(const PtrConnection& conn) 连接不是应该由tcp去处理吗?

“请求可能分多个TCP包到达,需要有地方存储部分解析的数据,这个地方就是上下文” 那这个数据不是存储在用户的缓冲区当中的吗?

HttpContext *context = conn->GetContext()->get<HttpContext>(); 这个代码不懂

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/76348.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

多模态记忆融合:基于LSTM的连续场景生成——突破AI视频生成长度限制

一、技术背景与核心挑战 2025年视频生成领域面临的关键难题是长时程连贯性——传统方法在生成超过5分钟视频时会出现场景跳变、物理规则不一致等问题。本研究提出时空记忆融合架构&#xff08;ST-MFA&#xff09;&#xff0c;通过LSTM记忆门控与多模态对齐技术&#xff0c;在R…

LabVIEW油气井井下集成监测系统

LabVIEW平台开发的油气井井下集成监测系统通过实时监控油气井的井下环境参数&#xff0c;如温度、压力和有害气体含量&#xff0c;有效提高了油气采收率并确保了作业安全。系统利用高精度传感器和强大的数据处理能力&#xff0c;通过综合监测和分析&#xff0c;实现了对油气井环…

【python画图】:从入门到精通绘制完美柱状图

目录 Python数据可视化&#xff1a;从入门到精通绘制完美柱状图一、基础篇&#xff1a;快速绘制柱状图1.1 使用Matplotlib基础绘制1.2 使用Pandas快速绘图 二、进阶篇&#xff1a;专业级柱状图定制2.1 多系列柱状图2.2 堆叠柱状图2.3 水平柱状图 三、专业参数速查表Matplotlib …

【 解决Cline插件无法激活及DeepSeek模型请求卡顿或者无法加载问题】

解决Cline插件无法激活及DeepSeek模型请求卡顿问题 问题描述 在VSCode中使用Cline插件时遇到以下问题&#xff1a; 插件长时间卡在"activating"激活状态成功激活后发起DeepSeek对话时&#xff0c;API请求阶段持续卡顿夜间时段问题出现频率较低 环境信息 Cline版…

聊透多线程编程-线程互斥与同步-9.C# 线程互斥实现方式

目录 1. 锁机制 (Locking Mechanisms) (1) lock 关键字 (2) Monitor 类 2. 跨进程互斥机制 3. 信号量机制 (1) Semaphore 和 SemaphoreSlim 4. 读写锁机制 (1) ReaderWriterLockSlim 5. 原子操作机制 (1) Interlocked 类 6. 自旋锁机制 (1) SpinLock 线程互斥是一种…

eNSP无法启动AR报错码40,而且按照eNSP帮助手册排查都没用,我的处理方法【自己存档版】

问题&#xff1a; 已经尝试过eNSP的帮助手册&#xff0c;发现都没用&#xff01; eNSP启动AR设备报错码40且常规排查无效时&#xff0c;可尝试以下解决方案&#xff08;按优先级排序&#xff09;&#xff1a; 1. 关闭Hyper-V和Windows沙盒&#xff08;我是这个问题&#xff0…

秒杀系统解决两个核心问题的思路方法总结:1.库存超卖问题;2.用户重复抢购问题。

秒杀系统解决两个核心问题 秒杀系统解决两个核心问题&#xff1a;一、解决库存超卖的核心逻辑&#xff1a;解释&#xff1a;原子性保证&#xff1a; 二、如何避免重复抢购&#xff1a;使用 Redis 做唯一标识判断优点&#xff1a; 三、流程完整梳理&#xff1a;四、通过数据库建…

【集成电路版图设计学习笔记】3.基本电路元件(MOS,电容,电阻)

一、MOSFET 在版图设计中&#xff0c;要定义一个mosfet&#xff0c;最关键的层次是polysilicon&#xff08;多晶硅&#xff09;和active&#xff08;有源区&#xff09;。用有源区定义了一个矩形的区域&#xff0c;在这个区域内才可以形成一个有源器件&#xff0c;然后再用多晶…

蓝桥杯之差分题型

一维差分 问题描述 给定一个长度为 nn 的序列 aa。 再给定 mm 组操作&#xff0c;每次操作给定 33 个正整数 l,r,dl,r,d&#xff0c;表示对 al∼ral∼r​ 中的所有数增加 dd。 最终输出操作结束后的序列 aa。 Update&#xff1a;由于评测机过快&#xff0c;n,mn,m 于 2024…

深入剖析 C/S 与 B/S 架构及网络通信基础

目录 C/S 架构详解​ 概念与示例​ 优点​ B/S 架构详解​ 概念与示例​ 优势​ 缺点​ C/S 与 B/S 的区别​ 架构组成​ 使用场景​ 开发和维护​ 安全性​ 网络通信基础​ IP 地址​ MAC&#xff08;物理地址&#xff09;​ 端口​ 路由器​ 网关​ 子网掩…

常见免杀框架的使用(3款)---【AniYaGUI1.2.0、AV_Evasion_Tool掩日、FoxBypass_V1.0】

一、AniYaGUI1.2.0免杀框架 环境&#xff1a;虚拟机Win10 、云服务器 工具&#xff1a;Xshell、CobaltStrike 项目下载地址&#xff1a; https://github.com/piiperxyz/AniYa 1. 安装Go语言环境 确保Win10虚拟机安装 Golang 且环境变量中包含 go 否则⽆法编译&#xff08;注…

Apache HTTPD 换行解析漏洞

漏洞介绍 CVE-2017-15715 Apache HTTPD 是一个广泛使用的 HTTP 服务器&#xff0c;可以通过 mod_php 模块来运行 PHP 网页。在其 2.4.0 到 2.4.29 版本中存在一个解析漏洞&#xff0c;当文件名以 1.php\x0A 结尾时&#xff0c;该文件会被按照 PHP 文件进行解析&#xff0c;这…

常用开发环境/工具版本选择(持续更新中)

操作系统&#xff1a;Ubuntu Server Version&#xff08;LTS&#xff09;Latest Sub VerRelease Time24.04(Noble Numbat)24.04.22025-02-1622.04(Jammy Jellyfish)22.04.52024-09-1120.04(Focal Fossa)20.04.62023-03-1418.04(Bionic Beaver)18.04.62021-09-1516.04.7(Xenial…

STM32 认识STM32

目录 什么是嵌入式&#xff1f; 认识STM32单片机 开发环境安装 安装开发环境 开发板资源介绍 单片机开发模式 创建工程的方式 烧录STM32程序 什么是嵌入式&#xff1f; 1.智能手环项目 主要功能有&#xff1a; 彩色触摸屏 显示时间 健康信息&#xff1a;心率&#…

C#核心笔记——(六)框架基础

我们在编程时所需的许多核心功能并不是由C#语言提供的,而是由.NET Framework中的类型提供的。本节我们将介绍Framework在基础编程任务(例如虚的等值比较、顺序比较以及类型转换)中的作用。我们还会介绍Framework中的基本类型,例如String、DateTime和Enum. 本章中的绝大部分…

AI——K近邻算法

文章目录 一、什么是K近邻算法二、KNN算法流程总结三、Scikit-learn工具1、安装2、导入3、简单使用 三、距离度量1、欧式距离2、曼哈顿距离3、切比雪夫距离4、闵可夫斯基距离5、K值的选择6、KD树 一、什么是K近邻算法 如果一个样本在特征空间中的k个最相似&#xff08;即特征空…

transient关键字深度解析

Java transient 关键字深度解析 transient(意思:瞬时的,瞬间的) 1. 核心概念 (1) 基本定义 作用:标记字段不参与序列化 适用场景: 敏感数据(如密码、密钥) 临时计算字段 依赖运行时环境的字段(如Thread对象) (2) 语法示例 java public class User implements Se…

信刻电子档案蓝光光盘刻录安全检测长期归档

信刻一直致力于为档案馆、各行业档案部门&#xff0c;提供跨网数据交换、电子档案数据磁光异质备份归档解决方案。所研制的电子档案光盘智能长期归档系统&#xff0c;满足国产环境下”刻、管、存、检、用”全生命周期管理应用需求&#xff0c;能够提供一份离线归档、一份近线存…

Word 中“母版页”的等效机制

Word 和 PowerPoint 不太一样——**Word 实际上没有像 PowerPoint 那样的“母版页&#xff08;Master Page&#xff09;”**功能。但它有1个和“母版页”功能类似的东西&#xff0c;可能造成你看到的“校徽自动出现在每一页”的现象&#xff1a; ✅ Word 中“母版页”的等效机制…

Go:反射

为什么使用反射 在编程中&#xff0c;有时需编写函数统一处理多种值类型 &#xff0c;这些类型可能无法共享同一接口、布局未知&#xff0c;甚至在设计函数时还不存在 。 func Sprint(x interface{}) string {type stringer interface {String() string}switch x : x.(type) …