【Linux】39.一个基础的HTTP Web服务器

文章目录

  • 1. 实现一个基础的HTTP Web服务器
    • 1.1 功能实现:
    • 1.2 Log.hpp-日志记录器
    • 1.3 HttpServer.hpp-网页服务器
    • 1.4 Socket.hpp-网络通信器
    • 1.5 HttpServer.cc-服务器启动器


1. 实现一个基础的HTTP Web服务器

1.1 功能实现:

  1. 总体功能

    • 提供Web服务,响应客户端(浏览器)的HTTP请求

    • 支持静态文件服务(如HTML、图片等)

    • 多线程处理并发请求

    • 带日志记录功能

  1. 具体工作流程
浏览器 → 发送HTTP请求 → 服务器↓解析请求↓查找文件↓返回响应↓
浏览器 ← 显示页面 ← 服务器
  1. 各模块职责:

日志记录器(Log.hpp)

  • 记录服务器运行状态
  • 错误追踪和调试

网页服务器(HttpServer.hpp)

  • 解析HTTP请求
  • 处理静态文件
  • 生成HTTP响应
  • 多线程处理请求

网络通信器(Socket.hpp)

  • 处理底层网络通信
  • 管理TCP连接

服务器启动器(HttpServer.cc)

  • 程序入口
  • 初始化和启动服务

1.2 Log.hpp-日志记录器

Log.hpp

#pragma once  // 防止头文件重复包含// 系统头文件包含
#include <iostream>     // 标准输入输出
#include <time.h>       // 时间相关函数
#include <stdarg.h>     // 可变参数处理
#include <sys/types.h>  // 基本系统数据类型
#include <sys/stat.h>   // 文件状态
#include <fcntl.h>      // 文件控制选项
#include <unistd.h>     // UNIX标准函数
#include <stdlib.h>     // 标准库函数// 基础配置宏定义
#define SIZE 1024      // 缓冲区大小
#define LogFile "log.txt"  // 默认日志文件名// 日志级别定义(按严重程度递增)
#define Info 0      // 普通信息:记录系统正常操作信息
#define Debug 1     // 调试信息:记录调试相关信息
#define Warning 2   // 警告信息:记录潜在问题
#define Error 3     // 错误信息:记录错误但不影响系统运行
#define Fatal 4     // 致命错误:记录导致系统崩溃的错误// 日志输出方式定义
#define Screen 1     // 输出到屏幕:直接显示在终端
#define Onefile 2    // 输出到单个文件:所有日志记录到同一个文件
#define Classfile 3  // 分类输出:根据日志级别输出到不同文件class Log {
private:int printMethod;      // 日志输出方式std::string path;     // 日志文件存储路径public:// 构造函数:初始化日志系统Log() {printMethod = Screen;  // 默认输出到屏幕path = "./log/";       // 默认日志目录}// 设置日志输出方式void Enable(int method) {printMethod = method;}// 将日志级别转换为对应的字符串std::string levelToString(int level) {switch (level) {case Info:    return "Info";case Debug:   return "Debug";case Warning: return "Warning";case Error:   return "Error";case Fatal:   return "Fatal";default:      return "None";}}// 根据设置的输出方式打印日志void printLog(int level, const std::string &logtxt) {switch (printMethod) {case Screen:    // 输出到屏幕std::cout << logtxt << std::endl;break;case Onefile:   // 输出到单个文件printOneFile(LogFile, logtxt);break;case Classfile: // 根据日志级别输出到不同文件printClassFile(level, logtxt);break;}}// 将日志输出到指定文件void printOneFile(const std::string &logname, const std::string &logtxt) {std::string _logname = path + logname;// 以追加方式打开文件,如果不存在则创建int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666);if (fd < 0) return;  // 打开失败则直接返回write(fd, logtxt.c_str(), logtxt.size());  // 写入日志内容close(fd);  // 关闭文件描述符}// 根据日志级别将日志输出到不同文件void printClassFile(int level, const std::string &logtxt) {std::string filename = LogFile;filename += ".";filename += levelToString(level);  // 构造文件名,如"log.txt.Debug"printOneFile(filename, logtxt);}// 重载函数调用运算符,实现日志记录的核心功能void operator()(int level, const char *format, ...) {// 1. 获取当前时间time_t t = time(nullptr);struct tm *ctime = localtime(&t);// 2. 格式化日志头部(时间和级别信息)char leftbuffer[SIZE];snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,ctime->tm_hour, ctime->tm_min, ctime->tm_sec);// 3. 处理可变参数,格式化日志内容va_list s;va_start(s, format);char rightbuffer[SIZE];vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);va_end(s);// 4. 组合完整的日志消息char logtxt[SIZE * 2];snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer);// 5. 输出日志printLog(level, logtxt);}
};// 创建全局日志对象,方便在程序各处使用
Log lg;/* 示例用法:
int main() {lg.Enable(Screen);  // 设置输出到屏幕lg(Info, "Server started on port %d", 8080);lg(Error, "Failed to connect to %s", "database");return 0;
}
*/

