20.添加HTTP模块
添加一个简单的静态HTTP。
这里默认读者是熟悉http协议的。
来看看http请求Request的例子
客户端发送一个HTTP请求到服务器的请求消息,其包括:请求行、请求头部、空行、请求数据。

HTTP之响应消息Response
服务器接收并处理客户端发过来的请求后会返回一个HTTP的响应消息,其包括:状态行、消息报头、空行和响应正文。


前面所说的就是http的请求和响应答复。那我们可以封装出两个类。
HttpRequest:http请求类封装
HttpResponse:http响应类封装
注意:这里会使用到我们之前写的Buffer类。因为服务器是把读到的数据存储在Buffer中的,所以大家要熟悉Buffer类的一些用法。
1、HttpRequest 类
该类的主要作用是客户端发送请求,服务端收到的数据存放于Buffer中,之后解析成HttpRequest请求对象,调用成员函数设置请求头、请求体等。
首先会有请求方式method_,http版本version_,请求头headers_(用map管理)。请求的路径path_(即是url),还有请求体query_。
请求体有可能是在url中的"?"后面,也可能是在请求头后面的。
class HttpRequest
{
public:enum class Method{kInvalid, kGet, kPost, kHead, kPut, kDelete};enum class Version{kUnknown, kHttp10, kHttp11};HttpRequest():method_(Method::kInvalid),version_(Version::kUnknown){}void setVersion(Version v) { version_ = v; }Version getVersion()const { return version_; }bool setMethod(const char* start, const char* end){string m(start, end);if (m == "GET") {method_ = Method::kGet;}else if (m == "POST") {method_ = Method::kPost;}//省略"HEAD","DELETE"等等方式......return method_ != Method::kInvalid;}Method getMothod()const { return method_; }const char* methodString()const {const char* result = "UNKNOWN";switch (method_) {case Method::kGet:result = "GET";break;case Method::kPost:result = "POST";break;//省略"HEAD","DELETE"等等方式......}return result;}void setPath(const char* start, const char* end) {path_.assign(start, end);}const string& path()const { return path_; }void setQuery(const char* start, const char* end) {query_.assign(start, end);}const string& query()const { return query_; }void addHeader(const char* start, const char* colon, const char* end){//isspace(int c)函数判断字符c是否为空白符//说明:当c为空白符时,返回非零值,否则返回零。(空白符指空格、水平制表、垂直制表、换页、回车和换行符。// 要求冒号前无空格string field(start, colon);++colon;while (colon < end && isspace(*colon))// 过滤冒号后的空格++colon;string value(colon, end);while (!value.empty() && isspace(value[value.size() - 1]))//过滤value中的空格value.resize(value.size() - 1);headers_[field] = value;}string getHeader(const string& field)const{string result;auto it = headers_.find(field);if (it != headers_.end()) {return it->second;}return result;}const std::unordered_map<string, string>& headers()const { return headers_; }private:Method method_;Version version_;string path_; //请求路径string query_; //请求体std::unordered_map<string, string> headers_;
};
注意:添加请求头时,函数addHeader需要删除键值对的字符串左侧和右侧的空字符,保证解析正常。因为解析请求头时,对一行字符串用冒号“:”进行分割解析。
2、HttpResponse 类
服务器端得到的客户的请求信息后,再创建一个HttpResponse响应对象,也是会调用成员函数设置响应头部、响应体,并格式化到Buffer中,回复给客户端。
按照上面的响应例子,那应该有响应头headers_,响应的状态码statusCode_,状态码的文字描述statusMessage_,响应体body_等等。
成员函数就是一些设置状态码,设置响应头等操作。
class HttpResponse
{
public:enum class HttpStatusCode{kUnknown,k200Ok = 200,k301MovedPermanently = 301,k400BadRequest = 400,k404NotFound = 404,};explicit HttpResponse(bool close):statusCode_(HttpStatusCode::kUnknown),closeConnection_(close){}void setStatusCode(HttpStatusCode code) { statusCode_ = code; }void setstatusMessage(const string& message) { statusMessage_ = message; }void setCloseConnection(bool on) { closeConnection_ = on; }bool closeConnection()const { return closeConnection_; }void setContentType(const string& contentType) { addHeader("Content-Type", contentType); }void addHeader(const string& key, const string& value) {headers_[key] = value;}void setBody(const string& body) { body_ = body; }void appendToBuffer(Buffer* output)const;private:std::unordered_map<string, string> headers_;HttpStatusCode statusCode_; //状态码string statusMessage_; //响应行中的状态码文字描述bool closeConnection_; //是否关闭连接string body_; //响应体
};
这里特别值得一说的是如何把响应消息格式化的操作格式化appendToBuffer(Buffer* output)。
该函数默认使用HTTP1.1版本,按照HTTP协议对HttpResponse对象进行格式化输出到Buffer中。
按照要求添加响应行,响应头,空行,响应体。
void HttpResponse::appendToBuffer(Buffer* output) const
{//响应行string buf = "HTTP/1.1 " + std::to_string(static_cast<int>(statusCode_));output->append(buf);output->append(statusMessage_);output->append("\r\n");//响应头部if (closeConnection_) {output->append("Connection: close\r\n");}else {output->append("Connection: Keep-Alive\r\n");buf = "Content-Length:" + std::to_string(body_.size()) + "\r\n";output->append(buf);}for (const auto& header : headers_) {buf = header.first + ": " + header.second + "\r\n";output->append(buf);}output->append("\r\n"); //空行output->append(body_); //响应体
}
3、HttpContext 类
服务端接收客户请求,存在Buffer中,那怎么从Buffer中解析得到我们想要的信息呢。这时,需要一个解析类HttpContext,解析后数据封装到回复HttpRequest中。
其成员有处理的状态state_,响应request_。
class HttpContext
{
public:enum class HttpRequestPaseState{kExpectRequestLine, //请求行kExpectHeaders, // 请求头kExpectBody, // 请求体kGotAll, //表示都处理完全};HttpContext():state_(HttpRequestPaseState::kExpectRequestLine)//默认从请求行开始解析{}bool parseRequest(Buffer* buf);// 解析请求Bufferbool gotAll()const { return state_ == HttpRequestPaseState::kGotAll; }void reset()// 为了复用HttpContext{state_ = HttpRequestPaseState::kExpectRequestLine;HttpRequest dumy;request_.swap(dumy);}const HttpRequest& request() const{ return request_; }HttpRequest& request(){ return request_; }private:bool processRequestLine(const char* begin, const char* end);HttpRequestPaseState state_; //需要处理的状态,状态机HttpRequest request_;
};
一个正常的请求,一般至少是有请求行的,默认解析状态为kExpectRequestLine。
这里就主要关注是如何解析Buffer的。
3.1、请求解析 parseRequest(Buffer* buf)
这里为了方便找到buf中的"\r\n",添加了Buffer::findCRLF()函数。
const char Buffer::kCRLF[] = "\r\n";//为了方便解析http "\r\n"位置
const char* findCRLF()const {const char* crlf = std::search(peek(), beginWirte(), kCRLF, kCRLF + 2);return crlf == beginWirte() ? nullptr : crlf;
}
传入需要解析的Buffer对象,根据期望解析的部分(即是状态state_)进行处理。
处理就三种情况:请求行,请求头,请求体。具体的流程可以看代码
bool HttpContext::parseRequest(Buffer* buf)
{bool ok = true;bool hasMore = true;while (hasMore) {if (state_ == HttpRequestPaseState::kExpectRequestLine) { //解析请求行//查找出buf中第一次出现"\r\n"位置const char* crlf = buf->findCRLF();if (crlf) {//若是找到"\r\n",说明至少有一行数据,可以进行解析//buf->peek()为数据开始部分ok = processRequestLine(buf->peek(), crlf);if (ok) {//解析成功buf->retrieveUntil(crlf + 2);//buf->peek()向后移动2字节,到下一行state_ = HttpRequestPaseState::kExpectHeaders;}else {hasMore = false;}}else {hasMore = false;}}else if (state_ == HttpRequestPaseState::kExpectHeaders) {const char* crlf = buf->findCRLF(); //找到"\r\n"位置if (crlf) {const char* colon = std::find(buf->peek(), crlf, ':');//定位分隔符if (colon != crlf) {request_.addHeader(buf->peek(), colon, crlf); //添加键值对}else {/*state_ = HttpRequestPaseState::kGotAll;hasMore = false;*/state_ = HttpRequestPaseState::kExpectBody;//这样就可以解析body}buf->retrieveUntil(crlf + 2); //后移动2字节}else {hasMore = false;}}else if (state_ == HttpRequestPaseState::kExpectBody) {//解析请求体if (buf->readableBytes()) {//表明还有数据,那就是请求体request_.setQuery(buf->peek(), buf->beginWirte());}state_ = HttpRequestPaseState::kGotAll;hasMore = false;}}return ok;
}
3.1、请求行的解析 processRequestLine()
请求行有固定格式Method URL Version \r\n,URL中可能带有请求参数。根据空格符进行分割成三段字符。URL可能带有请求参数,使用"?”分割解析。
bool HttpContext::processRequestLine(const char* begin, const char* end)
{bool succeed = true;const char* start = begin;const char* space = std::find(start, end, ' ');//第一个空格前的字符串是请求方法 例如:postif (space != end && request_.setMethod(start, space)) {start = space + 1;space = std::find(start, end, ' ');//寻找第二个空格 urlif (space != end) {const char* question = std::find(start, space, '?');if (question != space) {// 有"?",分割成path和请求参数request_.setPath(start, question);request_.setQuery(question, space);}else {request_.setPath(start, space);//只有path}//最后一部分,解析http协议版本string version(space + 1, end);if (version == "HTTP/1.0")request_.setVersion(HttpRequest::Version::kHttp10);else if (version == "HTTP/1.1")request_.setVersion(HttpRequest::Version::kHttp11);elsesucceed = false;}}return succeed;
}
这样解析就完成了。
4、HttpServer类
为了可以方便使用,封装个HttpServer类。
该类内部会有Server类型成员,并提供了一个回调函数的接口,当服务器收到http请求时,调用客户端的处理函数进行处理。
HttpServer支持多线程,也可以使用单线程。
class HttpServer
{
public:using HttpCallback = std::function<void(const HttpRequest&, HttpResponse*)>;HttpServer(EventLoop* loop, const InetAddr& listenAddr);void setHttpCallback(const HttpCallback& cb) { httpCallback_ = cb; }void start(int numThreads);private:void onConnetion(const ConnectionPtr& conn); //连接到来的回调函数void onMessage(const ConnectionPtr& conn, Buffer* buf); //消息到来的回调函数void onRequest(const ConnectionPtr& conn, const HttpRequest&);Server server_;HttpCallback httpCallback_;};
函数setHttpCallback就是设置用户的业务处理回调函数的。
4.1HttpServer构造函数
//默认的回调函数
void defaultHttpCallback(const HttpRequest& req, HttpResponse* resp)
{resp->setStatusCode(HttpResponse::HttpStatusCode::k404NotFound);resp->setstatusMessage("Not Found");resp->setCloseConnection(true);
}
//构造函数
HttpServer::HttpServer(EventLoop* loop, const InetAddr& listenAddr):server_(listenAddr,loop), httpCallback_(defaultHttpCallback)
{//新连接到来回调该函数server_.setConnectionCallback([this](const ConnectionPtr& conn) {onConnetion(conn); });//消息到来回调该函数 server_.setMessageCallback([this](const ConnectionPtr& conn, Buffer* buf) {onMessage(conn, buf); });
}
这里就是初始化Server,并将HttpServer的回调函数传给Server。主要有两个函数。
前面的HttpResponse类和HttpRequest类已经在HttpServer使用了,但是解析类HttpContext还没有使用。
很容易想到是在回调函数中使用。在有消息到来的时候,就会进行解析数据,这时就会使用到HttpContext。可以在每次调用函数onMessage中创建HttpContext对象。这在短连接中使用是合适的。但是在长连接的情况下,这样可能效率不高。
那么就可以在有新连接到来的时刻,就设置好HttpContext。
那就说到onConnetion函数。
4.2 连接到来的回调函数onConnetion
//这里绑定一个HttpContext主要是为了长连接中仅分配一次对象,提高效率。
void HttpServer::onConnetion(const ConnectionPtr& conn)
{if (conn->connected()) {//conn->setContext(std::make_shared<HttpContext>()); //c++11的std::shared_ptr<void>conn->setContext(HttpContext()); //c++17的std::any}
}
该函数为一个新的Connection绑定一个HttpContext对象,绑定之后,HttpContext就相当于Connection的成员,TcpConection在MessageCallback中就可以随意的使用该HttpContext对象了。
这里绑定一个HttpContext主要是为了长连接中仅分配一次对象,提高效率。
这里绑定使用的是c++17的std::any。std::any表示可以接受任意类型的变量。
来看看Conntection类中需要添加的变量
#include<any>
class Connection:public std::enable_shared_from_this<Connection>
{
public://省略之前的变量和函数//void setContext(std::shared_ptr<void> context) { context_ = context; }//std::shared_ptr<void> getConntext()const { return context_; }void setContext(const std::any& context) { context_ = context; }std::any* getMutableContext() { return &context_; }
private://std::shared_ptr<void> context_; //c++11做法std::any context_; //用来解析http或者websocket或者其他协议的
};
首先我们要明确为什么要的是接收任意类型的变量这总做法,为什么不是直接就是用HttpContext类替代std::any。
因为我们后续可能还需要解析其他协议的,例如websockte协议(下一节会讲解)。要是直接写HttpContext的话,那要解析websocket协议的时候,Connection类中还需要添加websocketContext类成员变量,这就很麻烦的。所以用std::any来就可以绑定所有的解析类。
那又有疑惑,为什么不直接用void*?简单点说是,它类型不安全,还需要用户手动去delete。
std::shared_ptr和void*一样不能解决类型安全的问题。详细的了解可以查看该文章https://www.cnblogs.com/gnivor/p/12793239.html
那说完std::any和回调函数onConnetion,那就到函数onMessage。
4.3 新消息到来的回调函数onMessage
void HttpServer::onMessage(const ConnectionPtr& conn, Buffer* buf)
{//HttpContext* context = reinterpret_cast<HttpContext*>(conn->getConntext().get()); //c++11做法auto context = std::any_cast<HttpContext>(conn->getMutableContext()); //c++117if (!context) {LOG_ERROR<<"context is bad\n";return;}if (!context->parseRequest(buf)) {conn->send("HTTP/1.1 400 Bad Request\r\n\r\n");conn->shutdown();}if (context->gotAll()) {onRequest(conn, context->request());context->reset();//一旦请求处理完毕,重置context,因为HttpContext和Connection绑定了,我们需要解绑重复使用}
}
当Connection中所拥有的连接有新消息到来时,会调用它的messageCallback_函数,其实就是调用HttpServer的onMessage()函数。而之前在函数onConnection()中把HttpContext利用std::any绑定给了Connection,那在该函数中就可以对Connection使用HttpContext类来解析数据包了。
onMessage()函数首先调用HttpContext的parserRequset()函数解析请求,判断请求是否合法,进而选择关闭连接,或者处理请求(函数onRequest)。
4.4处理请求的函数onRequest
void HttpServer::onRequest(const ConnectionPtr& conn, const HttpRequest& req)
{const std::string& connetion = req.getHeader("Connection");bool close = connetion == "close" || (req.getVersion() == HttpRequest::Version::kHttp10 && connetion != "Keep-Alive");HttpResponse response(close);//执行用户注册的回调函数httpCallback_(req, &response);Buffer buf;response.appendToBuffer(&buf);conn->send(&buf);//发送数据if (response.closeConnection()) {conn->shutdown();}
}
先判断是长连接还是短连接。接着使用close构造一个HttpResponse对象。之后很重要的是执行用户注册的回调函数,这个就是用户的业务函数。
5.HtttpServer的用法
#include"src/Server.h"
//省略一些其他头文件//用户的业务处理的函数
void onRequest(const HttpRequest& req, HttpResponse* resp)
{if (req.path() == "/") {// 根目录请求resp->setStatusCode(HttpResponse::HttpStatusCode::k200Ok);resp->setstatusMessage("OK");resp->setContentType("text/html");resp->addHeader("Server", "li");resp->setBody("<html><head><title>This is title</title></head>""<body><h1>Hello</h1>Now is hello" "</body></html>");}else if (req.path() == "/hello") {resp->setStatusCode(HttpResponse::HttpStatusCode::k200Ok);resp->setstatusMessage("OK");resp->setContentType("text/plain");resp->setBody("hello, world!\n");}else {resp->setStatusCode(HttpResponse::HttpStatusCode::k404NotFound);resp->setstatusMessage("Not Found");resp->setCloseConnection(true);}
}int main(int argc, char* argv[])
{EventLoop loop;HttpServer server(&loop, InetAddr(9999));server.setHttpCallback(onRequest); //比普通的server添加了这行server.start(0); //副io线程数量为0,单线程运行loop.loop();return 0;
}
主要就是用户自写的一个业务处理函数,之后调用HttpServer类的函数setHttpCallback来进行注册即可。
这里例子是创建了端口是9999的HTTPServer,提供访问的是/,/hello。
在浏览器输入 http://localhost:9999或者http://localhost:9999/hello即可访问成功。(localhost可以改成是自己linux的ip)

HTTP调用的流程图
HTTP服务器基本就是结束了,这里的是简单静态web服务器,我们没有解析客户发送过来的body。需要其他功能可以在这基础上进行完善或添加,比如支持fcgi。
完整源代码:https://github.com/liwook/CPPServer/tree/main/code/server_v20
相关文章:
20.添加HTTP模块
添加一个简单的静态HTTP。 这里默认读者是熟悉http协议的。 来看看http请求Request的例子 客户端发送一个HTTP请求到服务器的请求消息,其包括:请求行、请求头部、空行、请求数据。 HTTP之响应消息Response 服务器接收并处理客户端发过来的请求后会返…...
Qemu 架构 硬件模拟器
Qemu 架构 硬件模拟器 Qemu 是纯软件实现的虚拟化模拟器, 几乎可以模拟任何硬件设备, 我们最熟悉的就是能够模拟一台能够独立运行操作系统的虚拟机, 虚拟机认为自己和硬件打交道, 但其实是和 Qemu 模拟出来的硬件打交道ÿ…...
通过starrocks jdbc外表查询sqlserver
1.sqlserver环境准备,使用docker环境,可以参考使用flink sqlserver cdc 同步数据到StarRocks_gongxiucheng的博客-CSDN博客 部署获得sqlserver环境; 2.获取starrocks环境,也可以通过docker部署,参考:使用…...
ArcGIS 10.5安装教程!
软件介绍: ArcGIS Desktop 10.5中文特别版是一款功能强大的GSI专业电子地图信息编辑和开发软件,ArcGIS Desktop 包括两种可实现制图和可视化的主要应用程序,即 ArcMap 和 ArcGIS Pro。ArcMap 是用于在 ArcGIS Desktop 中进行制图、编辑、分析…...
ConstraintLayout约束布局
1.进行复杂页面布局时,最外层的根布局不要用ConstraintLayout. 示例布局: <?xml version"1.0" encoding"utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android"http://schemas.android.co…...
通过pyinstaller将python项目打包成exe执行文件
目录 第一步:安装pyinstaller 第二步:获取一个ico图标(也即是自己这个exe文件最后的图标) 第三步:打包 第一步:安装pyinstaller pip install pyinstaller 第二步:获取一个ico图标ÿ…...
P1068 [NOIP2009 普及组] 分数线划定
题目描述 世博会志愿者的选拔工作正在 A 市如火如荼的进行。为了选拔最合适的人才,A 市对所有报名的选手进行了笔试,笔试分数达到面试分数线的选手方可进入面试。面试分数线根据计划录取人数的 150 % 150\% 150% 划定,即如果计划录取 m m …...
应用在汽车新风系统中消毒杀菌的UVC灯珠
在病毒、细菌的传播可以说是一个让人敏感而恐惧的事情。而对于车内较小的空间,乘坐人员流动性大,更容易残留细菌病毒。车内缺少通风,残留的污垢垃圾也会滋生细菌,加快细菌的繁殖。所以对于车内消毒就自然不容忽视。 那么问题又来…...
TOOLLLM: FACILITATING LARGE LANGUAGE MODELS TO MASTER 16000+ REAL-WORLD APIS
本文是LLM系列的文章之一,针对《TOOLLLM: FACILITATING LARGE LANGUAGE MODELS TO MASTER 16000 REAL-WORLD APIS》的翻译。 TOOLLLMs:让大模型掌握16000的真实世界APIs 摘要1 引言2 数据集构建3 实验4 相关工作5 结论 摘要 尽管开源大型语言模型&…...
【JavaSpring】spring接口-beanfactory和applicationcontext与事件解耦
beanfactory 1.applicationcontext的父接口 2.是Spring的核心容器 功能 表面只有getBean,但实现类默默发挥了巨大作用 1.管理所有bean 2.控制反转 3.基本的依赖注入 applicationcontext 功能 1.继承了MessageSource,有了处理国际化资源的能力 …...
《深入理解Java虚拟机》——Java内存区域与内存溢出异常
Java内存区域与内存溢出异常 运行时数据区域程序计数器Java虚拟机栈本地方法栈Java堆方法区运行时常量池直接内存 实例堆溢出栈溢出 运行时数据区域 根据《Java虚拟机规范的规定》,Java虚拟机所管理的内存将会包含已下架几个运行时数据区域。 程序计数器 在Java虚…...
公众号hanniman往期精选
目录 一、AI产品分析(10篇) 二、AI产品经理(10篇) 三、AI技术(10篇) 四、AI行业及个人成长(10篇) 一、AI产品分析 1、【重点】深度 | 关于AIGC商业化的13个非共识认知(80…...
谷粒商城----缓存与分布式锁
1、缓存使用 为了系统性能的提升,我们一般都会将部分数据放入缓存中,加速访问。而 db 承担数据落盘工作。 哪些数据适合放入缓存? 即时性、数据一致性要求不高的 访问量大且更新频率不高的数据(读多,写少&…...
【JavaEE进阶】Spring事务和事务传播机制
文章目录 一. 什么是Spring事务二. Spring中事务的实现1. Spring编程式事务2. 声明式事务2.1 trycatch下事务不会自动回滚的解决方案2.2 Transactional 作用范围2.3 Transactional 参数说明2.4 Transactional 工作原理 三. 事务的隔离级别1. 事务的四大特性2. Spring中设置事务…...
【Hive】drop table需注意外部表
什么是内部表,外部表? 比较专业的定义: 外部表需要转为内部表,执行删除操作才能真的删表结构删表数据。否则drop table仅是删除了表数据,表结构还是存在的。 alter table tb_name set TBLPROPERTIES(EXTERNALfalse);…...
【2023数学建模国赛】A题定日镜场的优化设计模型建立
2023年全国大学生数学建模竞赛A题定日镜场的优化设计,目前已写出第四版国赛A题思路和模型详细公式,目录如下: 一、 问题重述... 1 二、 问题分析... 1 三、 模型假设... 6 四、 问题一模型的建立和求解... 6 4.1 定日镜场坐标系的建立...…...
QT 事件与信号区别
事件(Event)和信号(Signal)是两个在编程中具有不同概念和用途的术语。 事件(Event): 事件是程序运行过程中发生的特定动作或状态改变。可以是用户输入、硬件触发、系统通知等。 事件通常由操作…...
[Vue3 博物馆管理系统] 使用Vue3、Element-plus tabs组件构建选项卡功能
系列文章目录 第一章 定制上中下(顶部菜单、底部区域、中间主区域显示)三层结构首页 第二章 使用Vue3、Element-plus菜单组件构建菜单 第三章 使用Vue3、Element-plus走马灯组件构建轮播图 第四章 使用Vue3、Element-plus tabs组件构建选项卡功能 [第五…...
【算法专题突破】滑动窗口 - 长度最小的子数组(9)
目录 1. 题目解析 2. 算法原理 3. 代码编写 写在最后: 1. 题目解析 题目链接:209. 长度最小的子数组 - 力扣(Leetcode) 要注意的是,题目给的是正整数, 而题目要求并不难理解,就是找最短的…...
骨传导与入耳式耳机哪种音质好?该如何选择?
骨传导耳机和传统耳机的定位不同,所以没有可比性,如果一定要说哪款耳机音质好,答案是入耳式耳机音质比较好! 首先入耳式耳机是直接塞入耳朵佩戴,会最大程度减少漏音,同时不会改变音质,会直接传…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
无法与IP建立连接,未能下载VSCode服务器
如题,在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈,发现是VSCode版本自动更新惹的祸!!! 在VSCode的帮助->关于这里发现前几天VSCode自动更新了,我的版本号变成了1.100.3 才导致了远程连接出…...
【C语言练习】080. 使用C语言实现简单的数据库操作
080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...
06 Deep learning神经网络编程基础 激活函数 --吴恩达
深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...
JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...
现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?
现有的 Redis 分布式锁库(如 Redisson)相比于开发者自己基于 Redis 命令(如 SETNX, EXPIRE, DEL)手动实现分布式锁,提供了巨大的便利性和健壮性。主要体现在以下几个方面: 原子性保证 (Atomicity)ÿ…...
云原生安全实战:API网关Kong的鉴权与限流详解
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关(API Gateway) API网关是微服务架构中的核心组件,负责统一管理所有API的流量入口。它像一座…...
【JVM面试篇】高频八股汇总——类加载和类加载器
目录 1. 讲一下类加载过程? 2. Java创建对象的过程? 3. 对象的生命周期? 4. 类加载器有哪些? 5. 双亲委派模型的作用(好处)? 6. 讲一下类的加载和双亲委派原则? 7. 双亲委派模…...
Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
深入理解Optional:处理空指针异常
1. 使用Optional处理可能为空的集合 在Java开发中,集合判空是一个常见但容易出错的场景。传统方式虽然可行,但存在一些潜在问题: // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...
