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图片并显示
- 当HTML页面中引用了图片
接着就是对动态请求的处理。先循环遍历路由表表里面正则表达式是否匹配请求对象的资源路径,以找到对应的处理函数填充响应对象,如果找不到就是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(未找到)
}
接着就是把响应对象转换成响应报文并发送
// 把应答对象转化成应答报文并发送
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响应
}
疑问点
std::regex确实不能直接用作std::unordered_map的键
http不是管理的是协议的请求和响应吗 这个 void OnConnect(const PtrConnection& conn) 连接不是应该由tcp去处理吗?
“请求可能分多个TCP包到达,需要有地方存储部分解析的数据,这个地方就是上下文” 那这个数据不是存储在用户的缓冲区当中的吗?
HttpContext *context = conn->GetContext()->get<HttpContext>(); 这个代码不懂
相关文章:
Muduo网络库实现 [十六] - HttpServer模块
设计思路 本模块就是设计一个HttpServer模块,提供便携的搭建http协议的服务器的方法。那么这个模块需要如何设计呢? 这还需要从Http请求说起。 首先从http请求的请求行开始分析,请求行里面有个方法。分为静态资源请求和功能性请求的。 静态…...
工业触摸显示器助力智慧工业实验室发展
工业触摸显示器作为智慧工业实验室的核心人机交互设备,凭借其卓越的性能和灵活性,为实验室的智能化、自动化发展提供了强有力的支持。以下从多个方面阐述工业触摸显示器如何助力智慧工业实验室的发展: 一、提升操作便捷性与效率 直观操作&a…...
k8s介绍与实践
第一节 理论 基础介绍,部署实践,操作实践,点击这里学习 第二节 dashboard操作 查看安装的dashboard服务信息 kubectl get pod,svc -n kubernetes-dashboard 网页登录地址:https://server_ip:30976/#/login 创建token kube…...
ATEngin开发记录_5_C++日志打印引发的崩溃?一次虚函数调用引发的内存错误排查记录
该系列只做记录 不做教程 所以文章简洁直接 会列出碰到的问题和解决方案 只适合C萌新 在使用 C 进行事件系统开发时,我遇到了一次由于调用虚函数 GetName() 输出日志而引发的崩溃问题。通过逐步排查、使用防御性编程和类型检查,最终定位到了隐藏的生命…...
Yocto项目实战教程 · 第4章:4.2小节-菜谱
🔍 B站相应的视频教程: 📌 Yocto项目实战教程-第4章-4.2小节-菜谱 记得三连,标为原始粉丝。 在 Yocto 项目中,**菜谱(Recipe)**承载了包的配置信息、源码获取方式、编译与安装步骤,是…...
7.Rust+Axum:打造高效 RESTful API 的最佳实践
摘要 深入探讨 RustAxum 开发 RESTful API 的关键要点,涵盖资源路由设计、HATEOAS 实现、参数处理及 DTO 序列化与 JSON 处理案例。 一、引言 在现代 Web 开发中,RESTful API 是构建分布式系统的重要组成部分。Rust 作为一种高性能、安全的系统编程语…...
CAN总线嵌入式开发实战:从入门到精通
CAN总线嵌入式开发实战:从入门到精通 一、CAN总线基础概念 CAN(Controller Area Network)是一种广泛应用于汽车电子和工业控制领域的串行通信协议,由Bosch公司于1986年开发。它具有以下核心特点: 多主架构:所有节点地位平等&am…...
从头学 | 目标函数、梯度下降相关知识笔记(一)
很多基本的概念最近忘的有点多,简单回顾一些 文章目录 1 目标函数、梯度下降1.1 回归模型中的目标函数1.1.1 回归任务目标函数(1) 均方误差(MSE)(2) Huber损失 1.1.2 分类任务目标函数(1) 交叉熵损失(Cross-Entropy)(2…...
欣佰特携数十款机器人相关前沿产品,亮相第二届人形机器人和具身智能行业盛会
2025年4月15日至16日,备受关注的第二届中国人形机器人与具身智能产业大会已在北京成功举行。作为国内前沿科技及产品服务领域的重要参与者,欣佰特科技携众多前沿产品精彩亮相,全方位展示了其在人形机器人与具身智能领域的创新产品。 在本次大…...
QT常见显示类控件及其属性
Label QLabel可用用来显示文本和图片 核心属性如下 文本格式---textFormat 例: 在ui界面创建3个label,分别用不同的显示格式 在构造函数进行文本格式和文内容设置 此时运行后三种显示格式无区别 可以给富文本加标签 如<B>表示加粗 如果将<…...
基于领域知识的A型主动脉夹层综合分割及面向临床的评估|文献速递-深度学习医疗AI最新文献
Title 题目 Domain knowledge based comprehensive segmentation of Type-A aortic dissection with clinically-oriented evaluation 基于领域知识的A型主动脉夹层综合分割及面向临床的评估 01 文献速递介绍 A型主动脉夹层(TAAD)是一种医疗急症&a…...
守护进程编程、GDB调试以及外网连接树莓派
目录 一、什么是守护进程以及如何创建守护进程1. 什么是守护进程?2. 如何创建守护进程? 二、什么是GDB调试以及如何用GDB命令调试C程序1. 什么是GDB?2. 如何用GDB命令调试C程序? 三、外网访问树莓派 一、什么是守护进程以及如何创…...
HTML理论题
1.什么是HTML? 超文本标记语言(英语:HyperText Markup Language,简称:HTML)是一种用于创建网页的标准标记语言。 2.DOCTYPE 的作用是什么?标准与兼容模式(混杂模式)各有什么区别? DOCTYPE 的作用是告知浏览器的解析器用什么文档标准解析这个文档。 标准模式:用于…...
Cables为链上社区树立标杆:专注于实用性、用户主权与全球流动性
在 Web3 世界,“社区”这个词已逐渐沦为炒作、虚高数据与短暂参与的代名词。Cables 正在颠覆这一现状,以真实贡献与长期可持续发展为核心,走出了一条独特的路径。 最近,Cables 推出了其核心长期战略之一——积分计划(…...
Clickhouse 配置参考
Clickhouse 配置参考 适用版本 21.3.9.84 config.xml 配置 <?xml version"1.0"?> <!--NOTE: User and query level settings are set up in "users.xml" file. --> <yandex><access_control_path>/data/clickhouse/clickhous…...
thinkphp实现图像验证码
示例 服务类 app\common\lib\captcha <?php namespace app\common\lib\captcha;use think\facade\Cache; use think\facade\Config; use Exception;class Captcha {private $im null; // 验证码图片实例private $color null; // 验证码字体颜色// 默认配置protected $co…...
【Pandas】pandas DataFrame where
Pandas2.2 DataFrame Indexing, iteration 方法描述DataFrame.head([n])用于返回 DataFrame 的前几行DataFrame.at快速访问和修改 DataFrame 中单个值的方法DataFrame.iat快速访问和修改 DataFrame 中单个值的方法DataFrame.loc用于基于标签(行标签和列标签&#…...
redis利用备忘录
fofa: icon_hash"864611937" 防护: redis的安全设置:设置完毕,需要重加载配置文件启动redis 1.绑定内网ip地址进行访问 2. requirepass设置redis密码 3.保护模式开启protected-mode开启(默认开启) 4.最好把…...
【dify实战】chatflow结合deepseek实现基于自然语言的数据库问答、Echarts可视化展示、Excel报表下载
dify结合deepseek实现基于自然语言的数据库问答、Echarts可视化展示、Excel报表下载 观看视频,您将学会 在dify下如何快速的构建一个chatflow,来完成数据分析工作;如何在AI的回复中展示可视化的图表;如何在AI 的回复中加入Excel报…...
医疗行业如何构建合成数据平台?——技术、合规与实践全景
目录 医疗行业如何构建合成数据平台?——技术、合规与实践全景 一、为什么医疗领域尤需合成数据? 二、平台功能全景图 ✅ 模块划分: 三、典型合成数据生成方式 1. 结构化病例合成 2. 医学图像生成 3. 多轮医生-患者问答合成 四、数据…...
6.8.最小生成树
一.复习: 1.生成树: 对于一个连通的无向图,假设图中有n个顶点,如果能找到一个符合以下要求的子图: 子图中包含图中所有的顶点,同时各个顶点保持连通, 而且子图的边的数量只有n-1条࿰…...
MATLAB 控制系统设计与仿真 - 37
范数鲁棒控制器的设计 鲁棒控制器的设计 根据双端子状态方程对象模型结构,控制器设计的目标是找到一个控制器K(s),它能保证闭环系统的范数限制在一个给定的小整数下,即 这时控制器的状态方程为: 其中X与Y分别为下面两个代数Riccati方程的解…...
社交媒体时代的隐私忧虑:聚焦Facebook
在数字化时代,社交媒体平台已成为人们日常生活的重要组成部分。Facebook作为全球最大的社交媒体之一,拥有数十亿用户,其对个人隐私的影响和忧虑也日益凸显。本文将探讨社交媒体时代下,尤其是Facebook平台上的隐私问题。 数据收集…...
9.Rust+Axum 测试驱动开发与性能优化全攻略
摘要 本文深入讲解 RustAxum 测试驱动开发及性能优化,涵盖多种测试工具与优化技术。 一、引言 在当今的软件开发领域,测试驱动开发(TDD)和性能优化是保障软件质量和性能的关键环节。Rust 作为一种安全、高效的系统编程语言&…...
中国首个全国34省3,667个城市多属性建筑数据集(屋顶、高度、结构、功能、风格、年龄、质量等属性)
中国首个全国34省3,667个城市多属性建筑数据集(屋顶、高度、结构、功能、风格、年龄、质量等属性) 数据介绍 快速获取三维(3D)建筑数据,包括屋顶、高度和方向等几何属性,以及功能、质量和年龄等指示性属性…...
探索Spring Boot Web模块:设计思想与技术实现
探索Spring Boot Web模块:设计思想与技术实现 在现代Web开发中,Spring Boot作为一个强大的框架,提供了丰富的功能来简化Web应用的开发。本文将深入探讨Spring Boot框架中Web模块的核心类,揭示其设计思想、技术实现以及扩展知识。…...
GitHub创建远程仓库
使用GitHub创建远程仓库:从零开始实现代码托管与协作 前言 在当今软件开发领域,版本控制系统已成为开发者必备的核心工具。作为分布式版本控制系统的代表,Git凭借其强大的分支管理和高效的协作能力,已成为行业标准。而GitHub作为…...
PLM系统如何支持利益相关者分析?沟通矩阵设计
PLM(产品生命周期管理)系统在现代企业的产品研发与管理过程中扮演着至关重要的角色。它不仅仅是一个管理产品数据的工具,更能在利益相关者分析以及沟通矩阵设计方面提供强大的支持。通过合理运用PLM系统,企业能够更好地识别、理解…...
每日一题(8) 求解矩阵最小路径和问题
给定一个m行n列的矩阵,从左上角开始每次只能向右或者向下移动,最后到达右下角的位置,路径上的所有数字累加起来作为这条路径的路径和。求所有路径和中最小路径和。 输入格式: 首先输入行数m及列数n,接下来输入m行,每…...
JAVA设计模式:注解+模板+接口
1.基础组件 1.1注解类控制代码执行启动、停止、顺序 /*** author : test* description : 数据同步注解* date : 2025/4/18*/ Target({ElementType.TYPE}) Retention(RetentionPolicy.RUNTIME) Documented public interface SyncMeta {/*** 执行服务名称* return*/String name…...
如何在Linux系统中部署C++ Web应用
在 Linux 上部署 C Web 应用,和部署传统的 PHP 或 Node.js 应用相比更“原生”一些,通常涉及到自己编译、配置 Web 服务、处理依赖等。本文将详细讲解部署一个基于 C 编写的 Web 应用的完整流程,涵盖从构建、部署、到上线的每一步,…...
实用工具-screenrec介绍(截图工具)
官方地址:Communicate Faster with Instant Video Messages & Screenshots 官方下载安装包,安装完成后,默认快捷键 alt s 开启截图,录屏 介绍 ScreenRec 是一款免费无广告的屏幕录制与截图工具,支持多平台&…...
使用veaury,在vue项目中运行react组件
网上的信息太少了,记录一下 我的项目是vue3webpack 使用:veaury Veaury 是基于React和Vue3的工具库,主要用于React和Vue在一个项目中公共使用的场景,主要运用在项目迁移、技术栈融合的开发模式、跨技术栈使用第三方组件的场景。 参…...
开源 vs. 闭源:大模型的未来竞争格局
开源 vs. 闭源:大模型的未来竞争格局 引言 在人工智能领域,尤其是大型语言模型(LLM)的发展中,开源与闭源之争已成为决定行业未来走向的关键议题。随着ChatGPT的横空出世和开源模型的蓬勃发展,技术社区正经历着一场深刻的范式转变…...
pcl代码解析
一、库基础代码解析: PCL库基础:点云类型与算法详解-CSDN博客 主要介绍PCL库的一些基本的点云类型、相关数据类型以及ROS接口消息,和一些常用的算法。 用到的一些PCL点云类型 pcl::PointXYZ: 这是最简单也可能是最常用到的点类型;它只储存…...
中华传承-医山命相卜-梅花易数
梅花易数 灵活起卦(如数字、声音、外应等)和象数结合,准确率可达96.8%。其起卦方式摆脱传统龟壳、蓍草的繁琐,强调直觉与灵活性。 个人决策、事件预测等 尤其在短期、具体问题上表现突出。...
HOOPS Exchange 与HOOPS Communicator集成:打造工业3D可视化新标杆!
一、概述 在工业3D开发、BIM建筑、数字孪生和仿真分析等高端应用场景中,数据格式复杂、模型体量庞大、实时交互体验要求高,一直是困扰开发者的难题。Tech Soft 3D旗下的HOOPS Exchange和HOOPS Communicator,正是解决这类问题的黄金搭档。二者…...
SQL预编译——预编译真的能完美防御SQL注入吗
SQL注入原理 sql注入是指攻击者拼接恶意SQL语句到接受外部参数的动态SQL查询中,程序本身 未对插入的SQL语句进行过滤,导致SQL语句直接被服务端执行。 拼接的SQL查询例如,通过在id变量后插入or 11这样的条件,来绕过身份验证&#…...
通过 Zotero 的样式编辑器(Style Editor)自定义文献引用和参考文献列表的格式
好的!以下是一个更为详细的教程,帮助你通过 Zotero 的样式编辑器(Style Editor)自定义文献引用和参考文献列表的格式。 详细教程:使用 Zotero 样式编辑器自定义文献格式 1. 准备工作 在开始之前,请确保&a…...
PostgreSQL 通过 copy 命令导入几何数据 及 通过 CopyManager.copyIn() 导入几何数据
COPY命令介绍 copy是postgresql提供的一个专门用于快速导入导出数据的命令,通常用于从文件(TXT、CSV等)或标准输入输出中读取或写入数据。适合批量导入导出数据,速度快。 默认情况下,如果在处理过程中遇到错误,COPY将失败。 COPY只能用于表,不能用于视图!!! COPY…...
Next.js 技术详解:构建现代化 Web 应用的全栈框架
1. Next.js 概述 Next.js 是一个基于 React 的全栈框架,由 Vercel 团队开发和维护。它提供了一系列开箱即用的功能,使开发者能够快速构建高性能的 Web 应用。 核心优势 服务端渲染 (SSR)静态站点生成 (SSG)增量静态再生成 (ISR)文件系统路由API 路由图…...
【unity实战】Unity动画层级(Animation Layer)的Sync同步和Timing定时参数使用介绍,同步动画层制作角色的受伤状态
文章目录 前言方案一:复制粘贴原有层级的状态机1、实现2、问题 方法二:勾选Sync同步动画层1、简单实现同步2、同步blend tree的问题3、动画状态的播放时长4、下层状态覆盖了上层状态 专栏推荐完结 前言 如何制作角色的受伤状态? 玩家角色在…...
NFC 碰一碰发视频源码搭建,碰一碰发视频定制化开发技术
在移动互联时代,便捷的数据传输方式备受青睐。NFC(近场通信)技术以其操作简单、连接迅速的特性,为设备间的数据交互提供了高效解决方案。通过搭建 NFC 碰一碰发视频功能,用户只需将支持 NFC 的设备轻轻靠近,…...
获取视频封面
目录 实现方式注意事项代码实现 实现方式 通过 video 元素canvas 元素的方式实现 生成 video 和 canvas 元素当 video 元素资源加载完成时,将 video 元素绘制到 canvas 画布上,然后通过 toBlob 或则 toDataURL 获取到对应的封面图片资源 注意事项 vid…...
c#开发大冲锋游戏登录器
1 前言 本文主要分享登录器的简要开发过程,只适合小白选手,高手请自动避让。 此项目是复刻大冲锋计划中的子集。 (注:大冲锋是迅雷代理的一款次时代多职业第一人称FPS射击游戏,目前已经关服嗝屁。) 2 …...
堆的实现以及利用堆进行排序
堆 堆的实现1. 什么是堆?2. 最小堆的核心操作2.1 初始化堆2.2 销毁堆2.3 插入元素2.4 删除堆顶元素2.5 获取堆顶元素2.6 判断堆是否为空 3. 调整堆的算法3.1 向上调整3.2 向下调整 4. 测试代码 堆排序一.向下调整建堆二.向上调整建堆 时间复杂度分析向上建堆分析&am…...
FPGA-VGA
目录 前言 一、VGA是什么? 二、物理接口 三、VGA显示原理 四、VGA时序标准 五、VGA显示参数 六、模块设计 七、波形图设计 八、彩条波形数据 前言 VGA的FPGA驱动 一、VGA是什么? VGA(Video Graphics Array)是IBM于1987年推出的…...
仿腾讯会议项目开发——界面关闭功能实现
目录 1、include(./netapi/netapi.pri) 2、加快构建速度 3、INCLUDEPATH./netapi 4、添加控制类 5、用单例模式创建一个Ckernel的对象 6、创建一个回收的槽函数 7、添加界面文件 8、创建一个私有的界面对象 9、修改为使用单例模式的控制类创建界面 10、在Ckernel类中…...
微信小程序怎么分包步骤(包括怎么主包跳转到分包)
第一步 主包跳转到分包 第一步 第二步...
点云配准控制迭代停止的阈值
在点云配准(如ICP算法)中,setEuclideanFitnessEpsilon() 是一个设置收敛条件的函数,用于控制迭代停止的阈值。以下是关于该参数的详细说明: 函数作用 setEuclideanFitnessEpsilon() 设置的是 两次连续迭代之间均方误…...