1.3 HttpServer.hpp-网页服务器

HttpServer.hpp

功能:

  • HTTP请求处理
  • 多线程服务
  • 静态文件响应
  • Cookie支持
  • 错误页面处理
#pragma once  // 防止头文件重复包含// 基础库和系统库引入
#include <iostream>     // 标准输入输出
#include <string>       // 字符串处理
#include <pthread.h>    // POSIX线程库
#include <fstream>      // 文件流操作
#include <vector>       // 动态数组
#include <sstream>      // 字符串流
#include <sys/types.h>  // 基本系统数据类型
#include <sys/socket.h> // Socket通信
#include <unordered_map> // 哈希表// 自定义头文件
#include "Socket.hpp"   // Socket封装类
#include "Log.hpp"      // 日志系统// 全局常量定义
const std::string wwwroot="./wwwroot"; // web服务器根目录
const std::string sep = "\r\n";        // HTTP消息分隔符
const std::string homepage = "index.html"; // 默认主页static const int defaultport = 8082;    // 默认端口号class HttpServer;  // 前向声明// 线程数据结构:存储每个线程处理的连接信息
class ThreadData
{
public:ThreadData(int fd, HttpServer *s) : sockfd(fd), svr(s) {}public:int sockfd;         // 客户端连接的socket描述符HttpServer *svr;    // HTTP服务器对象指针
};// HTTP请求解析类
class HttpRequest
{
public:// 反序列化HTTP请求void Deserialize(std::string req) {while(true){std::size_t pos = req.find(sep);if(pos == std::string::npos) break;std::string temp = req.substr(0, pos);if(temp.empty()) break;req_header.push_back(temp);    // 保存请求头req.erase(0, pos+sep.size());  // 移除已处理部分}text = req;  // 保存请求体}// 解析HTTP请求,处理URL和文件路径void Parse(){// 解析请求行(方法、URL、HTTP版本)std::stringstream ss(req_header[0]);ss >> method >> url >> http_version;// 构建文件路径file_path = wwwroot;  if(url == "/" || url == "/index.html") {file_path += "/";file_path += homepage;  // 处理默认主页}else file_path += url;     // 其他页面// 获取文件后缀auto pos = file_path.rfind(".");if(pos == std::string::npos) suffix = ".html";else suffix = file_path.substr(pos);}// 调试打印函数void DebugPrint(){// 输出请求信息用于调试for(auto &line : req_header){std::cout << "--------------------------------" << std::endl;std::cout << line << "\n\n";}std::cout << "method: " << method << std::endl;std::cout << "url: " << url << std::endl;std::cout << "http_version: " << http_version << std::endl;std::cout << "file_path: " << file_path << std::endl;std::cout << text << std::endl;}public:std::vector<std::string> req_header;  // 请求头部std::string text;                     // 请求正文// 解析后的请求信息std::string method;       // 请求方法(GET、POST等)std::string url;         // 请求URLstd::string http_version; // HTTP协议版本std::string file_path;   // 请求文件路径std::string suffix;      // 文件后缀
};// HTTP服务器类
class HttpServer
{
public:// 构造函数:初始化端口和支持的内容类型HttpServer(uint16_t port = defaultport) : port_(port){content_type.insert({".html", "text/html"});content_type.insert({".png", "image/png"});}// 启动服务器bool Start(){// 初始化Socket// 1. 创建Socketlistensock_.Socket();/* 这一步完成以下操作:a) 调用系统函数 socket(AF_INET, SOCK_STREAM, 0) 创建TCP Socket- AF_INET: 使用IPv4协议族- SOCK_STREAM: 使用TCP协议- 0: 使用默认协议b) 设置Socket选项- SO_REUSEADDR: 允许地址重用,避免服务器重启时的"地址已被使用"错误*/// 2. 绑定端口listensock_.Bind(port_);/* 这一步完成以下操作:a) 创建sockaddr_in结构体,设置:- sin_family = AF_INET (IPv4)- sin_port = htons(port_) (设置端口号,转换为网络字节序)- sin_addr.s_addr = INADDR_ANY (监听所有网卡接口)b) 调用bind()函数将Socket与地址绑定- 如果端口已被占用或权限不足,会失败*/// 3. 开始监听listensock_.Listen();/* 这一步完成以下操作:a) 调用listen()函数,将Socket转换为监听状态- backlog参数设置为10,表示等待连接队列的最大长度- 超过此长度的新连接请求会被拒绝b) 此后Socket就能接受客户端连接请求- 服务器调用Accept()接受新的连接*/// 主循环:接受并处理连接for (;;){// 准备变量存储客户端信息std::string clientip;    // 将存储客户端的IP地址uint16_t clientport;     // 将存储客户端的端口号// 接受新的客户端连接int sockfd = listensock_.Accept(&clientip, &clientport);/* Accept函数做了这些事:1. 等待客户端连接2. 获取客户端的IP和端口3. 返回新的socket描述符用于与该客户端通信*/// 连接失败则继续等待下一个连接if (sockfd < 0) continue;// 记录新连接日志lg(Info, "get a new connect, sockfd: %d", sockfd);// 创建新线程处理请求// 1. 声明线程ID变量pthread_t tid;    // 用于存储新创建线程的ID// 2. 创建线程数据结构,传入连接描述符和当前服务器对象ThreadData *td = new ThreadData(sockfd, this);/* ThreadData包含:- sockfd:与客户端通信的socket描述符- this:当前服务器对象的指针,用于访问服务器的方法*/// 3. 创建新线程处理请求pthread_create(&tid, nullptr, ThreadRun, td);/* 参数含义:- &tid:存储新线程ID- nullptr:使用默认线程属性- ThreadRun:线程将执行的函数- td:传递给线程函数的参数*/// 新线程会执行ThreadRun函数处理客户端请求// 主线程继续循环等待新的连接}}// 读取HTML文件内容static std::string ReadHtmlContent(const std::string &htmlpath){// 1. 打开文件std::ifstream in(htmlpath, std::ios::binary);/* 说明:- binary模式打开确保文件按原样读取- 不会对换行符进行转换*/// 文件打开失败则返回空字符串if(!in.is_open()) return "";// 2. 获取文件大小in.seekg(0, std::ios_base::end);   // 将读指针移到文件末尾auto len = in.tellg();             // 获取当前位置(即文件大小)in.seekg(0, std::ios_base::beg);   // 将读指针移回文件开头// 3. 读取文件内容std::string content;           // 用于存储文件内容content.resize(len);           // 预分配空间// 一次性读取整个文件内容到字符串中in.read((char*)content.c_str(), content.size());// 4. 关闭文件in.close();return content;  // 返回文件内容}// 根据文件后缀获取Content-Typestd::string SuffixToDesc(const std::string &suffix){// 在content_type映射表中查找文件后缀对应的MIME类型auto iter = content_type.find(suffix);// 如果找不到对应的MIME类型if(iter == content_type.end()) return content_type[".html"];  // 默认返回html的MIME类型:"text/html"else return content_type[suffix];   // 返回找到的MIME类型/* 例如:- .html -> "text/html"- .png  -> "image/png"这个MIME类型会被用在HTTP响应头的Content-Type字段中*/}// 处理HTTP请求void HandlerHttp(int sockfd){// 1. 接收HTTP请求char buffer[10240];ssize_t n = recv(sockfd, buffer, sizeof(buffer) - 1, 0);/* 参数解释:1. sockfd: 套接字描述符,用于标识与客户端的连接2. buffer: 接收数据的缓冲区3. sizeof(buffer) - 1: 最大接收长度,预留1个字节给'\0'4. 0: 标志位,使用默认行为返回值n:- 大于0:实际接收的字节数- 等于0:连接已关闭- 小于0:接收错误*/if (n > 0){buffer[n] = 0;  // 字符串结束符// 2. 解析HTTP请求HttpRequest req;req.Deserialize(buffer);   // 反序列化请求内容req.Parse();               // 解析请求(获取方法、URL、版本等)// 3. 读取请求的文件内容std::string text;bool ok = true;text = ReadHtmlContent(req.file_path);  // 读取请求的文件if(text.empty())  // 文件不存在或读取失败{ok = false;// 返回错误页面std::string err_html = wwwroot + "/err.html";text = ReadHtmlContent(err_html);}// 4. 构建HTTP响应// 4.1 响应行std::string response_line;if(ok)response_line = "HTTP/1.0 200 OK\r\n";elseresponse_line = "HTTP/1.0 404 Not Found\r\n";// 4.2 响应头std::string response_header = "Content-Length: ";response_header += std::to_string(text.size());response_header += "\r\n";response_header += "Content-Type: ";response_header += SuffixToDesc(req.suffix);  // 设置正确的MIME类型response_header += "\r\n";response_header += "Set-Cookie: name=haha&&passwd=12345";  // 设置Cookieresponse_header += "\r\n";// 4.3 空行std::string blank_line = "\r\n";// 4.4 组装完整响应(响应行+响应头+空行+响应体)std::string response = response_line + response_header + blank_line + text;// 5. 发送响应给客户端send(sockfd, response.c_str(), response.size(), 0);}// 6. 关闭连接close(sockfd);}// 线程运行函数static void *ThreadRun(void *args){pthread_detach(pthread_self());  // 设置线程分离ThreadData *td = static_cast<ThreadData *>(args);td->svr->HandlerHttp(td->sockfd);delete td;return nullptr;}~HttpServer() {}private:Sock listensock_;    // 监听socketuint16_t port_;      // 服务器端口std::unordered_map<std::string, std::string> content_type;  // 支持的内容类型映射
};

