当前位置: 首页 > news >正文

HttpServer模块 --- 封装TcpServer支持Http协议

目录

模块设计思想

模块代码实现


模块设计思想

本模块就是设计一个HttpServer模块,提供便携的搭建http协议的服务器的方法。

那么这个模块需要如何设计呢? 这还需要从Http请求说起。

首先http请求是分为静态资源请求和功能性请求的。

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

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

但是还有一种特殊的情况就是资源路径是一个目录,比如 / ,这时候有可能是一个访问网站首页的请求,所以我们需要判断在这个路径后面加上 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对象,这是毋庸置疑的。 同时还需要保存一个网页根目录,这个根目录是要交给用户设置的,由使用者决定。

那么最终HttpServer的成员如下:


//支持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的连接建立和新数据到来的回调方法,这两个方法是必需的,其他的三个倒是无所谓。因为在连接建立时我们必须要设置上下文,在新数据到来时必须要有逻辑来决定怎么处理。

至于具体的实现,我们一步一步慢慢来。

模块代码实现

首先实现几个简单的提供给用户的接口:当然这里的Start或者说构造还没有完全实现,因为我们还没有设置连接建立回调和新数据回调这两个回调方法。

public:void SetBasePath(const std::string& basedir){_base_path = basedir;}void Get(const std::regex& e , const Handler& cb)  //设置GET{_get_route.push_back(std::make_pair(e,cb));}void Post(const std::regex& e , const Handler& cb)  //设置POST{_post_route.push_back(std::make_pair(e,cb));}void Put(const std::regex& e , const Handler& cb)  //设置PUT{_put_route.push_back(std::make_pair(e,cb));} void Head(const std::regex& e , const Handler& cb)  //设置HEAD{_head_route.push_back(std::make_pair(e,cb));}void Delete(const std::regex& e , const Handler& cb)  //设置DELETE{_delete_route.push_back(std::make_pair(e,cb));}        void EnableInactiveRelease(int delay = 30) //启动非活跃销毁   {_server.EnableInactiveRelease(delay);}void SetThreadCount(int cnt)    //设置线程数量{_server.SetThreadCount(cnt);}void Start()                    //启动服务器{_server.Start();}

那么剩下的就是连接建立回调以及新数据回调的逻辑了,