1.4 Socket.hpp-网络通信器

Socket.hpp

功能:

  • TCP连接封装
  • 地址绑定
  • 端口监听
  • 客户端连接处理
  • 错误处理
#pragma once  // 防止头文件重复包含// 系统相关头文件
#include <iostream>     // 标准输入输出
#include <string>       // 字符串处理
#include <unistd.h>    // UNIX标准函数定义
#include <cstring>     // C字符串处理
#include <sys/types.h> // 基本系统数据类型
#include <sys/stat.h>  // 文件状态
#include <sys/socket.h> // Socket接口
#include <arpa/inet.h> // IP地址转换函数
#include <netinet/in.h> // IP协议家族
#include "Log.hpp"     // 日志系统// 错误枚举定义
enum
{SocketErr = 2,  // Socket创建错误BindErr,        // 绑定错误ListenErr,      // 监听错误
};// 监听队列长度
const int backlog = 10;// Socket封装类
class Sock
{
public:Sock() {}~Sock() {}public:// 创建Socketvoid Socket(){// 创建TCP Socketsockfd_ = socket(AF_INET, SOCK_STREAM, 0);if (sockfd_ < 0){// 创建失败,记录错误日志并退出lg(Fatal, "socker error, %s: %d", strerror(errno), errno);exit(SocketErr);}// 设置Socket选项:地址重用int opt = 1;setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));}// 绑定端口void Bind(uint16_t port){// 创建并初始化地址结构struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;           // IPv4local.sin_port = htons(port);         // 端口号local.sin_addr.s_addr = INADDR_ANY;   // 监听所有网卡// 绑定地址和端口if (bind(sockfd_, (struct sockaddr *)&local, sizeof(local)) < 0){// 绑定失败,记录错误日志并退出lg(Fatal, "bind error, %s: %d", strerror(errno), errno);exit(BindErr);}}// 开始监听void Listen(){// 启动监听if (listen(sockfd_, backlog) < 0){// 监听失败,记录错误日志并退出lg(Fatal, "listen error, %s: %d", strerror(errno), errno);exit(ListenErr);}}// 接受连接int Accept(std::string *clientip, uint16_t *clientport){// 准备接收客户端地址信息struct sockaddr_in peer;socklen_t len = sizeof(peer);// 接受新连接int newfd = accept(sockfd_, (struct sockaddr*)&peer, &len);if(newfd < 0){// 接受连接失败,记录警告日志lg(Warning, "accept error, %s: %d", strerror(errno), errno);return -1;}// 获取客户端IP地址char ipstr[64];inet_ntop(AF_INET, &peer.sin_addr, ipstr, sizeof(ipstr));*clientip = ipstr;// 获取客户端端口号*clientport = ntohs(peer.sin_port);return newfd;  // 返回新连接的文件描述符}// 连接服务器(客户端使用)bool Connect(const std::string &ip, const uint16_t &port){// 准备服务器地址信息struct sockaddr_in peer;memset(&peer, 0, sizeof(peer));peer.sin_family = AF_INET;peer.sin_port = htons(port);inet_pton(AF_INET, ip.c_str(), &(peer.sin_addr));// 建立连接int n = connect(sockfd_, (struct sockaddr*)&peer, sizeof(peer));if(n == -1) {// 连接失败std::cerr << "connect to " << ip << ":" << port << " error" << std::endl;return false;}return true;  // 连接成功}// 关闭Socketvoid Close(){close(sockfd_);}// 获取Socket文件描述符int Fd(){return sockfd_;}private:int sockfd_;  // Socket文件描述符
};

1.5 HttpServer.cc-服务器启动器

HttpServer.cc

功能:

  • 程序入口
  • 参数解析
  • 服务器初始化
  • 智能指针管理
// 包含必要的头文件
#include "HttpServer.hpp"  // HTTP服务器类定义
#include <iostream>        // 标准输入输出
#include <memory>         // 智能指针
#include <pthread.h>      // POSIX线程库
#include "Log.hpp"        // 日志系统using namespace std;int main(int argc, char *argv[])
{// 检查命令行参数if(argc != 2)  // 要求必须提供端口号参数{exit(1);   // 参数错误,退出程序}// 将命令行参数转换为端口号uint16_t port = std::stoi(argv[1]);  // 字符串转换为整数// 创建HTTP服务器实例// 以下是三种方式,注释掉的是不推荐的方式// 方式1(不推荐):普通指针,需要手动管理内存// HttpServer *svr = new HttpServer();// 方式2(语法错误):unique_ptr的错误声明方式// std::unique<HttpServer> svr(new HttpServer());// 方式3(推荐):使用智能指针unique_ptr,自动管理内存std::unique_ptr<HttpServer> svr(new HttpServer(port));// 启动服务器svr->Start();  // 开始监听和处理请求// 程序正常退出return 0;
}

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

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

相关文章