首先连接建立的时候,我们需要设置一个上下文给Connection对象。

    void OnConnect(const PtrConnection& conn){//设置一个上下文HttpContext ctx;conn->SetContext(ctx);}

剩下的就是最复杂的新数据回调了。

    void OnMessage(const PtrConnection& conn,Buffer* buf)   //获取新数据回调{}

首先第一步需要将上下文获取出来。

        // 1 获取上下文Any* context = conn->GetContext();HttpContext* pctx = context->GetData<HttpContext>();

然后就需要通过上下文对缓冲区数据进行解析,也就是调用HttpContext的接口进行处理,但是我们要看处理结果是什么来判断下一步怎么做。

        // 2 解析缓冲区数据pctx->RecvHttpRequest(buf);HttpRequest& req = pctx->GetRequest();HttpResponse resp;//判断解析是否出错if(pctx->RespStatu() >= 400)  //请求解析出错,此时的_recv_statu 也一定是RECV_ERR{HandlerError(req,resp);         //调用错误处理方法WriteResponse(conn,req,resp);   //返回响应conn->ShutDown();               //发生错误就关闭连接return;}if(pctx->RecvStatu() != RECV_OVER)      //还没收到一个完整请求return;//走到这里说明req是一个完整的请求
    void HandlerError(HttpRequest& req , HttpResponse& resp);void WriteResponse(const PtrConnection& conn , HttpRequest& req , HttpResponse& resp);

这里用到的两个接口我们一会再来实现。

接受到一个请求之后,其实我们就需要进行方法的路由了,那么我们直接再封装成一个接口。

        // 3 数据处理,路由Route(req,resp);    //进行方法路由,判断是不是静态资源请求。
    void Route(HttpRequest& req , HttpResponse& resp);

那么路由的过程中会填充好我们的响应的关键信息。 

处理完之后,我们就需要将响应发回给客户端。

        // 4 返回给客户端WriteResponse(conn,req,resp);

最后我们需要判断需不需要关闭连接,因为Http协议是请求应答式的服务,一般来说,只处理一个请求之后就会关闭连接。但是我们不要忘了长连接这个技术,也就是说,如果对方支持长连接,那么我们就不需要关闭连接,而是重置上下文之后进行下一个请求的处理。

        // 5 处理完之后重置上下文pctx->Reset();// 6 判断长短连接if(resp.Close())    //如果是短连接就直接关闭{conn->ShutDown();return;}//如果是长连接就需要搞成循环,读取下一个报文

如果是长连接的话,那么我们上面的处理的流程就应该是循环式的。

    void OnMessage(const PtrConnection& conn,Buffer* buf)   //获取新数据回调{while(buf->ReadSize() > 0)      //从逻辑上来说 while(1) 也是一样的{// 1 获取上下文Any* context = conn->GetContext();HttpContext* pctx = context->GetData<HttpContext>();// 2 解析缓冲区数据pctx->RecvHttpRequest(buf);HttpRequest& req = pctx->GetRequest();HttpResponse resp;//判断解析是否出错if(pctx->RespStatu() >= 400)  //请求解析出错,此时的_recv_statu 也一定是RECV_ERR{HandlerError(req,resp);         //调用错误处理方法WriteResponse(conn,req,resp);   //返回响应conn->ShutDown();               //发生错误就关闭连接return;}if(pctx->RecvStatu() != RECV_OVER)      //还没收到一个完整请求return;//走到这里说明req是一个完整的请求// 3 数据处理,路由Route(req,resp);    //进行方法路由,判断是不是静态资源请求。// 4 返回给客户端WriteResponse(conn,req,resp);// 5 处理完之后重置上下文pctx->Reset();// 6 判断长短连接if(resp.Close())    //如果是短连接就直接关闭{conn->ShutDown();return;}//如果是长连接就需要搞成循环,读取下一个报文}}

那么接下来就是里面用到的接口的实现了。

我们先来完成Route接口,在路由的接口中,首先我们需要判断资源路径是不是静态资源,如果是,那么就需要读取文件,如果不是,那么就需要进行任务的路由或者说派发。

    void Route(HttpRequest& req , HttpResponse& resp){if(IsFileResquest(req,resp))       //判断是否是静态资源请求return FileHandler(req,resp);//否则就需要到几个方法表中进行路由if(req._method == "GET")return Dispatcher(req,resp,_get_route);if(req._method == "POST")return Dispatcher(req,resp,_post_route);if(req._method == "PUT")return Dispatcher(req,resp,_put_route); if(req._method == "HEAD")return Dispatcher(req,resp,_head_route);if(req._method == "DELETE")return Dispatcher(req,resp,_delete_route);         //如果走到了这里,说明前面的处理方法都不行,那么一定是请求出问题了resp._statu = 405;      //Method Not AllowedHandlerError(req,resp,resp->_statu);}

那么静态资源如何判断处理呢?下面是判断的方法:

    bool IsFileResquest(HttpRequest& req , HttpResponse& resp) //判断以及处理静态资源{// 1 首先需要判断有没有设置资源根目录if(_base_path.empty()) return false;    //肯定不是静态资源请求// 2 静态资源请求的方法必须是 GET 或者 HEAD ,因为其他的方法不是用来获取资源的if(!(req._method == "GET" || req._method == "HEAD")) return false;//然后静态资源请求的路径必须是一个合法的路径if(Util::IsValid(req._path) == false) return false;//最后就需要判断请求的路径的资源是否存在//但是我们需要考虑路径是目录的时候,给它加上一个 index.htmlstd::string path = req._path;if(path.back() == '/') path += "index.html";//判断文件是否存在DEBUG_LOG("path:%s",path.c_str());std::string real_path = _base_path+path; if(Util::IsRegular(real_path) == false) return false;return true;    //走到这里才算是一个静态资源请求}

静态资源方法如何处理? 其实很简单,将文件读取出来放到响应的正文就行了,不过读取完之后还需要设置一些响应的Content相关的头部字段。

    void HandlerFile(HttpRequest& req , HttpResponse& resp) //处理静态资源请求{std::string path = _base_path+req._path;if(path.back() == '/') path +="index.html";Util::ReadFile(path,&resp._body);//然后设置响应头部字段//在这里我们可以只设置 Content-Type 字段,Content-Length可以交给WriteResponse接口来设置std::string mime = Util::GetMime(path);resp.AddHeader("Content-Type",mime);}

然后就是功能性请求的路由,其实就是遍历方法表进行匹配就行了。

    void Dispatcher(HttpRequest& req , HttpResponse& resp , const HandlerTable& table){for(std::pair<const std::regex& , Handler> p: table){const std::regex& e = p.first;const Handler& cb = p.second;std::smatch matches;bool ret = std::regex_match(req._path,matches,e);if(ret) return cb(req,&resp);}//走到这里说明路由表中没有对应的方法resp._statu = 404;  //Not FoundHandlerError(req,resp,resp->_statu);}

那么到此为止,路由的方法就解决了。

剩下的就是错误处理以及响应的格式化了。

错误的处理我们可以返回一个错误的展示界面

    void HandlerError(HttpRequest& req , HttpResponse& resp ,int statu){std::string body;body += "<!DOCTYPE html>";body += "<html><head><title>";body += std::to_string(statu);body += Util::StatuDesc(statu);body += "</title></head><body><h1>抱歉,该页面无法找到。</h1>";body += "<p>请检查您输入的网址是否正确,或者 <a href=\"/\">返回首页</a>。</p>";body += "</body></html>";resp._body = body;resp.AddHeader("Content-Type","text/html");}

最后就是处理一下我们的WriteResponse接口,

第一步需要完善响应的报头字段:

        if(req.Close()) resp.AddHeader("Connection","close");else  resp.AddHeader("Connection","keep-alive");if(resp._body.size()&&!resp.HasHeader("Content-Length")) resp.AddHeader("Content-Length",std::to_string(resp._body.size()));if(resp._body.size() && !resp.HasHeader("Content-Type")) resp.AddHeader("Content-Type","application/octet-stream");//重定向信息if(resp._redirect_flag) resp.AddHeader("Location",resp._redirect_url);

然后需要按指定格式组织响应,我们可以使用 osstream 这个字符流对象

        // 2 组织响应std::ostringstream out;//响应行  HTTP/1.0 404 NotFound\r\nout<<req._version<<" "<<std::to_string(resp._statu)<<" "<<Util::StatuDesc(resp._statu)<<"\r\n";//头部字段for(auto& p : resp._headers){out<<p.first<<": "<<p.second<<"\r\n";} //空行out<<"\r\n";//正文out<<resp._body;

最后就是发送出去

        // 3 发送conn->Send(out.str().c_str(),out.str().size());

那么WriteResponse的总体的代码:

    void WriteResponse(const PtrConnection& conn , HttpRequest& req , HttpResponse& resp){// 1 先把响应的头部字段完善了if(req.Close()) resp.AddHeader("Connection","close");else  resp.AddHeader("Connection","keep-alive");if(resp._body.size()&&!resp.HasHeader("Content-Length")) resp.AddHeader("Content-Length",std::to_string(resp._body.size()));if(resp._body.size() && !resp.HasHeader("Content-Type")) resp.AddHeader("Content-Type","application/octet-stream");//重定向信息if(resp._redirect_flag) resp.AddHeader("Location",resp._redirect_url);// 2 组织响应std::ostringstream out;//响应行  HTTP/1.0 404 NotFound\r\nout<<req._version<<" "<<std::to_string(resp._statu)<<" "<<Util::StatuDesc(resp._statu)<<"\r\n";//头部字段for(auto& p : resp._headers){out<<p.first<<": "<<p.second<<"\r\n";} //空行out<<"\r\n";//正文out<<resp._body;// 3 发送conn->Send(out.str().c_str(),out.str().size());}

那么最后我们再完善一下构造函数,需要传入一个端口号来对我们内部的TcpServer对象进行初始化,以及绑定两个回调函数,

    HttpServer(int port ,int delay = 30):_server(port) {_server.EnableInactiveRelease(delay);   //我们的http服务器默认是开启超时释放的_server.SetConnectCallBack(std::bind(&HttpServer::OnConnect,this,std::placeholders::_1));_server.SetMessageCallBack(std::bind(&HttpServer::OnMessage,this,std::placeholders::_1,std::placeholders::_2));}

那么我们也会注意到一个问题,就是新数据到来进行处理的时候,解析失败会调用 HandlerError和WriteResponse,而WriteResponse中会用到 req 的version ,但是我们实际上可能并没有读取到,所以我们可以给version一个初始值,可以给HttpRequest增加一个构造函数。

    HttpRequest():_version("HTTP/1.0"){}

其他的倒是没什么大问题了。

那么我们的http服务器的设计也就设计完了。

为了防止头文件重复包含,我们也需要加上条件编译。

#ifndef __HTTP__MUDUO__SERVER
#define __HTTP__MUDUO__SERVER
//  头文件内容
#endif

我们的服务器的代码编译是没有问题的,后续我们会对其进行测试,来修正项目中的一些没有注意到的bug。

相关文章:

HttpServer模块 --- 封装TcpServer支持Http协议

目录 模块设计思想 模块代码实现 模块设计思想 本模块就是设计一个HttpServer模块&#xff0c;提供便携的搭建http协议的服务器的方法。 那么这个模块需要如何设计呢&#xff1f; 这还需要从Http请求说起。 首先http请求是分为静态资源请求和功能性请求的。 静态资源请求…...

蓝牙资讯|iOS 18.1 正式版下周推送,AirPods Pro 2耳机将带来助听器功能

苹果公司宣布将在下周发布 iOS 18.1 正式版&#xff0c;同时确认该更新将为 AirPods Pro 2 耳机带来新增“临床级”助听器功能。在启用功能后&#xff0c;用户首先需要使用 AirPods 和 iPhone 进行简短的听力测试&#xff0c;如果检测到听力损失&#xff0c;系统将创建一项“个…...

C语言之环形缓冲区概述及实现

在C语言中存在一种高效的数据结构&#xff0c;叫做环形缓存区&#xff0c;其被广泛用于处理数据流与缓存区的管理。如&#xff1a;数据的收发、程序层级之间的数据交换、硬件接收大量数据的场景&#xff0c;同时也可配合DMA实现通信协议收发数据&#xff0c;已确保流量控制、数…...

C++Socket通讯样例(服务端)

1. 创建Socket实例并开启。 private int OpenTcp(int port, string ip "") {//1. 开启服务端try{_tcpServer new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);IPAddress ipAddr IPAddress.Any;if (ip ! "" && i…...

【学术会议论文投稿】大数据治理:解锁数据价值,引领未来创新

第六届国际科技创新学术交流大会&#xff08;IAECST 2024&#xff09;_艾思科蓝_学术一站式服务平台 更多学术会议请看&#xff1a;https://ais.cn/u/nuyAF3 目录 引言 一、大数据治理的定义 二、大数据治理的重要性 三、大数据治理的核心组件 四、大数据治理的实践案例…...

location中href和replace的区别

1.有两种方式&#xff1a; a、使用 location.href&#xff1a;window.location.href“success.html”; b、使用location.replace&#xff1a;window.location.replace(“new_file.html”); 2.区别是什么&#xff1f; 结果&#xff1a;href相当于打开一个新页面&#xff0c;…...

基于Spring Boot的在线摄影工作室开发指南

1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及&#xff0c;互联网成为人们查找信息的重要场所&#xff0c;二十一世纪是信息的时代&#xff0c;所以信息的管理显得特别重要。因此&#xff0c;使用计算机来管理网上摄影工作室的相关信息成为必然。开发合…...

JDK源码系列(五)—— ConcurrentHashMap + CAS 原理解析

更好的阅读体验 \huge{\color{red}{更好的阅读体验}} 更好的阅读体验 ConcurrentHashMap 类 ConcurrentHashMap 1.7 在JDK1.7中ConcurrentHashMap采用了数组分段锁的方式实现。 Segment(分段锁)-减少锁的粒度 ConcurrentHashMap中的分段锁称为Segment&#xff0c;它即类似于…...

技术成神之路:二十三种设计模式(导航页)

设计原则/模式链接面向对象的六大设计原则技术成神之路&#xff1a;面向对象的六大设计原则创建型模式单例模式建造者模式原型模式工厂方法模式抽象工厂模式行为型模式策略模式状态模式责任链模式观察者模式备忘录模式迭代器模式模板方法模式访问者模式中介者模式命令模式解释器…...

Rust编程与项目实战-元组

【图书介绍】《Rust编程与项目实战》-CSDN博客 《Rust编程与项目实战》(朱文伟&#xff0c;李建英)【摘要 书评 试读】- 京东图书 (jd.com) Rust编程与项目实战_夏天又到了的博客-CSDN博客 8.2.1 元组的定义 元组是Rust的内置复合数据类型。Rust支持元组&#xff0c;而且元…...

容性串扰和感性串扰

串扰根源在于耦合&#xff0c;电场耦合产生容性耦合电流&#xff0c;磁场耦合产生感性耦合电流 关于容性后向串扰电压与后向串扰系数推导...

windows Terminal 闪退 -- 捣蛋砖家

最近点击Windows 终端总是闪退。 日志提示: 错误应用程序名称: WindowsTerminal.exe&#xff0c;版本: 1.21.2410.17001&#xff0c;时间戳: 0x67118f02 错误模块名称: ucrtbase.dll&#xff0c;版本: 10.0.22621.3593&#xff0c;时间戳: 0x10c46e71 异常代码: 0xc0000409 错…...

java-web-day5

1.spring-boot-web入门 目标: 开始最基本的web应用的构建 使用浏览器访问后端, 后端给浏览器返回HelloController 流程: 1.创建springboot工程, 填写模块信息, 并勾选web开发的相关依赖 注意: 在新版idea中模块创建时java下拉框只能选17, 21, 23 这里选17, maven版本是3.6.3, 很…...

Python | Leetcode Python题解之第508题出现次数最多的子树元素和

题目&#xff1a; 题解&#xff1a; class Solution:def findFrequentTreeSum(self, root: TreeNode) -> List[int]:cnt Counter()def dfs(node: TreeNode) -> int:if node is None:return 0sum node.val dfs(node.left) dfs(node.right)cnt[sum] 1return sumdfs(r…...

Java 分布式缓存

在当今的大规模分布式系统中&#xff0c;缓存技术扮演着至关重要的角色。Java 作为一种广泛应用的编程语言&#xff0c;拥有丰富的工具和框架来实现分布式缓存。本文将深入探讨 Java 分布式缓存的概念、优势、常见技术以及实际应用案例&#xff0c;帮助读者更好地理解和应用这一…...

【MySQL】MySQL 使用全教程

MySQL 使用全教程 介绍 MySQL 是一种广泛使用的开源关系型数据库管理系统(Relational Database Management System)&#xff0c;它基于 Structured Query Language&#xff08;SQL&#xff09;进行数据管理&#xff0c;允许用户存储、检索、更新和删除数据库中的数据。通过提供…...

油猴脚本-GPT问题导航侧边栏增强版

为 GPT官网和相关网站提供了一个便捷的侧边栏目录&#xff0c;能够自动搜集当前会话页面的问题&#xff0c;展示在侧边栏上&#xff0c;可快速导航到问题的位置。 安装使用地址:https://scriptcat.org/zh-CN/script-show-page/1972 安装前请确保浏览器有油猴&#xff0c;没有…...

Java Lock ConditionObject 总结

前言 相关系列 《Java & Lock & 目录》&#xff08;持续更新&#xff09;《Java & Lock & ConditionObject & 源码》&#xff08;学习过程/多有漏误/仅作参考/不再更新&#xff09;《Java & Lock & ConditionObject & 总结》&#xff08;学习…...

模块化主动隔振系统市场规模:2023年全球市场规模大约为220.54百万美元

模块化主动隔振系统是一种用于精密设备和实验装置的隔振解决方案&#xff0c;通过主动控制技术消除振动干扰&#xff0c;提供稳定的环境。目前&#xff0c;随着微纳制造和精密测量技术的发展&#xff0c;对隔振系统的要求越来越高。模块化设计使得系统能够灵活适应不同负载和工…...

SpringAOP:对于同一个切入点,不同切面不同通知的执行顺序

目录 1. 问题描述2. 结论结论1&#xff1a;"对于同一个切入点&#xff0c;同一个切面不同类型的通知的执行顺序"结论2&#xff1a;"对于同一个切入点&#xff0c;不同切面不同类型通知的执行顺序" 3. 测试环境&#xff1a;SpringBoot 2.3.4.RELEASE测试集合…...

unique_ptr初始化

std::unique_ptr 是 C11 引入的智能指针&#xff0c;用于管理动态分配的对象的生命周期。unique_ptr 确保每个动态分配的对象有且仅有一个所有者&#xff0c;当 unique_ptr 超出作用域时&#xff0c;它会自动释放其管理的对象。以下是 std::unique_ptr 的一些常见初始化方法。 …...

HelloCTF [RCE-labs] Level 8 - 文件描述和重定向

开启靶场&#xff0c;打开链接&#xff1a; GET传参cmd system($cmd.">/dev/null 2>&1"); 这行代码将执行命令 $cmd&#xff0c;并且将其标准输出和标准错误输出都重定向到 /dev/null&#xff0c;这意味着无论命令的输出还是可能产生的错误信息都不会显示…...

DEVOPS: 集群伸缩原理

概述 阿里云 K8S 集群的一个重要特性&#xff0c;是集群的节点可以动态的增加或减少有了这个特性&#xff0c;集群才能在计算资源不足的情况下扩容新的节点&#xff0c;同时也可以在资源利用 率降低的时候&#xff0c;释放节点以节省费用理解实现原理&#xff0c;在遇到问题的…...

什么是SMO算法

SMO算法&#xff08;Sequential Minimal Optimization&#xff09; 是一种用于求解 支持向量机&#xff08;SVM&#xff09; 二次规划对偶问题的优化算法。它由 John Platt 在 1998 年提出&#xff0c;目的是快速解决 SVM 的优化问题&#xff0c;特别是当数据集较大时&#xff…...

MySQL根据.idb数据恢复脚本,做成了EXE可执行文件

文章目录 1.代码2.Main方法打包3.Jar包打成exe可执行文件4.使用&#xff08;1.&#xff09;准备一个表结构一样得数据库&#xff08;2.&#xff09;打开软件&#xff08;3.&#xff09;输入路径 5.恢复成功 本文档只是为了留档方便以后工作运维&#xff0c;或者给同事分享文档内…...

Spring Boot面试题

1.什么是SpringBoot&#xff1f;它的主要特点是什么&#xff1f; Spring Boot 是一个基于 Spring 框架的开发和构建应用程序的工具&#xff0c;它旨在简化 Spring 应用的初始搭建和开发过程。Spring Boot 提供了一种约定优于配置的方式&#xff0c;通过自动配置和默认值&#…...

原生页面引入Webpack打包JS

Webpack简介 概述&#xff1a; Webpack是一个现代JavaScript应用程序的静态模块打包器。它将应用程序中的每个文件视为一个模块&#xff0c;并通过配置规则来解析这些模块之间的依赖关系&#xff0c;最终将其打包成一个或多个浏览器可以执行的文件。动态加载&#xff08;Code …...

健康之路押注医药零售:毛利率下滑亏损扩大,医疗咨询人次大幅减少

《港湾商业观察》黄懿 2024年9月13日&#xff0c;健康之路股份有限公司&#xff08;下称“健康之路”&#xff09;再次递表港交所&#xff0c;建银国际为独家保荐人。健康之路国内运营主体为健康之路&#xff08;中国&#xff09;信息技术有限公司和福建健康之路信息技术有限公…...

【人工智能-初级】第7章 聚类算法K-Means:理论讲解与代码示例

文章目录 一、K-Means聚类简介二、K-Means 聚类的工作原理2.1 初始化簇中心2.2 分配簇标签2.3 更新簇中心2.4 迭代重复2.5 K-Means 算法的目标三、K-Means 聚类的优缺点3.1 优点3.2 缺点四、K 值的选择五、Python 实现 K-Means 聚类5.1 导入必要的库5.2 生成数据集并进行可视化…...

HOT 100 技巧题(136/169/75/31/287)

136. 只出现一次的数字 技巧类型题目&#xff0c;通过异或运算实现 169. 多数元素 三种常见解法&#xff1a;1. 哈希2. 排序3. 投票法 75. 颜色分类 单指针 两次遍历&#xff1a;第一次遍历把所有0都交换到前面&#xff0c;记录最后一个0的位置index&#xff0c;第二次遍…...

网站运营专员是干嘛的/怎么让客户主动找你

下面的这些经典的引言来自英文&#xff0c;也许有些我翻译的是不很好&#xff0c;所以&#xff0c;我提供了中英对照&#xff0c;如果有问题&#xff0c;请大家指正。 过早的优化是万恶之源。Premature optimization is the root of all evil!- Donald Knuth 在水里行走和以一个…...

网站建设公司用的什么后台/合肥关键词排名工具

Windbg工作中用的不多&#xff0c;所以命令老是记不住&#xff0c;每次使用都要重新查命令&#xff0c;挺烦。趁这次培训的机会好好测试和总结了一下&#xff0c;下次再用就方便多了。在这里一起共享一下&#xff0c;如果有错误&#xff0c;请指正。基本知识和常用命令 &#x…...

网站友情链接模板/在线数据分析工具

并没有人动到/etc/my.cnf这个配置,可能有人动到了权限相关表... 问题: mysql连接时错误信息:Cant get hostname for your address 即navicat就连不上. 原因不必找了,解决问题: [mysqld] skip-name-resolve #加上这一个属性后重启mysql服务就可以了. /etc/init.d/mysql resta…...

网站设计代做/市场调研报告模板ppt

一、简介 美国国家漏洞数据库收集了操作系统&#xff0c;应用软件的大量漏洞信息&#xff0c;当有新的漏洞出现时&#xff0c;它也会及时发布出来&#xff0e; 由于信息量巨大&#xff0c;用户每次都需要到它的网站进行搜索&#xff0c;比较麻烦&#xff0e;如果能有个工具&…...

企业网站建设需要哪些费用/推广点击器

在上篇文章写到我们为什么要分层.有很多读者提出来很多宝贵的意见.让我受益匪浅,深深的感觉到自己的水平"还有很大的提升空间".首先感谢这些朋友们,我会进一步总结完善自己的想法. 截取了部分朋友的留言,感谢他们: 这次我用对比的方式描述一下,分层到底分出了什么.俗…...

建一个网站都需要什么/seo搜索引擎优化实训总结

合并效果图 Bootstrap其他请求的属性我们就不写了 和平常的一样 重点是columns var columns [ [ { field : merchantId, title : 商户ID, align : center, colspan : 1, rowspan : 2 …...