沐渥科技详解氮气柜操作指南

氮气柜是一种通过持续注入高纯度氮气&#xff0c;维持柜内惰性气体环境的设备&#xff0c;用于存储半导体晶圆或其他敏感元件&#xff0c;防止氧化、吸湿和污染。氮气柜操作指南是怎样的&#xff1f;下面沐渥小编给大家介绍一下。 一、操作前准备 &#xff08;1&#xff09;安全…

从零实现Agent智能体配置使用(Ragflow)

从零实现Agent智能体配置使用&#xff08;Ragflow&#xff09; 1. 创建智能体2. 配置智能体2.1 配置问题识别2.2 配置问题分类2.3 不同问题进行单独配置2.4 保存Agent 3. 体验效果 1. 创建智能体 2. 配置智能体 2.1 配置问题识别 2.2 配置问题分类 2.3 不同问题进行单独配置 当…

显示器各类异常处理方法

显示器各类异常处理方法 导航 文章目录 显示器各类异常处理方法导航画面无显示/黑屏/无HDMI信号输入显示器闪烁显示器花屏显示画面模糊或扭曲显示器颜色异常显示器出现死点或亮点 画面无显示/黑屏/无HDMI信号输入 ​ 首先应该检查的是显示器电源&#xff08;真的有人弄掉电源…

原理剖析 + 实战教程 + 资源优化总结大模型微调实战:LoRA / QLoRA / PEFT 全解析,教你低成本玩转大模型微调

随着大语言模型&#xff08;LLM&#xff09;在自然语言处理各领域取得突破性进展&#xff0c;越来越多开发者和企业开始关注模型的微调方式。然而&#xff0c;全参数微调不仅成本高昂、资源要求极高&#xff0c;还容易引发过拟合与知识遗忘等问题。为此&#xff0c;LoRA、QLoRA…

Higress: 阿里巴巴高性能云原生API网关详解

一、Higress概述 Higress是阿里巴巴开源的一款基于云原生技术构建的高性能API网关&#xff0c;专为Kubernetes和微服务架构设计。它集成了Ingress控制器、微服务网关和API网关功能于一体&#xff0c;支持多种协议和丰富的流量管理能力。 发展历程 Higress 从最初社区的 Isti…

解决 IntelliJ IDEA 中 Maven 项目左侧项目视图未显示顶层目录问题的详细步骤说明

以下是解决 IntelliJ IDEA 中 Maven 项目左侧项目视图未显示顶层目录问题的详细步骤说明&#xff1a; 1. 切换项目视图模式 默认情况下&#xff0c;IDEA 的项目视图可能处于 Packages 模式&#xff0c;仅显示代码包结构&#xff0c;而非物理目录。 操作步骤&#xff1a; 点击…

【Vue-vue基础知识】学习笔记

目录 <<回到导览vue基础知识1.1.创建一个vue实例1.2.vue基础指令1.2.1.v-bind1.2.2.v-model1.2.3.常用事件1.2.4.指令修饰符 1.3.计算属性1.3.1.计算属性的完整写法1.3.2.【案例】成绩 1.4.watch1.4.1.watch属性1.4.2.翻译业务实现1.4.3.watch属性的完整写法1.4.4.【案例…

Element Plus 图标使用方式整理

Element Plus 图标使用方式整理 以下是 Element Plus 图标的所有使用方式&#xff0c;包含完整代码示例和总结表格&#xff1a; 1. 按需引入图标组件 适用场景&#xff1a;仅需少量图标时&#xff0c;按需导入减少打包体积 示例代码&#xff1a; <template><div>…

使用Scrapy官方开发的爬虫部署、运行、管理工具:Scrapyd

一般情况下&#xff0c;爬虫会使用云服务器来运行&#xff0c;这样可以保证爬虫24h不间断运行。但是如何把爬虫放到云服务器上面去呢&#xff1f;有人说用FTP&#xff0c;有人说用Git&#xff0c;有人说用Docker。但是它们都有很多问题。 FTP&#xff1a;使用FTP来上传…

41、web前端开发之Vue3保姆教程(五 实战案例)

一、项目简介和需求概述 1、项目目标 1.能够基于Vue3创建项目 2.能够基本Vue3相关的技术栈进行项目开发 3.能够使用Vue的第三方组件进行项目开发 4.能够理解前后端分离的开发模式 2、项目概述 使用Vue3结合ElementPlus,ECharts工具实现后台管理系统页面,包含登录功能,…

OpenCV--图像平滑处理

在数字图像处理领域&#xff0c;图像平滑处理是一项极为重要的技术&#xff0c;广泛应用于计算机视觉、医学影像分析、安防监控等多个领域。在 OpenCV 这一强大的计算机视觉库的助力下&#xff0c;我们能便捷地实现多种图像平滑算法。本文将深入探讨图像平滑的原理&#xff0c;…

性能优化利器:前后端防抖方案解析

精心整理了最新的面试资料和简历模板&#xff0c;有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 在Web开发中&#xff0c;高频触发的事件&#xff08;如用户输入、按钮点击、滚动监听等&#xff09;可能导致性能问题或资源浪费。防抖&#xff08;Debounce&…

【ES系列】Elasticsearch简介:为什么需要它?(基础篇)

🔥 本文将详细介绍Elasticsearch的前世今生,以及为什么它在当今的技术栈中如此重要。本文是ES起飞之路系列的基础篇第一章,适合想要了解ES的读者。 文章目录 一、什么是Elasticsearch?1. ES的定义2. ES的核心特性2.1 分布式存储2.2 实时搜索2.3 高可用性2.4 RESTful API3.…

用 HTML 网页来管理 Markdown 标题序号

文章目录 工具介绍核心优势使用指南基本使用方法注意事项 部分截图完整代码 工具介绍 在日常的文档编写和博客创作中&#xff0c;Markdown因其简洁的语法和良好的可读性而广受欢迎。然而&#xff0c;当文档结构复杂、标题层级较多时&#xff0c;手动维护标题序号不仅耗时耗力&…

批量将 Markdown 转换为 Word/PDF 等其它格式

在工作当中&#xff0c;我们经常会接触到 Markdown 格式的文档。这是一种非常方便我们做记录&#xff0c;做笔记的一种格式文档。现在很多互联网编辑器都是支持 Markdown 格式的&#xff0c;编辑起文章来更加的方便简介。有时候&#xff0c;我们会碰到需要将 Markdown 格式的文…

剑指Offer(数据结构与算法面试题精讲)C++版——day8

剑指Offer&#xff08;数据结构与算法面试题精讲&#xff09;C版——day8 题目一&#xff1a;链表中环的入口节点题目二&#xff1a;两个链表的第1个重合节点题目三&#xff1a;反转链表附录&#xff1a;源码gitee仓库 题目一&#xff1a;链表中环的入口节点 这道题的有如下三个…

【BFT帝国】20250409更新PBFT总结

2411 2411 2411 Zhang G R, Pan F, Mao Y H, et al. Reaching Consensus in the Byzantine Empire: A Comprehensive Review of BFT Consensus Algorithms[J]. ACM COMPUTING SURVEYS, 2024,56(5).出版时间: MAY 2024 索引时间&#xff08;可被引用&#xff09;: 240412 被引:…

前端用用jsonp的方式解决跨域问题

前端用用jsonp的方式解决跨域问题 前端用用jsonp的方式解决跨域问题 前端用用jsonp的方式解决跨域问题限制与缺点&#xff1a;前端后端测试使用示例 限制与缺点&#xff1a; 不安全、只能使用get方式、后台需要相应jsonp方式的传参 前端 function jsonp(obj) {// 动态生成唯…

MySQL详解最新的官方备份方式Clone Plugin

一、Clone Plugin的动态安装 install plugin clone soname mysql_clone.so;select plugin_name,plugin_status from information_schema.plugins where plugin_name clone; 二、Clone Plugin配置持久化 在 MySQL 配置文件my.cnf中添加以下内容&#xff0c;确保插件在 MySQL …

解决python manage.py shell ModuleNotFoundError: No module named xxx

报错如下&#xff1a; python manage.py shellTraceback (most recent call last):File "/Users/z/Documents/project/c/manage.py", line 10, in <module>execute_from_command_line(sys.argv)File "/Users/z/.virtualenvs/c/lib/python3.12/site-packa…