• 【Linux网络系列】:JSON+HTTP,用C++手搓一个web计算器服务器!

【Linux网络系列】:JSON+HTTP,用C++手搓一个web计算器服务器!

2026-02-03 21:09:10 栏目:帮助文档 1 阅读

🔥 本文专栏:Linux网络 Linux实践系列
🌸作者主页:努力努力再努力wz

💪 今日博客励志语录别害怕选错,人生最遗憾的从不是‘选错了’,而是‘我本可以’。每一次推倒重来的勇气,都是在给灵魂贴上更坚韧的勋章。

★★★ 本文前置知识:

序列化与反序列化


引入

在之前的博客中,我详细介绍了序列化反序列化 的概念。对于使用 TCP 协议进行通信的双方,由于 TCP 是面向字节流的,在发送数据之前,我们通常需要定义一种结构化的数据来描述传输内容,并以此作为数据的容器。在 C++ 中,这种结构化数据通常表现为对象或结构体。然而,我们不能直接将结构体内存中对应的字节原样发送到另一端,因为直接传递内存字节会引发字节序 和结构体内存对齐 的问题。不同平台、不同编译器所遵循的内存对齐规则可能不同,这可能导致接收方在解析结构体字段时出现错误。

因此,我们需要借助序列化序列化 是指将结构化的数据按照预定的规则转换为连续的字节流。其主要目的是屏蔽平台差异,使得位于不同平台的进程能够以统一的方式解析该字节流。序列化通常分为两种形式:文本序列化二进制序列化

文本序列化将结构化的数据转换为一个完整的字符串。字符串本身是以字符为单位的连续序列,每个字符通常占用一个字节,因此字符串本质上也是一个连续的字节流。由于字符串以字符为单位解析,不存在字节序问题。通信双方只需约定字符串的格式与编码方式,即可正确解析该字符序列,最终将连续的字节流还原为结构化的数据。

二进制序列化则直接发送数据在内存中的原始二进制序列,无需额外转换。这两种方式各有优劣:文本序列化直观、可读性高、便于调试;而二进制序列化发送的是二进制数据,人类难以直接阅读。文本序列化会将数据转换为字符形式,可能导致传输体积增大——例如整数 100000 在文本序列化中会被转换为 “100000” 占 6 个字节,而作为 int 类型的二进制序列化仅需 4 个字节。因此,二进制序列化在传输体积上通常更小。此外,文本序列化还需要对字符串进行解析以恢复原始数据,而二进制序列化的解析开销通常更低,因为它直接对应数据的原始二进制表示。

特性文本序列化 (JSON/XML)二进制序列化 (Protobuf/Thrift)
可读性极高(肉眼可读)低(十六进制乱码)
传输体积较大(数字变字符,带大量引号)极小(紧凑编码)
解析速度较慢(需字符串扫描、词法解析)极快(直接偏移寻址或位运算)
跨语言完美(天然支持)优秀(需编译 IDL 文件)

在上一篇博客中,我们手动实现了文本序列化,即将结构体各字段按一定格式拼接为完整字符串。我之所以手动实现,是为了帮助大家理解序列化的基本原理,并为本文内容做铺垫。

然而在实际开发中,我们通常不需要从头实现序列化,可以使用成熟的第三方库来完成这项工作。这些库的实现通常更完善、更高效。本文将介绍的第一个主题——JSON ,就是一种广泛应用的文本序列化格式。

JSON

首先,介绍一下什么是 JSONJSON (JavaScript Object Notation)是一种轻量级、基于文本、人类可读的数据交换格式。JSON 源于 JavaScript,借鉴了其对象和数组的表示方法。但由于 JSON 本身是文本格式,且所表示的基本数据类型(如整型、布尔值等)在绝大多数编程语言中都得到支持,因此JSON 并不局限于 JavaScript,而是能够被多种编程语言解析与生成。正因如此,JSON 不仅具备跨平台 能力,还能实现跨语言 的数据交换。

了解 JSON 的基本定义后,我们进一步探讨其本质。如上所述,JSON 实质上是一种文本序列化的方式。在此之前,我们曾手动实现过文本序列化,其核心原理是将结构体的各个字段按照特定格式拼接为一个完整的字符串。因此,JSON 的本质其实就是符合 JSON 规范(风格)的字符串。

理论上,只要我们清楚 JSON 格式的规范,就可以利用字符串操作函数手动拼接出符合 JSON 风格的字符串,而无需借助第三方库。字符串拼接本身并不复杂,因此自然引出一个疑问:相比手动实现,第三方库的优势究竟在哪里?如果仅实现序列化(即转换为 JSON 字符串),那么使用第三方库似乎并未显著减轻负担,因为序列化这一步本身并不困难。要回答这个问题,我们首先需要明确 JSON 风格字符串的具体形式,进而理解第三方库所承担的工作。这一点我们稍后再展开。

JSON 支持若干基本数据类型 ,例如整型、浮点型和布尔型,也支持字符串对象 等复杂类型:

JSON 类型C++ 对应类型描述
Numberint, double, floatJSON 不区分整数和浮点数,统一视为数字。
Booleanbool只有 truefalse 两个字面值。
Stringstd::string必须使用 双引号 包围,支持转义字符(如 , )。
Nullnullptr / NULL表示空值或不存在,常用于可选字段。

需注意,基本数据类型(如整型、浮点型)可直接书写,而字符串类型必须用双引号括起来。

如果我们需要将一个对象或结构体的数据传递给另一端,在未接触 JSON 时,通常需要手动将其各字段拼接成字符串,再发送该字符串的字节流。而 JSON 可以直接表示对象,其方法是用一对大括号包裹内容,括号内是一个或多个键值对 。每个键值对 中,键与值之间用冒号分隔,不同键值对之间用逗号分隔。

这里的键值对对应于结构体或对象的成员变量:键表示成员名称,值表示该成员的取值。这种表示方式不仅书写方便,也能直观体现对象结构及其属性值:

{
    "age": 20,
    "sex": "girl",
    "height": 160
}

需要注意的是,值可以是任意基本类型,但键必须是字符串类型。

对于数组,JSON 使用中括号表示,括号内为数组元素,各元素之间以逗号分隔。数组元素可以是基本类型,也可以是对象等复杂类型:

[
    100,
    "bob",
    {
        "id": 1234,
        "name": "mike"
    }
]

了解 JSON 字符串的格式后,我们可以回应前文提出的问题:既然已知对象用花括号表示、键值对用逗号分隔,数组用中括号表示、元素间用逗号分隔,我们确实可以通过字符串拼接函数,将结构化数据转换为符合 JSON 格式的字符串。

然而,如果需要修改对象中某一字段的值,通常有两种做法:一是修改原始结构体的值,然后重新拼接整个字符串;二是直接修改已拼接好的字符串(在不改动原始数据的情况下)。第二种方式需要定位字符串中的特定字段,进行覆盖并调整后续字符位置,过程较为繁琐。此外,在序列化过程中,某些字符(如双引号、反斜杠等)需要进行转义处理,这也增加了手动实现的复杂度。

更重要的是,除了序列化,我们还需考虑反序列化——即将 JSON 字符串解析并还原为原始数据。根据上述 JSON 格式,反序列化需识别键值对、分离键与值,并将值转换回对应数据类型。若遇到嵌套对象(即对象中某个属性的值仍为对象),则需递归处理,实现难度显著增加:

{
    "id": 1234,
    "name": "bob",
    "person": {
        "id": 125,
        "name": "kie"
    }
}

如果序列化与反序列化均自行实现,那么在序列化时若遗漏逗号或括号,将给反序列化带来极大困难,甚至导致解析失败。

因此,引入第三方库显得十分必要。其优势不仅在于提供高效的序列化与反序列化功能,更在于它提供了一系列功能丰富的接口,用于直接操作 JSON 对象内部维护的结构化原始数据。这些库通常通过一个与编程语言相对应的数据结构(在 C++ 中通常是一个类对象)来映射和承载解析后的 JSON 数据,同时提供成员函数来方便地进行相关操作。

在 C++ 中,常用的 JSON 库包括json.hpp (即 nlohmann/json)。它提供的 JSON 对象可被视为一个容器,用于存储要发送或解析的 JSON 数据,并封装了丰富的操作方法,大大简化了 JSON 的处理流程。


这里需要注意,nlohmann/json 是一个第三方库,这意味着 C++ 标准库并不包含该库,因此我们需要自行引入。本文采用的方式是:获取官方json.hpp 源代码文件,将其全部内容复制到 Linux 系统下的相应目录中并保存。当然,引入该库还有其它多种方法,在此不再赘述。

需要明确的是,该第三方库维护了一个类, 该类可实例化为一个 JSON 对象。它不仅作为数据容器,还能以更灵活的方式支持我们对内部结构化数据的管理与维护。通过该对象提供的函数,我们可以直接操作数据项,而不需要去关心底层字符串的具体拼写格式。

首先关注其使用方法,即如何操作这个json 对象。json 类的定义位于json.hpp 中的nlohmann 命名空间内,因此我们需要指定该命名空间,随后创建一个json 对象。

json 对象最常见的数据类型是对象(object)和数组(array)。初始化一个 json 对象主要有两种方式。第一种是通过构造函数完成,由于json.hpp 支持 C++11,我们可以使用列表初始化的语法。

如前所述,JSON 对象的内容由一系列键值对组成。在列表初始化中,可以直接向构造函数传递一系列std::pair 对象,每个 pair 对应一个键值对。

nlohmann::json j = {
    {"name", "WZ"},
    {"age", 20},
    {"gender", "girl"}
};

除了通过构造函数进行初始化,另一种更推荐的方式是直接使用赋值运算符。可以这样理解:若 json 对象存储的是 JSON 对象,其内部实际上维护了一个字典(即哈希表)。更详细的实现原理将在后文说明。

我们知道哈希表内部存储键值对,并重载了
“[]” 运算符。若哈希表中不存在指定的键,则会插入对应的键值对,从而完成初始化。这种方式不仅方便,也更符合 C++ 标准库容器的使用习惯,其效果与上述列表初始化相同,因此本人更推荐此种写法:

nlohmann::json j;
j["name"] = "wz";
j["age"] = 20;
j["gender"] = "girl";

了解如何创建json 对象后,下一步是进行序列化。json 类提供了dump() 成员函数用于序列化,其返回类型为std::string 。因为 JSON 本质上是一个具有特定格式的字符串,而dump() 的返回值正是该格式的字符串表示。该函数可接收一个整数参数,若不传递参数,则默认生成紧凑格式(compact)的字符串。紧凑格式是指所有键值对均在同一行内输出,键值对之间仅以逗号分隔,不包含换行与额外空格,因此可读性相对较低。以下代码演示其效果:

#include "json.hpp"
#include 

int main() {
    nlohmann::json j = {
        {"name", "WZ"},
        {"age", 20},
        {"gender", "girl"}
    };
    std::string name = j["name"];
    std::string s = j.dump();
    return 0;
}

若向dump() 传递一个整型参数,则输出的字符串会进行格式化:每个键值对单独占一行,并且该参数值表示每一级缩进的空格数。例如,若参数值为 2,则每对键值前会有 2 个空格。如果 JSON 对象中嵌套了其他对象或数组,内层元素会根据嵌套深度进一步增加缩进。具体而言,设嵌套深度为 n(n ≥ 0),每级缩进空格数为 m,则某键值对前的空格总数为 (n + 1) * m。此规则不必强记,了解即可。

以下以dump(4) 为例说明其视觉格式:

{
    "name": "WangZhe",           // 第 1 层:4 个空格
    "stats": {                   // 第 1 层:4 个空格
        "level": 99,             // 第 2 层:8 个空格 (4+4)
        "equipment": [           // 第 2 层:8 个空格 (4+4)
            "Sword",             // 第 3 层:12 个空格 (8+4)
            "Shield"             // 第 3 层:12 个空格 (8+4)
        ]                        // 回到第 2 层缩进
    }                            // 回到第 1 层缩进
}

在原先的代码基础上,我们可令序列化后的 JSON 字符串使用 2 格缩进,观察其效果:

#include "json.hpp"
#include 

int main() {
    nlohmann::json j = {
        {"name", "WZ"},
        {"age", 20},
        {"gender", "girl"}
    };
    std::string name = j["name"];
    std::string s = j.dump();
    return 0;
}

通常不建议在序列化时添加缩进,因为缩进虽然提高了可读性,但也会引入额外的换行符和空格,从而增加字符串的体积。若 JSON 数据包含大量键值对或嵌套层次较深,这种体积增长会在网络传输等场景中带来额外开销。因此,一般情况下建议调用dump() 时不传入参数。

接下来介绍如何初始化表示数组的json 对象。初始化数组同样有两种方法:第一种仍然是通过构造函数的列表初始化,但此时传递的是值(而非键值对),构造函数会据此完成初始化:

#include "json.hpp"
#include 

int main() {
  nlohmann::json j = {
        {"name", "WZ"},
        {"age", 20},
        {"gender", "girl"}
    };
    std::string s=j.dump(2);
    nlohmann::json j1 = {1, 2, 3, 4, 5};
    std::string s1=j1.dump();
    std::cout<<s<<std::endl;
    std::cout<<s1<<std::endl;
    return 0;
}

第二种方式是使用 json 类提供的 push_back() 函数,该函数专用于向表示数组的 json 对象末尾添加元素。可将其简单理解为在内部维护的数组尾部插入元素,具体原理将在后文详细阐述。初始化完成后,同样可调用 dump() 进行序列化。

json 对象的功能不止于此。假设json 对象内部存储的是一个对象或者数组,我们可以像操作标准库中的哈希表或者vector一样,通过[] 运算符访问或修改其字段值:

#include "json.hpp"
#include 

int main() {
    nlohmann::json j = {
        {"name", "WZ"},
        {"age", 20},
        {"gender", "girl"}
    };
    std::string name = j["name"];
    std::cout << name << std::endl;
    j["name"] = "kiki";
    std::cout << j["name"] << std::endl;
    
    std::string s = j.dump(2);
    std::cout << s << std::endl;
    
    nlohmann::json j2 = {1, 2, 3, 4, 5};
    std::cout << j2[1] << std::endl;
    j2[1] = 100;
    std::cout << j2[1] << std::endl;
    
    std::string s2 = j2.dump();
    std::cout << s2 << std::endl;
    
    return 0;
}

最后介绍反序列化,即将 JSON 格式的字符串还原为json 对象。json 类提供了静态成员函数parse() ,它接收一个 JSON 格式的字符串,在内部解析后存储到 json 对象中。此过程即反序列化——将连续的字节流还原为结构化的 json 对象,其内部保存的数据即为原始内容。

为了熟悉parse() 的用法,我们编写如下代码进行验证。首先需要准备一个符合 JSON 语法的字符串。根据前面的介绍,JSON 对象由大括号包裹,数组由中括号包裹,键名与字符串值必须使用双引号。这些特殊字符在 C++ 字符串中需要使用转义字符表示,因此手动构造 JSON 字符串较为繁琐,例如:

std::string s = "{"name":"WZ","skills":["C++","Linux"]}";

为此,C++11 引入了原始字符串字面量(raw string literal)语法:R"()" 。其基本格式为:

R"delimiter( raw_characters )delimiter"

其中R 指明该字符串为原始字符串,括号内为字符串内容,delimiter 为可选的分隔标识符。在原始字符串中,绝大多数字符(包括引号和换行)无需转义。仅当字符串内容本身包含")" 时,才需要在括号前添加一个自定义分隔符以避免歧义,例如:

// 在引号和括号间添加自定义标识符 "art"
std::string s = R"art({"msg": "Look at this )" symbol"})art";
// 编译器在遇到匹配的 )art" 时才会认为字符串结束

了解该语法后,即可方便地进行反序列化操作:

#include "json.hpp"
#include 

int main() {
    std::string s = R"({"name":"WZ","age":18,"is_student":true,"gender":"female"})";
    nlohmann::json j = nlohmann::json::parse(s);
    

std::cout << j["name"] << std::endl;
std::cout << j["age"] << std::endl;
std::cout << j["is_student"] << std::endl;
std::cout << j["gender"] << std::endl;

return 0;

}

原理

接下来介绍 JSON 的实现原理。基于上文的背景,json.hpp 库内部维护一个json 类,该类包含两个核心成员变量:类型变量 与 值变量。其中,类型变量用于记录当前json 对象所维护的原始数据类型,例如是一个对象、一个数组,还是一种基本数据类型。除了类型变量,类中还维护一个值变量,该值变量被实现为一个联合体。我们知道,联合体的各个成员变量共享同一块内存,且都从联合体的起始地址开始布局,这意味着在任意时刻,联合体中只有一个成员是有效的。将值变量设计为联合体的原因在于,一个json 对象同一时刻只能表示一种数据类型,不可能同时维护数组与对象;只能在数组内嵌套对象,或在对象内嵌套数组。

#include 
#include 
#include 
#include 

// 1. 类型标签:标识当前存储的数据类型
enum class value_t {
    null,
    number_integer,
    string,
    array,
    object
};

class json {
public:
    // 2. 核心联合体:所有类型共用同一内存区域
    union internal_value {
        int64_t number_integer;          // 基本类型直接存储值
        std::string* string;             // 复杂类型存储指针
        std::vector<json>* array;      // 递归定义:数组中存储的是 json 对象
        std::map<std::string, json>* object;

 internal_value() : number_integer(0) {} // 默认初始化
};

value_t m_type = value_t::null;
internal_value m_value;

// ...

};

在该联合体中,基本数据类型(如整数)直接存储其值,而复杂数据类型(如字符串、数组等)则存储指针,以此减少json对象本身的内存占用。json 类的关键组成部分之一是其构造函数。该类提供了多个版本的构造函数,每个版本对应一种特定的数据类型。每个构造函数的主要职责是:将类型变量设置为对应的数据类型,并同时初始化值变量。其中,对象与数组对应的构造函数较为特殊。

基于上文,对象和数组支持通过列表初始化进行构造。对于对象,列表初始化使用一系列pair (二元组)来完成;对于数组,则直接列举各个元素的值。列表初始化的底层机制与std::initializer_list 相关,它是一个标准库提供的模板类。

json 类定义了两个接收std::initializer_list 的构造函数:一个接收std::initializer_list> ,用于对象初始化;另一个接收std::initializer_list ,用于数组初始化。读者可能会有疑问:为什么数组构造函数的参数是以及pair对象的值的类型都是json 类型?这是因为数组的元素以及二元组的值的类型可以不同,例如:

[1, "hello", true]
{{"name","wz"},{"age",20}}

json 对象本身是“泛型”的,其内部的值变量为联合体,可以容纳任意支持的数据类型。因此,std::initializer_list 模板在这里实例化为json 类型,这是一个需要注意的设计点。

若列表初始化传入的是pair 列表,则会调用接收std::initializer_list> 的构造函数。该函数的执行过程如下:首先,编译器会在栈上构建一个临时的只读数组,其元素类型为std::pair ;接着,std::initializer_list 内部会保存两个指针,分别指向该临时数组的起始与结束位置。在构造函数内部,首先将类型变量设为object ,并在堆上动态分配一个std::map 作为值变量;之后,遍历临时数组,将每个pair 插入该std::map 中。

若传入的不是pair 列表,则会调用接收std::initializer_list 的构造函数。此时,类型变量被设置为array ,并在堆上分配一个std::vector 作为值变量,随后将临时数组中的每个json 元素依次插入该向量。

class json {
// --- 构造函数重载:根据输入决定类型 ---

json(std::initializer_list<std::pair<std::string, json>> init) {
    // 1. 设置类型标签为对象
    m_type = value_t::object;

// 2. 在堆(Heap)上分配 map 空间
m_value.object = new std::map<std::string, json>();

// 3. 遍历栈(Stack)上的临时 pair 数组
for (auto it = init.begin(); it != init.end(); ++it) {
    // 将栈上的 pair 拷贝到堆上的 map 中
    // 注意:这里会递归触发 MyJson 的拷贝构造函数
    m_value.object->insert(*it);
}

}

MyJson(std::initializer_list<json> init) {
    // 1. 设置类型标签为数组
    m_type = value_t::array;

// 2. 在堆上分配 vector 空间
m_value.array = new std::vector<json>();

// 3. 预留空间以减少扩容开销
m_value.array->reserve(init.size());

// 4. 遍历栈上的临时 json 对象数组
for (auto it = init.begin(); it != init.end(); ++it) {
    // 将栈上的临时 json 对象深拷贝到堆上的 vector 中
    m_value.array->push_back(*it);
}

}

// 整数版本构造函数
json(int value) {
    m_type = value_t::number_integer;
    m_value.number_integer = value;
}

// 字符串版本构造函数
json(const char* value) {
    m_type = value_t::string;
    m_value.string = new std::string(value); // 在堆上分配字符串
}

// 析构函数:根据类型释放堆内存
~json() {
    if (m_type == value_t::string) {
        delete m_value.string;
    } else if (m_type == value_t::array) {
        delete m_value.array;
    }
    // ... 其他类型的释放
}

};

在了解json 类的基本构造机制后,接下来介绍几个关键的成员函数——operator[]运算符的重载。
operator[] 有两个重载版本:一个接收std::string 类型参数,另一个接收整型参数。接收std::string 的版本用于处理 JSON 对象(键值对结构)。此时,json 对象的值变量应为一个哈希表(或
std::map )。如果调用该运算符时,当前json对象为空(即类型为null ),则该运算符会先将其类型转为object ,并初始化值变量为空的哈希表,然后插入对应的键值对;若非空,则直接进行键值对的插入或访问。

接收整型参数的版本用于处理 JSON 数组。类似地,若当前json对象为空,运算符会将其类型转为array
,并初始化值变量为一个空的向量。此外,该版本还包含自动扩容逻辑:若访问的下标超出当前数组长度,向量会自动扩容至该下标加一的大小,新增位置将以默认构造的json 对象(即null 类型)填充。

// 接收字符串,处理 {"key": value} 结构
json& operator[](const std::string& key) {
    // 1. 若当前为空对象,则转为 object 类型
    if (m_type == value_t::null) {
        m_type = value_t::object;
        m_value.object = new std::map<std::string, json>();
    }

// 2. 类型检查:若非 object 类型,抛出异常
if (m_type != value_t::object) {
    throw std::domain_error("JSON类型不是object,无法使用字符串key访问");
}

// 3. 利用 std::map 特性:若 key 不存在,会自动插入一个默认构造的 json 对象
return (*m_value.object)[key];

}

// 接收索引,处理 [value1, value2] 结构
json& operator[](size_t index) {
    // 1. 若当前为空对象,则转为 array 类型
    if (m_type == value_t::null) {
        m_type = value_t::array;
        m_value.array = new std::vector<MyJson>();
    }

// 2. 类型检查
if (m_type != value_t::array) {
    throw std::domain_error("JSON类型不是array,无法使用索引访问");
}

// 3. 自动扩容:若索引超出当前大小,调整向量大小
if (index >= m_value.array->size()) {
    m_value.array->resize(index + 1);
}

// 4. 返回对应位置的引用
return (*m_value.array)[index];

}

在了解了operator[] 的基本访问逻辑后,需要注意其返回类型是json 对象的引用。在代码中,我们常会书写如下语句:

int age = j["age"];

此语句的底层执行逻辑如下:首先调用operator[] 重载函数,函数内部检查j 对象不为空且类型为object (而非array 或其他类型),随后找到键"age" 对应的值。该值本身是一个json 对象,函数返回其引用。然而,为了将json 对象转换为int 这样的基本类型,编译器会尝试进行隐式类型转换。具体地,当赋值运算符的左右操作数类型不匹配时,编译器会检查json 类是否定义了相应的类型转换运算符,若已定义,则自动调用。因此,上述代码实际上依次调用了两个重载函数:operator[]operator int()

class MyJson {
public:
    // ... 其他成员 ...

// 1. 转换为 int 的类型转换运算符
operator int() const {
    if (m_type != value_t::number_integer) {
        // 在实际的工业级库中,此处通常会抛出更精确的类型错误异常
        throw std::runtime_error("类型不匹配,无法转换为int");
    }
    return static_cast<int>(m_value.number_integer);
}

// 2. 转换为 std::string 的类型转换运算符
operator std::string() const {
    if (m_type != value_t::string) {
        throw std::runtime_error("类型不匹配,无法转换为string");
    }
    return *(m_value.string); // 解引用指针获取字符串
}

// 3. 转换为 bool 的类型转换运算符
operator bool() const {
    // ... 实现逻辑类似
}

};

另一种常见情况是赋值操作:

j["age"] = 18;

此语句的执行过程是:首先调用operator[] 并返回一个json 对象的引用。这个被引用的json对象作为赋值运算符的左操作数(属于自定义类型),随后会调用其赋值运算符operator= 。该类定义了多个重载版本的operator= ,其中一个接收int 类型参数。该赋值运算符会首先检查当前json 对象的类型是否匹配:如果匹配(例如原本就是整数类型),则直接修改其内部存储的值;如果不匹配,则需要先释放当前对象可能持有的资源(如堆内存),再将类型标签更新为目标类型,并初始化对应的值变量。

MyJson& operator=(int value) {
    if (m_type == value_t::number_integer) {
        // 类型匹配:直接修改值,避免额外的资源释放与分配,性能更优
        m_value.number_integer = value;
    } else {
        // 类型不匹配:需先释放现有资源,再重新构造
        this->destroy();                // 清理当前值所持有的资源
        m_type = value_t::number_integer; // 更新类型标签
        m_value.number_integer = value;  // 设置新值
    }
    return *this; // 返回当前对象的引用,支持链式赋值
}

通过结合operator[] 、类型转换运算符以及赋值运算符的重载,json 类实现了灵活且直观的读写接口,同时在内部保证了类型安全与资源管理的正确性。

json.hpp 的底层设计在一定程度上模糊了数组与对象的界限。其operator[] 不仅是一个访问器,更扮演了构造助手的角色。它利用std::vector 的默认构造特性,以null 对象作为“内存粘合剂”,实现了边访问边构造的灵活性,同时借助my_type 成员确保了 C++ 层面的类型安全。

补充

至此,我们已从使用与原理两个层面解析了 JSON。在原理层面,我并未详细解释dumpparse 的具体实现原理,因为本文的重点在于“使用轮子而非造轮子”。适当了解轮子的构造,有助于我们更从容、得心应手地运用 JSON,但对其底层的理解也应适度——将 JSON 类的实现完全剖析清楚,反而可能收益有限。dumpparse 的具体实现较为复杂,感兴趣的读者可自行深入研究。

对于结构化数据(即 JSON 对象),我们可以调用dump 函数将其序列化为符合 JSON 规范的字符串。该字符串是以字符为单位的字符序列,每个字符通常对应一个字节。dump 函数的作用正是将数据结构转化为连续的字符序列。然而,转换为字符序列后,还需经过一步额外处理才能通过网络发送:即通过特定编码将字符序列转换为字节序列。因此,这里补充说明一下编码的相关知识。

我们知道,计算机底层只能存储二进制序列。但现实生活中大部分信息需以字符串形式表示,因此需要将字符串中的各个字符映射为唯一的二进制值,即进行编码。早期最常见的编码是 ASCII 码,它使用一个字节为英文字母及特殊符号分配唯一的二进制值,其范围是 0~127。

随着计算机的发展,需要表示的字符不再仅限于英文,还包括中文、其他语言字符乃至表情符号等。一个字节已不足以表示如此多的字符,于是 UTF-8 编码应运而生。UTF-8 是当前最主流、最常用的编码方式,它能表示包括中文在内的多国语言,并且完全兼容 ASCII 码。

为了对全球各类字符进行编码,Unicode 标准为每个字符定义了一个唯一的“码点”(Code Point),相当于字符的身份证。UTF-8 则是一种将码点转换为 1 至 4 个字节的二进制序列的规则,使得计算机能够存储和处理这些字符。具体字符映射到几个字节,取决于其码点的大小:

码点范围 (十六进制)字节数字节模板 (二进制)
0000 0000 - 0000 007F10xxxxxxx (完全兼容 ASCII)
0000 0080 - 0000 07FF2110xxxxx 10xxxxxx
0000 0800 - 0000 FFFF31110xxxx 10xxxxxx 10xxxxxx (大部分汉字在这)
0001 0000 - 0010 FFFF411110xxx 10xxxxxx 10xxxxxx 10xxxxxx

此时读者可能会产生疑问:当一个字符映射为多个字节时,会存在字节序(Endianness)问题,但为何在将文本序列化为字符串时,大多数编译器和平台使用 UTF-8 编码却不会遇到字节序问题?

原因在于 UTF-8 是一种面向字节的编码。尽管一个字符可能对应多个字节,但 UTF-8 始终以字节为单位进行解析,而非将多个字节作为一个整体来处理。其解析规则如下:

  • 若某字节的最高位为0 ,则该字节直接对应一个字符(即 ASCII 字符)。
  • 若字符对应多个字节,则第一个字节称为“前导字节”。对于占用 n 个字节的字符(1 ≤ n ≤ 4),其前导字节的高 n 位为1 ,第 n+1 位为0 ,后续的每个辅助字节均以10 开头。这种设计使得解析器能够明确识别字符的起始与边界,从而正确地将多个字节组合解析为一个字符。

字节序问题本质源于多字节整型在内存中的存储顺序,而 UTF-8 在本质上是一种字节流协议。由于 UTF-8 的前导字节已包含字符长度与边界信息,且解析过程是逐字节顺序进行的,因此它天然避免了因 CPU 大小端架构差异所引发的问题。

相对地,像 UTF-16 这类定长编码(每个字符固定对应 2 或 4 字节),由于需将多个字节作为一个整体解析,就会受到平台字节序的影响。

JSON 标准明确规定使用 UTF-8 作为其编码方式,这进一步确保了其在不同系统和环境间的兼容性与一致性。

HTTP

引入

在上文详细讲解了JSON之后,接下来我们将过渡到HTTP的内容。在具体介绍HTTP协议之前,我仍然通过一个例子来引入。

大家平时应该都有使用浏览器上网的习惯。我们常常在浏览器中输入一个网址,用来打开或获取网页,甚至是观看视频等。但你是否想过,在输入网址的背后,其实涉及客户端与服务端之间的通信原理。浏览器作为客户端,会与服务端进行通信,而最终呈现给我们的各种网页、视频等内容,都可以统称为“资源”。这些资源都是从服务端获取的,而HTTP正是一个应用层的通信协议。

要理解HTTP的原理,我们首先要从整个通信过程的起点说起,也就是在浏览器输入网址的那一刻。

原理

根据上文可知,我们获取网页、音视频等资源的过程,其背后实际基于客户端-服务器模型(Client-Server Model),即客户端与服务器之间的通信。现在,让我们从这一通信过程的起点开始讲解——也就是在浏览器中输入网址的那一刻。首先需要明确的是,由于该过程本质上是客户端与服务器之间的通信,因此通信双方必须依赖 IP 地址 与 端口号,才能确保数据准确发送到目标主机上的对应进程。IP 地址通常表示为点分十进制形式的字符串,端口号则为整数值。然而,我们在输入网址时,并不会手动输入 IP 地址和端口号,却在按下回车键之后,对应的网页、音视频等资源几乎立即呈现在浏览器中。这究竟是如何实现的呢?

这就需要我们先了解“网址”这一概念。网址是一种通俗的说法,其专业术语是 URL(Uniform Resource Locator,统一资源定位符)。一个完整的 URL 通常由以下几个部分组成:协议、域名、端口、路径、查询参数和片段。

例如:

https://www.example.com:443/music/list?id=1024&type=pop#comment
组成部分示例内容专业术语
协议https://Scheme
域名www.example.comDomain/Host
端口:443Port
路径/music/listPath
参数?id=1024&type=popQuery String
锚点#commentFragment

URL 被称为“统一资源定位符”,是因为我们在浏览器中所见的各种内容——无论是网页、图片还是视频——本质上都是资源,通常存放在服务器上。客户端(浏览器)向服务器发起请求以获取这些资源。为了准确定位资源,不仅需要知道服务器的 IP 地址和端口,还需明确资源在服务器上的具体位置,这正是路径所起的作用。因此,URL 实际上提供了一种统一的方法来定位网络上的资源。关于路径的具体细节,我们将在后文进一步展开说明。

以上简要说明了路径的作用,端口我们也熟悉其含义,而协议则对应通信双方所使用的应用层协议。那么,域名又是什么?接下来的内容将围绕域名展开讲解。

域名

实际上,在浏览器中可以直接输入 IP 地址来替代域名进行访问。既然如此,为什么大多数情况下我们仍使用域名呢?原因在于,IP 地址是一串点分十进制数字,对普通用户而言并不友好。如果每次访问网站都需要记忆和输入 IP 地址,将会非常不便。相比之下,域名更为直观,例如www.baidu.com 能让人联想到百度网站,而不暴露其背后的 IP 地址信息。我们可以将域名比作“人名”,而 IP 地址则相当于“身份证号”——显然,使用域名访问更加直观和方便。

然而,问题随之而来:从形式上看,域名与 IP 地址并无直接关联,但获取资源的本质是进程间通信,必须依赖 IP 地址。因此,域名必须通过某种方式转换为对应的 IP 地址。这一转换过程即是接下来要介绍的域名解析服务。

在深入讲解域名解析之前,我们需要先了解域名的基本格式。一个完整的域名由“.”分隔为若干部分,从右向左依次为:顶级域名、二级域名、三级域名等。通常,在完整域名的末尾还有一个表示根域的“.”。

例如:

www.example.com.
根域名(Root Domain):.

顶级域名(Top-Level Domain, TLD):.com

二级域名(Second-Level Domain, SLD):example

三级域名(Third-Level Domain):www

在了解了域名的组成之后,接下来将分别说明这些域名的含义与作用。首先从根域名开始,根域名通常表示为顶级域名右侧的一个点(“.”),它是所有域名的起点,这是一种约定俗成的设计。其次是顶级域名。

顶级域名主要分为两类:一类是国家及地区域名,另一类是通用域名。通过顶级域名,可以反映网站的背景或用途。国家及地区域名常见的有:

  • .cn (中国)
  • .us (美国)
  • .jp (日本)
  • .uk (英国)

如果使用国家或地区域名,通常意味着该网站受相应国家或地区的法律法规约束。相比而言,通用域名更为常见,并能更直接地体现网站的用途或所属行业,因为它们通常与机构、组织或特定行业相关联。例如:

  • .com 代表商业用途;
  • .net 代表网络服务机构;
  • .org 代表非营利性组织;
  • .edu 原指美国高等教育机构,该后缀主要由美国使用,中国对应使用二级域名.edu.cn
  • .gov 代表美国政府机构。

此外,如今的通用域名已大大扩展,出现了诸如.museum (博物馆)、.shop (电商平台)等新后缀。

域名后缀代表含义适用对象
.comCommercial最初限企业,现已演变成全球通用的商业标识。
.orgOrganization各种非营利性机构、开源项目。
.netNetwork最初为网络基础设施(ISP)设计。
.eduEducation主要是美国高等教育,中国则对应二级域名 .edu.cn
.govGovernment仅限政府机构使用,具有极高权威性。

通过顶级域名,用户可以初步判断网站的性质与用途。紧接着顶级域名的是二级域名。上文提到,域名的作用是对网站进行身份标识,使用户能够了解网站的背景与用途。而二级域名可进一步增强网站的身份识别性与记忆点。

二级域名通常与品牌、企业或个人身份等内容紧密关联,例如www.google.comwww.baidu.com 。若需进一步添加个性化信息,还可以继续设置三级、四级域名等。

在了解域名的各个组成部分后,还需要补充一点关于域名申请的说明。首先要明确的是,申请域名并不是一次性获得完整域名,而是需要先确定顶级域名。申请顶级域名一般分为两种情况:

第一种是创建全新的顶级域名。这种情况需向ICANN(互联网名称与数字地址分配机构)这一最高管理机构提交申请,审核该域名是否符合条件,并缴纳注册费。若申请成功,该顶级域名将交由申请者或其委托的机构管理,包括其下所有子域名的注册事务。不过这种情况较为少见。

更常见的是第二种情况,即申请已注册的顶级域名(如.com )。每个顶级域名通常有专门的注册局进行管理,负责该顶级域名下子域名的注册,并与注册商(如腾讯云、阿里云等)对接。注册商直接面向用户,帮助其申请二级域名。注册商会查询注册局的数据库,若该二级域名未被占用,即可完成注册并缴纳相应费用。需注意的是,二级域名并非永久有效,一旦到期未续费,该域名将被释放。

可以这样理解:顶级域名如同一个集合,二级域名则是在该集合中开辟出属于你自己的子集。获得二级域名后,就像开发商获得一块地皮,可在其下进一步设置子域名。子域名的管理不再通过注册商,但需要向注册商提供一个权威域名服务器地址,以便注册商知晓该二级域名对应的IP地址。这部分内容与后文将介绍的DNS解析相关,具体原理将在后续详细说明。


在了解了域名的构成、含义以及申请方式之后,接下来便进入域名解析环节,即域名如何转换为IP地址。这一过程与DNS服务器密切相关。DNS服务器专门负责域名解析,它会接收DNS查询请求,并返回该域名对应的IP地址。

在进行DNS查询之前,系统会首先在本机缓存中查找是否有该域名映射的IP地址。如果之前曾在浏览器中访问过该域名对应的网站,浏览器可能会保留相应的缓存。若浏览器缓存未命中,则会查询操作系统缓存;若操作系统缓存也未命中,则会进一步查询磁盘上的hosts文件。hosts文件中存储的内容是一组组“域名-IP”映射条目,因此域名解析会优先查找本机的这三级缓存。

需要补充的是,hosts文件的优先级高于本地DNS服务器查询。我们有时利用这一机制屏蔽浏览器中弹出的广告,具体方法是将广告对应的域名映射到本机IP地址(如127.0.0.1),并将该条目添加到hosts文件中。这样一来,在解析该域名时,会直接指向本机,从而阻止广告内容的加载。

如果本地缓存均未命中,则需要查询本地DNS服务器。本地DNS服务器通常由用户手动配置或由DHCP服务自动分配。此时,主机会向本地DNS服务器发送一个DNS查询报文,请求解析该域名对应的IP地址。若本地DNS服务器中存有该记录,则直接返回IP地址。

若本地DNS服务器没有缓存该记录,则会启动迭代查询流程。首先,它会向根域名服务器发送请求,询问应如何解析该域名。根域名服务器存储了所有顶级域名服务器的地址,它会根据域名中的顶级域名(如.com、.org等),返回对应顶级域名服务器的IP地址,指引本地DNS服务器向下一级查询。全球共有13组根域名服务器IP地址,但每组IP背后对应着分布在全球各地的多台服务器,查询时会通过任播技术路由到距离最近的服务器节点。

接着,本地DNS服务器会向获得的顶级域名服务器发起查询。顶级域名服务器则管理其下属的权威域名服务器信息,并根据域名中的二级域名部分,返回对应的权威域名服务器地址。本地DNS服务器随后向该权威域名服务器查询,最终获得域名对应的真实IP地址,并将其返回给请求主机。至此,完成域名解析的全过程。

主机获得IP地址后,浏览器、操作系统及hosts文件都可能建立相应缓存,以加速后续解析。但这些缓存条目均具有时效性,到期后会被自动清除。本地DNS服务器在返回IP地址时,也会将这一映射关系缓存到本地,以提高相同域名的解析效率,该缓存同样设有有效期,常用于存储访问频率较高的域名记录。如果缓存失效或未命中,本地DNS服务器将重新执行从根域名服务器开始的迭代查询流程。


在了解了域名解析服务后,我们对网络通信流程的认知会更加清晰。根据上文的说明,通信的起点是在浏览器地址栏中输入URLURL 本质上是一个字符串,浏览器作为客户端,在获取用户输入的URL 字符串后,会按照其构成进行解析,将其拆分为协议、域名、路径和查询参数等部分。

由于浏览器需要与服务器进行通信,因此必须首先获得服务器的IP 地址,这一步即为域名解析。浏览器会先查询本地缓存,若未命中,则向本地DNS 服务器发起查询,最终获取到对应的IP 地址。获得IP 地址后,浏览器便可开始与服务器建立通信。

HTTP 作为应用层协议,基于 TCP 传输协议实现。因此,浏览器作为客户端,首先需要与服务器建立连接,即完成TCP 三次握手。三次握手成功后,客户端与服务器才正式进入通信阶段。接着,浏览器会构建一个请求报文,发送给服务器,报文中包含对特定资源(如网页、图片等)的请求。服务器作为资源的持有者,在接收到请求报文后,会处理该请求,定位对应资源,并构建响应报文。响应报文中携带客户端请求的资源,随后将其返回给客户端。以上便是完整的通信流程。

理解这一过程,有助于我们更清晰地认识 Web 服务器的工作原理。掌握该流程,意味着已经理解了大部分基本原理,后续只需对这一过程中涉及的具体细节进行补充说明。首先便是HTTP 协议。

HTTP协议

HTTP协议作为应用层协议,定义了通信双方交互的规则。应用层协议所规定的内容,其本质上是对请求与响应报文的格式进行约束。通信双方必须对请求和响应报文的格式有明确的共识,才能正确解析对方发送的内容。

HTTP是一种文本协议,这一特性体现在其请求与响应报文的格式上。文本协议的主要特征是所传输的数据由字符串构成,这意味着HTTP的请求与响应报文并非纯二进制流,而是字符序列,因此对人类是可读的。

首先来认识HTTP请求报文的格式。请求报文可由三部分或四部分构成,包括请求行、请求头、空行和可选的请求正文。之所以说“三部分或四部分”,是因为请求正文是可选的,它是否存在取决于具体请求方法,这一点将在后文说明。

HTTP请求报文的开头是请求行,由请求方法、URL和协议版本三部分组成,各部分之间以空格分隔。请求行之后是请求头,它由多行键值对组成,每行以回车换行符 结束。请求行与请求头之间也通过一个回车换行符分隔。

请求头之后是一个空行,即单独的 。由于请求头每行已以 结尾,因此请求头末尾会连续出现两个换行符,即 标志着请求头的结束。空行之后的部分即为请求正文。

示例:

"POST /api/login HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 27
Connection: keep-alive

username=admin&password=123456"

接下来详细说明请求报文各组成部分的含义与作用。首先是请求行,它包含请求方法、URL和协议版本三个部分,各部分以空格分隔。请求行的作用是告知服务器执行何种操作——这是通过第一个字段“请求方法”来体现的。此外,它还指明了操作的目标资源(URL)以及所使用的协议版本。

请求方法 定义了客户端希望服务器执行的具体操作。HTTP 协议定义了多种请求方法,常见的如下表所示:

请求方法语义 (Action)数据位置是否有 Body幂等性*安全性**典型应用场景
GET获取资源URL 查询参数浏览网页、搜索图片、查询余额
POST新增或处理资源请求体 (Body)注册账号、发表评论、上传文件
PUT更新(全量覆盖)请求体 (Body)修改用户完整档案、上传同名覆盖文件
PATCH更新(局部修改)请求体 (Body)只修改用户的头像或改个密码
DELETE删除资源URL 路径注销账户、删除一条朋友圈
HEAD获取头部信息N/A检查链接有效性、获取文件大小
OPTIONS查询支持的方法N/A跨域(CORS)前询问服务器允许哪些操作
TRACE回显服务器收到的请求N/A用于诊断或测试网络路径中的代理

注:幂等性 指同一操作执行一次或多次对服务器端的资源状态不会产生额外影响,即效果相同。例如 GET 请求仅从服务器获取数据,发起一次与发起多次对该资源本身没有区别(因为它是只读的)。而 POST 不具备幂等性,是因为其通常用于提交数据,例如在数据库中新增一条记录,执行一次与重复执行多次所产生的效果不同(会导致多条记录被创建)。安全性 则指该操作本身不应引起资源状态的改变。

根据上表可以看出,HTTP 协议定义了多种请求方法,每种方法对应特定的语义操作。在实际开发中,并不需要掌握所有方法,因为绝大多数 HTTP 通信场景只涉及GETPOST 两种方法。其中一个重要原因是,POST 方法在功能上具有一定的涵盖性,能够替代其他某些操作(这一点将在后文说明)。因此,掌握GETPOST 即可满足大部分基础开发需求。

GET请求

首先是GET 方法,其作用是从服务器获取资源。之前提到,这类资源可以是网页、图片、视频等,它们通常以文件形式存在于服务器上。例如,网页本质上是一个HTML 文档,图片和音视频则是二进制文件。服务器在磁盘中存储这些文件,而GET 请求的目标就是根据客户端提供的路径,定位并返回对应资源。

以网页为例,用户在浏览器中看到的内容是由浏览器渲染得到的。浏览器不仅作为网络通信的客户端,还负责解析和呈现网页内容。网页的骨架是HTML 文档,其基本结构包含以下三部分:

DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>我的第一个网页title>
head>
<body>
    <h1>欢迎来到我的网站h1>
    <p>这是一个关于 HTML 结构的探讨。p>
body>
html>
  • 文档类型声明:首行的 用于告知浏览器HTML 版本,确保其以符合标准的方式解析文档。
  • 头部: 部分定义网页的元信息,如字符编码、标题等。
  • 主体: 部分包含网页的实际内容,即用户在页面上看到的所有信息。

浏览器通过解析HTML 文档,将其转换为可视化的网页界面。作为后端开发者,我们通常不需要深入掌握HTML 细节——这属于前端开发的范畴。但在后续编写完整 Web 服务器代码时,可能会涉及简单网页的构建,因此在此对其结构作简要说明。

服务器上持有的资源,如网页(文本文件形式的HTML文档)或图片、音视频等(二进制文件),通常存储在服务器的文件系统中。GET请求的核心目的,正是从该文件系统中获取这些资源对应的文件。为了实现这一目标,客户端必须在请求中提供一个路径,以便服务器能够准确定位资源。随后,服务器将找到的资源载入响应报文,并返回给客户端。因此,请求行中的URL部分,其作用就是指明这个资源路径。

请求行中的 URL 是浏览器中所输入 URL 去除协议、域名和片段(fragment)后剩余的部分,仅包含路径与查询参数。在此,我们终于可以明确解释“路径”的含义:路径的第一层含义是代表服务器文件系统中的某一目录路径。但需要说明的是,路径虽然以/ 开头,并不表示它一定是文件系统的绝对路径(即从根目录开始)。在实际的服务器配置中,路径通常是相对于服务器设定的根目录(如网站根目录)来解释的。

刚才我特别强调“第一层含义”,是因为路径所指示的对象并不一定是静态文件,也可能是一个可执行程序。这种情况下,请求的目的并非获取静态资源,而是获取动态资源。此时,服务器需要调用该可执行程序,获取其输出结果。典型做法是:服务器通过fork() 创建子进程,再通过exec() 系列接口将子进程替换为目标可执行程序,接着建立管道获取程序输出,最后将输出填入响应报文并返回客户端。此外,路径甚至可以不对应实际文件,而表示服务器内部的一个可调用函数。服务器识别该 URL 后,会调用相应的内部函数,并将其返回值放入响应报文中。这一点将在后文结合具体场景进一步说明,此处请先形成初步印象。

而这里之所以说 POST 能够涵盖其他操作,是因为 POST 的 URL 可以作为虚拟路径,映射到服务器内部预定义的可调用函数

在这种设计下,原本属于 DELETEPUT 等请求方法的操作逻辑,现在直接被写在了这些函数的内部;而请求报文携带的正文内容,则作为参数传递给函数进行处理。这也就解释了上文埋下的伏笔:为什么绝大多数场景只涉及 GET 和 POST,因为我们完全可以通过“POST + 虚拟路径”的方式,在函数层面实现任何复杂的业务操作。

POST /deleteUser?id=1  ->映射到内部 delete_user_func(id)
POST /updateUser      -> 映射到内部 update_user_func(data)

URL 还可能包含查询参数(query string),其作用是对资源进行附加处理,例如数据筛选、排序、分页等:

数据筛选:GET /products?category=phone
// 服务器只返回手机类商品,而非全部商品

排序:GET /users?sort=age_desc
// 服务器对获取的数据按年龄降序排列后再返回

分页:GET /news?page=2&size=10
// 避免单次返回数据量过大,节省带宽

还需补充的是,查询参数有其固定格式:它以? 与路径分隔,由多个键值对组成,键值对之间以& 分隔。若键或值中包含特殊字符(如/?& 等),为避免解析冲突,需要对它们进行编码。通常使用 UTF-8 编码将字符转换为二进制序列,由于&% 等特殊字符通常映射为单字节,因此对应两个十六进制数字。接着将该十六进制值前加上% 作为转义前缀。当服务器检测到 URL 含有查询参数时,首先会以"&" 分割出各个键值对,再以"=" 分割出键与值。之后,服务器会检查键和值中是否出现以% 开头的转义序列(即 URL 编码部分)。如有,则需对其进行解码,将其恢复为原始字符。

原始字符特殊含义编码后 (Hex)
空格分隔符(旧标准变 +%20
/路径分隔符%2F
?查询参数起始符%3F
&键值对分隔符%26
=键值对连接符%3D
%编码引导符本身%25

最后是HTTP协议版本。HTTP协议存在不同版本,其主要差异体现在TCP传输层的使用方式上。目前主流的版本是1.0和1.1。HTTP/1.0默认采用短连接,即服务器在处理完客户端的一次请求后便会主动断开连接。如果客户端需要再次向同一服务器发送请求,则必须重新进行TCP三次握手以建立新连接。而HTTP/1.1默认使用长连接。

上文提到,一个网页实际上对应一个HTML文档,而一个网页通常包含多种资源,如图片、音频、视频等。这些都属于网页所需的资源。浏览器要完整渲染该网页,仅获取HTML文档是不够的,还必须加载所有相关资源。因此,HTML文档中通常会记录这些资源的路径。当用户访问该网页时,客户端首先向服务器发送请求,获取对应的HTML文件。服务器解析请求报文,定位到文件并返回给客户端。浏览器解析HTML时,若发现还需要加载图片等资源,则会继续向服务器发送请求获取这些资源。

在上述场景中,客户端通常需要连续向服务器发送多个请求。如果使用短连接,将会涉及大量重复的TCP三次握手和四次挥手过程,从而显著降低传输效率。长连接正是为了应对这种情况而设计:通信双方只需完成一次三次握手,建立一条持续的TCP连接。客户端可以通过该连接连续发送多个请求,服务器在收到请求后不会立即断开连接,而是依次解析这些请求,并返回对应的响应报文。如果客户端希望结束连接,或在发送最后一个请求时,可通过请求头中的Connection 字段通知服务器。服务器检查该字段,若其值为close ,则会主动关闭连接。

此外,HTTP还有2.0和3.0版本。由于目前所学知识尚不足以深入理解这两个版本的区别,本文仅重点介绍常见的1.0与1.1版本。


在掌握请求行及其各字段含义后,接下来介绍请求头。根据上文,请求头由若干行键值对构成,每对之间以回车换行符分隔。每个键值对对应一个特定属性,其内容总体可分为四个方面:一是逻辑站点信息,二是客户端身份信息,三是客户端向服务端发起的内容协商(可视为客户端的“期望清单”),四是连接状态协商。

首先,第一个方面对应的字段是HostHost 字段的值为 URL 中的域名部分。在客户端与服务端建立连接之前,会先提取 URL 中的域名并进行域名解析,将其转换为 IP 地址。IP 地址用于标识目标主机,而端口号则用于标识该主机上的特定进程。一个进程可能提供多个服务,例如一台主机可同时承载www.baidu.comwww.google.com 的服务。通过“IP地址 + 端口号 ”这个二元组,可以定位到目标主机上的服务器进程,而该进程可能管理多个站点。此时,需依据Host 字段确定具体请求哪个站点的资源,从而将服务器根目录切换至对应站点的目录。这个过程类似于总机将电话转接至相应分机。

Host: sales.com   // 对应 /var/www/sales 目录
Host: tech.com    // 对应 /var/www/tech 目录

因此,域名解析得到的 IP 地址与端口号定位的是物理主机上的服务器进程,该进程如同设有多部门的大楼。服务器进程根据Host 字段进行“分流”,即切换到对应的逻辑站点。在代码层面,这会体现为修改路径的根目录,将其映射到目标站点的根目录,从而正确解析相对路径并获取该站点的资源。随后,请求会被路由到对应的处理函数或进程,其上下文即代表该站点的业务逻辑。

这里补充说明一下“站点”这一概念。有的读者可能对这个专业术语不太熟悉,如果用更通俗的方式来理解,站点通常对应一个域名,但它本质上不等同于域名本身。域名只是站点的访问地址,而站点则是在该地址下所有资源的集合。

我们可以将其与网页进行对比。网页、图片等都属于资源,而站点正是这些资源的集合体,它持有并组织所有归属于它的资源。多个网页及其相关的静态资源(如图片、样式表、脚本等)共同构成了一个完整的站点。

如果用比喻来说明:访问一个网页,就像是阅读一本书中的某一页;而访问一个站点,则相当于阅读整本书——它包含了所有的章节、图表及附属内容,是一个完整的信息集合。

第二个方面是客户端的身份信息,对应User-Agent 字段。该字段的值包含客户端的操作系统、浏览器等信息,使服务端能够识别客户端类型,例如是桌面设备还是移动设备,是 Windows 系统还是 Mac OS 系统。服务端可根据设备类型返回不同的页面,如为移动端返回轻量化页面,为桌面端返回功能更丰富的页面。

此外,User-Agent 在反爬虫机制中也有重要作用。爬虫程序通常自行构造 HTTP 请求报文来获取网站资源,而请求头中必须包含User-Agent 字段。因此,爬虫常试图伪造该字段以模拟浏览器请求。服务端会检查User-Agent 的合法性,若发现异常,可判定请求并非来自正常浏览器,从而拒绝响应。因此,User-Agent 也用于验证客户端身份的合法性。

第三个方面是客户端对服务端的内容协商,主要涉及Accept 字段。该字段用于指明客户端期望接收的响应正文数据类型,例如 JSON 或纯文本等。需注意,Accept字段通常提供多个选项,而非单一类型。例如,若服务端无法返回 JSON 格式,仍可协商返回其他类型的数据。这里引入“q 值”(权重值)的概念,它使客户端能够为不同内容类型设置优先级,从而与服务端进行灵活协商。

Accept: application/json, text/html;q=0.9, text/plain;q=0.8

q 值介于 0 到 1 之间,数值越高,优先级越高。Accept 字段中多个选项以逗号分隔,第一个选项默认 q=1。

内容协商还包括响应正文的压缩方式,对应Accept-Encoding 字段。其值与Accept 类似,由多个表示压缩方式的条目组成,每个条目可附带 q 值以标示优先级。

Accept-Encoding: gzip, deflate, br;q=0.9, *;q=0.5
// * 表示其他压缩方式,优先级最低(q=0.5)

此外,Accept-Language 字段用于协商响应正文的自然语言,即页面内容的语言版本。服务器可能针对同一资源提供多语言版本(如index_zhe.htmlindex_en.html ),并根据该字段选择对应资源。其取值同样支持多个带权重的选项。

Accept-Language: zh-CN,zh;q=0.9,en;q=0.8

最后是连接状态协商,通过Connection 字段实现。若其值为keep-alive ,表示告知对方保持连接,后续仍有请求或响应;若为 close ,则表示本方即将关闭连接,当前报文为最后一条,发送后应主动断开。

Connection: keep-alive

通过以上四个方面,请求头在 HTTP 通信中承载了关键的控制与协商信息,确保客户端和服务端能够高效、准确地交换数据。

POST请求

上文介绍了GET请求,接下来介绍POST请求。POST请求与GET请求的主要区别在于请求正文:GET请求通常不需要携带请求正文,因为它仅用于获取资源;而POST请求则需要携带请求正文,用于向服务器提交数据,请求服务器进行动态处理并返回结果。

POST请求的URL不一定对应实际文件路径,也可以是虚拟路径。该路径会被映射到服务器内部预定义的函数,调用该函数并将返回值放入响应报文中。请求正文则相当于传递给该函数的参数,其内容通常来源于用户在浏览器输入框中输入的数据。这就引入了“表单 ”的概念。

表单在HTML文档中定义,用于收集用户输入,并将数据上传至远程服务器。例如:

<form action="/login" method="POST">
  <input type="text" name="user"> 
  <input type="password" name="pwd"> 
  <button type="submit">登录button>
form>

表单包含以下关键信息:

  • action:指定数据提交的URL,一般对应服务器端处理该请求的函数。
  • method:定义提交方法,常见值为GET或POST,属于表单的元数据之一。

表单中的每一个 元素对应一个输入项。例如上例中,name="user" 表示用户名输入框,name="pwd" 表示密码输入框,共同构成一个登录认证界面。

用户提交表单后,浏览器会将输入的数据整合到请求正文中,格式为一系列键值对,各键值对之间用"&" 分隔。如果键或值中包含特殊字符(如&/ 等),浏览器会自动对其进行编码。

user=admin&pwd=123

通过浏览器开发者工具(按F12)查看网页元素,可看到如百度搜索框等内容也是通过表单实现的。

除了文本数据,表单也支持文件上传。文件内容同样被整合到请求正文中。文件可分为文本文件和二进制文件。如果沿用默认的文本模式(即键值对字符串格式),遇到文件内容中含有& 等特殊字符时,解析会出现冲突。若对文件中所有特殊字符进行编码,则处理成本较高,且二进制文件可能包含无法映射为字符的字节值,因此文本模式不适用于文件上传。

此时需使用multipart/form-data 格式。由于POST请求包含请求正文,其请求头除包含与GET请求相似的字段(如Host、Accept等)外,还会包含描述正文属性的字段,主要有:

  • Content-Type:指示正文的数据类型。
  • Content-Length:说明正文的长度,以便服务器确定消息边界。

Content-Type的值表示正文所采用的编码模式:

  • 若为普通键值对文本,值为application/x-www-form-urlencoded ,强制要求使用 URL 编码(Percent-encoding)。所有的空格变成 +,所有的特殊字符(如 &)变成 %XX。。
  • 若包含文件上传,值为multipart/form-data ,并且后面会指定一个分界字符串。

分界字符串是随机生成的、较长且复杂的字符串,用于分隔正文中不同部分(如不同字段或文件)。每个部分之前以-- 加分界字符串开始,正文结束处以-- 加分界字符串再加-- 结束。这样可避免与内容中的文本意外重合。

示例:

假设在网页中输入用户名为“张三”,上传文本文件 note.txt(内容为Hello&World ),并上传二进制图片 avatar.jpg,则请求正文格式大致如下:

------MyBoundary
Content-Disposition: form-data; name="user"

张三
------MyBoundary
Content-Disposition: form-data; name="note"; filename="note.txt"
Content-Type: text/plain

Hello & World
------MyBoundary
Content-Disposition: form-data; name="avatar"; filename="avatar.jpg"
Content-Type: image/jpeg

[图片的二进制数据]
------MyBoundary--

每个部分的首部包含元数据,其中:

  • Content-Disposition 为form-data ,表示该部分来自表单。
  • name 对应HTML中name 属性。
  • 对于文件,会包含filename 指明文件名,并由Content-Type 指明其MIME类型。

对应的表单HTML示例:

<form action="/api/upload" method="POST" enctype="multipart/form-data">
  
  <label>用户名:label>
  <input type="text" name="user" value="张三">

  <label>备注文档:label>
  <input type="file" name="note">

  <label>上传头像:label>
  <input type="file" name="avatar">

  <button type="submit">提交整车货物button>
form>

而需要补充说明的是,表单 (Form)也可以设置为通过GET方法提交数据。由于GET请求不携带请求正文,表单数据会被整合到URL 中,以查询参数 (Query Parameters)的形式出现。查询参数 位于路径之后,以"?" 符号分隔,其本身由"键=值" 对组成,多个参数之间用"&" 连接,格式与之前介绍的查询参数一致,只是与POST请求存放的位置不同。此外,如果表单值中包含特殊字符(如空格、中文等),仍会进行URL编码以确保传输的可靠性。

提交方式数据藏在哪?报文里的样子有无正文?
GET请求行 (URL)GET /path?name=tom&age=20 HTTP/1.1
POST请求正文Content-Type: multipart/form-data...

注:由于GET方式将数据暴露在URL中,这意味着如果提交的表单包含敏感信息(例如用户名、密码、身份证号等),这些信息会直接显示在浏览器的地址栏中,因此存在泄露风险。因此,GET方法仅适合用于非敏感、可公开、可分享的查询操作,而不应用于传输密码、个人身份信息等敏感数据,此外,由于URL长度存在限制(通常因浏览器与服务器的不同,在数千字符以内),通过GET请求提交的表单无法传输大量数据,例如文件上传。

POST方式将数据放在请求正文中,更适合提交敏感或大量数据(如文件上传)。

响应报文

上文梳理了请求报文之后,接下来梳理响应报文。响应报文的格式与请求报文高度对称,也由四部分组成,分别是响应行、响应头、空行和响应正文。每个部分之间会有空格分隔,末尾会有一个回车换行符,下文将依次解析这四个部分的构成及相关细节。

首先是响应行。响应行由三部分组成,分别是协议版本、状态码和状态描述。其中状态码是一个3位十进制数字,首位数字为1~5,分别对应不同的状态类别:

类别含义场景比喻常见例子
1xx信息性状态码“收到了,别急,处理中…”101 Switching Protocols:升级到 WebSocket 协议。
2xx成功状态码“没问题,你要的东西在这。”200 OK:请求成功;201 Created:上传文件成功。
3xx重定向状态码“你要的东西搬家了,去那取。”301:永久搬家;302:临时出差;304:你本地有缓存,直接看缓存。
4xx客户端错误“你的请求有误,我没法办。”400:参数写错了;403:我有但不给你看;404:我这没这东西。
5xx服务器错误“我出故障了,稍后再试。”500:后台程序崩溃了;502:网关坏了;504:后台超时了。

首位为1的状态码表示服务器已收到客户端请求,正在处理中,此时会先返回一个1xx的响应报文。首位为2的状态码表示服务器已成功接收并正确处理请求,最常见的如200,描述为“OK”。

首位为3的状态码表示重定向。所谓重定向,是指服务器上原有站点可能已临时或永久关闭,但客户端并不感知,仍会访问旧地址。服务器收到请求后,发现目标站点已关闭,便会构造一个响应报文。若为临时关闭,状态码设为302;若为永久关闭,则设为301。同时,响应头中会设置一个Location 字段,其值为新站点的URL,而响应正文通常为空。

HTTP/1.1 301 Moved Permanently
Location: https://www.new-site.com/index.html

Content-Length: 0
---------------------------------------------------------------------

HTTP/1.1 302 Found
Location: https://example.com/login.html

客户端收到此类响应后,会依据Location 字段重新向新地址发起请求。对于临时重定向,浏览器一般不会缓存该跳转;而对于永久重定向,浏览器会进行缓存,之后用户再次访问原地址时将直接请求新站点,无需再次收到重定向响应。

首位为4的状态码表示客户端错误,通常是请求的资源不存在。这类错误多由GET请求触发,因为GET请求的URL通常对应服务器文件系统中某个资源的路径。若该路径无效或资源不存在,则属于客户端错误——客户端请求了不存在的资源。此时服务器会构造状态码为404的响应报文,并通常返回一个自定义的错误页面(HTML文档)作为响应正文,浏览器渲染后即呈现常见的“404页面”。另外,状态码403表示请求的URL有效,服务器能找到对应资源,但客户端无权访问,例如尝试访问服务器配置文件时会返回403。

首位为5的状态码表示服务器内部错误,常见如500。例如服务器端处理程序抛出异常并被捕获后,服务器会构造状态码为500的响应报文返回给客户端。


解析完响应行后,接下来是响应头。响应头的格式与请求头相同,由一行行键值对构成,每个键值对之间以回车换行符分隔。响应头中的大部分字段与请求头一致,但由于响应报文中通常包含响应正文,因此响应头会包含描述正文属性的字段,主要是 Content-Type 和 Content-Length,分别用于指明正文的数据类型和长度。

除了这两个常见字段,响应报文还可能包含一些特有的头部字段,例如 Set-Cookie。要理解这个字段的作用,需要先回顾一下 HTTP 连接的基本行为:每当客户端向服务器请求资源时,都需要发送一个请求。如果使用短连接,则客户端每次请求后,服务器返回响应,连接即关闭;下次再请求资源时,必须重新建立连接。

重要的是,这些连接之间是相互独立的,即一次连接中进行的通信内容与之前的连接毫无关联,每个连接都是全新的会话。这类似于每次与某人聊天都不保存历史记录,每次都从头开始——这也正是 HTTP 协议无状态(stateless)特性的体现。

在这种无状态模式下,若某个网站要求用户身份认证,就会带来问题。例如,用户访问页面 A 并完成登录,接着跳转到页面 B 时,由于 HTTP 无状态,服务器无法得知这次请求来自之前已认证的用户,因此会再次要求提交身份信息。这意味着每访问一个新页面,都可能需要重新登录,体验十分繁琐。

为了解决这个问题,常见的做法是利用会话(Session)机制。当客户端首次向服务器发起请求时,服务器会为其生成一个唯一的 Session ID,用以标识该会话。服务器通常会管理来自不同客户端的会话,一般通过一个结构体来记录会话信息(例如用户名、登录时间、Session ID ,过期时间等),并将这些结构体以 Session ID 为键组织成哈希表,以提升查询效率。

在构建响应报文时,服务器会在响应头中添加 Set-Cookie 字段,将 Session ID 通过该字段返回给客户端。Set-Cookie 除了携带会话标识外,通常还包含若干属性,用于控制 Cookie 的作用范围、有效期等。例如:

  • Domain:指定 Cookie 生效的域名。例如,在taobao.com 站点完成登录后,该站点的 Cookie 通常仅在此域名下生效。当访问其他站点(如baidu.com )时,浏览器不会发送属于淘宝的 Cookie,从而实现安全的跨站隔离。
  • Path:进一步限制 Cookie 的有效路径,即 URL 的路径部分。它可以限定 Cookie 仅在某个路径下有效(如/music仅对音乐相关页面共享登录状态),或在整个站点生效(path=/)。Path 可视为对登录状态共享范围的一层更细粒度的约束。
  • Expires:指定 Cookie 过期的绝对时间,格式为标准的 HTTP 日期时间。
  • Max-Age:指定 Cookie 的有效时长(以秒为单位),是一个相对时间。如果同时设置了ExpiresMax-Age ,通常Max-Age 优先级更高。

示例格式如下:

Expires=Wed, 21 Oct 2026 07:28:00 GMT
Max-Age=3600

通过合理设置这些属性,服务器可以精确控制会话状态的作用域和生命周期,在保障安全的前提下维持用户的登录状态。

客户端(通常是浏览器)收到后会将 Set-Cookie中的所有字段保存起来,可以存储在内存中(浏览器关闭后失效),也可以持久化到磁盘。

Set-Cookie: session_id=abc123456; Expires=Wed, 21 Oct 2026 07:28:00 GMT; Path=/;

此后,客户端再次向该服务器发送请求时,会在请求头的 Cookie 字段中携带这个 Session ID。服务器接收到后,即可通过查询会话哈希表获取对应的会话信息,从而识别用户身份,并进一步获取相关数据(例如根据用户名查询数据库)。这样,就通过 Session 与 Cookie 的配合,在无状态的 HTTP 协议基础上实现了用户状态的保持,避免了重复登录的问题。


在响应头之后是一个空行。这里的“空行”实质上就是一个回车换行符(CRLF,即 )。由于每个响应头末尾已经带有一个 ,而响应头结束后紧跟的空行又是一个 ,因此实际上会用两个连续的 来标记响应头的结束。空行之后便是响应正文。

至此,我们已经梳理了 HTTP 请求与响应报文的完整结构。

HTTP服务器

基于已有的HTTP理论储备,接下来可以进入实战环节,即实现一个Web服务器。

根据上文的讨论,客户端在地址栏获取用户输入的URL。用户输入的URL本质上是一个字符串,客户端(浏览器)会按照URL的结构对其解析,分割出协议、域名等部分,接着查询本地缓存或向本地DNS服务器发起请求,将域名转换为IP地址,随后开始与服务器通信。

HTTP协议基于TCP传输协议,因此客户端与服务器需先建立连接,完成三次握手后正式通信。客户端构建请求报文并发送给服务器,服务器接收后处理请求,构造响应报文并返回给客户端,这就是通信的核心流程。

在这一流程中,服务器的实现框架与我们之前编写的服务端程序大体一致,主要区别在于业务处理逻辑,即通信环节的具体实现。整体框架遵循固定模式:创建监听套接字,绑定IP地址与端口号,进入监听状态等,即依次调用socketbind 等系统接口。

本次使用C++编写Web服务器,借助C++的面向对象特性,将服务器抽象为一个对象,用Httpserver 类进行描述。将上述固定流程——即系统接口的调用——封装到Httpserver 类的成员函数中,以使逻辑更清晰。

此外,我们做了进一步封装:不在Httpserver类的成员函数中直接调用系统接口,而是将这些接口封装到一个专门的sock 类中。该类维护一个文件描述符,并提供socketbind 等方法。这些方法内部执行固定逻辑:调用对应接口、检查返回值,若出错则记录错误日志(此处已引入日志模块)。同时,该类采用RAII思想,将套接字的生命周期交由sock 类管理,其析构函数会尝试调用close 接口释放套接字资源。

#include
#include
#include
#include
#include
#include
#include"log.hpp"
extern log lg;
enum
{
    Socket_Error = 1,
    Bind_Error,
    Listen_Error,
    Accept_Error,
    Connect_Error,
    Usage_Error,
};

class sock
{
public:
    sock()
        :socketfd(-1)
    {

}
~sock()
{
    if (socketfd >= 0)
    {
        ::close(socketfd);
    }
}
void socket()
{
    socketfd = ::socket(AF_INET, SOCK_STREAM, 0);
    if (socketfd < 0)

 {   
            lg.logmessage(Fatal, "socket error");
            socketfd = -1;
            exit(Socket_Error);
        }
       int opt = 1;
    setsockopt(socketfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));
        lg.logmessage(info, "socket successfully");
    }

void bind(std::string ip, uint16_t port)
{
    if (socketfd < 0)
    {
        lg.logmessage(Fatal, "socket not created");
        exit(Socket_Error);
    }

struct sockaddr_in server;
memset(&server, 0, sizeof(server));

server.sin_family = AF_INET;
server.sin_port = htons(port);

if (ip == "0.0.0.0")
{
    server.sin_addr.s_addr = INADDR_ANY;
}
else if (inet_pton(AF_INET, ip.c_str(), &server.sin_addr) <= 0)
{

lg.logmessage(Fatal, "inet_pton fail");
            ::close(socketfd);
            socketfd = -1;
            exit(Bind_Error);
        }   
        socklen_t serverlen = sizeof(server);
        int n = ::bind(socketfd, (struct sockaddr*)&server, serverlen);
        if (n < 0)
        {
            lg.logmessage(Fatal, "bind error");
            ::close(socketfd);
            socketfd = -1;
            exit(Bind_Error);
            

}
lg.logmessage(info, "bind successfully");

}   
void listen()
{
    if (socketfd < 0)
    {
        lg.logmessage(Fatal, "socket not created");
        exit(Socket_Error);
    }
    int n = ::listen(socketfd, 5);
    if (n < 0)
    {
        lg.logmessage(Fatal, "listen error");
        ::close(socketfd);
        socketfd = -1;
        exit(Listen_Error);
    }
    lg.logmessage(info, "listen successfully");
}
int accept(struct sockaddr_in* client, socklen_t* clientlen)
{

if (socketfd < 0)
{   
    lg.logmessage(Fatal, "socket not created");
    exit(Socket_Error);
}
int client_fd = ::accept(socketfd, (struct sockaddr*)client, clientlen);
if (client_fd < 0)
{   
    lg.logmessage(Fatal, "accept error");
    return -1;
}
lg.logmessage(info, "accept successfully");
return client_fd;

}
void connect(struct sockaddr_in* server, socklen_t serverlen)
{
    if (socketfd < 0)
    {
        lg.logmessage(Fatal, "socket not created");
        exit(Socket_Error);
    }
    int n = ::connect(socketfd, (struct sockaddr*)server, serverlen);
    if (n < 0)
    {
        lg.logmessage(Fatal, "connect error");
        ::close(socketfd);
        socketfd = -1;
        exit(Connect_Error);
    }
    lg.logmessage(info, "connect successfully");
}
void close()

  {
        if (socketfd >= 0)
        {
            ::close(socketfd);
            socketfd = -1;
        }
    }
    sock(const sock&) = delete;
    sock& operator=(const sock&) = delete;
private:
    int socketfd;
};

Httpserver 类而言,无需在其成员函数中重复实现上述逻辑,只需在类中维护一个sock 对象,并直接调用该对象提供的方法即可。这样做的好处是,将sock 类独立置于Socket.hpp 文件中,未来编写其他服务器代码时可直接引入该头文件。由于服务器的整体框架大致相同,都会依次调用这些接口,因此只需维护一个通用的sock 类。

Httpserver 类的成员变量包括:一个sock 对象、服务器要绑定的IP地址(string 类型)、端口号(整型),以及一个布尔类型的监听标志。其构造函数接收IP地址和端口号两个参数。sock 对象作为自定义类型,会调用默认构造函数进行初始化,将其内部的套接字文件描述符置为无效值-1。IP地址通常设置为"0.0.0.0" ,以接收所有网络接口的数据包;HTTP服务默认在80端口监听,因此我们为这两个参数提供了缺省值,并将布尔类型的监听标志初始化为false

std::string _default = "0.0.0.0";
extern log lg;
class Httpserver
{
public:
        Httpserver(std::string _ip = _default, uint16_t _port=80)
                :ip(_ip)
                , port(_port)
                ,islistening(false)
        {

}
//...

private:
    uint16_t port;
    std::string ip;
    sock listen_socket;
    bool islistening;

 };

接下来是init 函数,其功能是创建监听套接字并将其绑定到指定IP地址和端口,即依次调用sock 类提供的socketbind 接口。

然后是start 函数。该函数首先检查监听标志是否为true :若是,说明监听套接字已处于监听状态,即之前已调用过start 函数,此时记录日志并直接返回;否则,调用sock 类的listen 接口,将监听套接字从CLOSE状态转为LISTEN状态。

成功设置为监听状态后,将监听标志设为true ,随后进入循环。循环内的逻辑是:调用sock 类的accept 接口接收新连接,然后进入通信环节。需要注意的是,服务端会持续收到完成TCP三次握手的新连接,如果服务端在接收一个新连接后立即进入通信环节,而不继续接收其他已就绪的连接,会导致监听套接字的全连接队列迅速填满,进而使客户端连接建立失败。此外,接收新连接与处理通信是两个相互独立、可并行执行的动作,也就是说,接收新连接无需等待当前通信过程结束即可进行,因此需要对二者进行解耦。

基于以上两点,我们考虑创建线程。线程本质上是一个用户态函数,其执行上下文即为通信处理逻辑。本Web服务器基于HTTP/1.0实现,默认使用短连接,即服务端在接收请求、发送响应后立即关闭连接,因此单次通信持续时间较短。为避免频繁创建和销毁线程带来的开销,这里引入线程池。

线程池维护一个任务缓冲区与一组消费者线程。我们定义一个Task 任务对象,其中包含一个run 方法,该方法封装了通信处理逻辑。因此,Task 对象内部需保存一个成员变量:已连接套接字对应的文件描述符。服务器作为主线程,在accept接收到新连接后,会构造一个Task 对象并将其放入线程池的缓冲区。消费者线程从缓冲区获取任务对象,并调用其run 方法执行通信处理。由于线程池采用单例模式,在进入循环前,需先通过threadpool 类的静态成员函数获取单例对象,并调用其start 函数初始化并创建一批工作线程,随后进入循环。

accept 拿到 client_fd -> 封装成 Task -> 扔进队列 -> 消费者线程竞争获取任务。
class httpserver{ 
void start()
        {
                listen_socket.listen();
                if (islistening)
                {
            lg.logmessage(warning,"server is already listening");
                        return;
                }
                islistening = true;
                threadpool& tp = threadpool::getinstance();
                tp.start();
 struct sockaddr_in client;
                socklen_t client_len = sizeof(client);
                memset(&client, 0, client_len);
                while (islistening)
                {
                        size_t client_fd=listen_socket.accept(&client,&client_len);
                        Task t(client_fd);
                        tp.push(t);
                }
        }
        //...
    }

接下来将聚焦于通信环节。我们知道,HTTP通信的大致流程是:服务端接收客户端发送的请求报文,处理该请求报文,构建响应报文,然后将其返回给客户端。

由于 HTTP 协议基于 TCP 协议,因此整个通信的第一个环节便是读取客户端发来的请求报文。TCP 协议是面向字节流的,这意味着读取报文时以字节为单位,可能导致一次读取仅得到报文的一部分,或超过一个完整报文。而客户端期望服务端能读取完整的 HTTP 请求,因此我们需要依据 HTTP 请求报文的格式进行解析。

HTTP 请求报文由四个部分组成。其中,请求头结束后会有一个空行,而请求头最后一行末尾带有回车换行符(CRLF),与空行共同构成连续的两个 CRLF(即 )。这为我们判断是否读取到完整的请求行与请求头提供了依据。

下面我们将读取完整 TCP 报文段的功能封装为Get_HttpRequest函数模块。该函数返回类型为bool:读取成功返回true ,失败返回false

我们首先准备一个大小为 1024 字节的字符数组作为输入缓冲区,调用recv 接口读取请求报文并存入缓冲区。接着检查recv 的返回值:若小于 0,表示读取失败,记录错误日志并返回false

接下来需要判断是否已读取完整的请求行与请求头。由于 HTTP 请求报文是文本内容,即字符序列,我们需进行字符串解析。为此,将输入缓冲区(字符数组)转换为std::string 对象,以便利用其提供的字符串操作函数。我们调用find 函数查找连续的两个 CRLF( )。
find 函数返回ssize_t 类型的值:若找到,则返回子串首个字符的索引(非负);若未找到,则返回std::string::npos 。因此,若返回值不等于npos ,说明已读取完整的请求行与请求头;否则,需继续读取。

我们使用一个循环持续读取数据。在循环前定义输入缓冲区及std::string 对象data 。每次读取一定字节的数据存入缓冲区,再将其拼接到data 中。注意:拼接时应使用append 函数而非+= 运算符,因为缓冲区中的字符序列不一定以 结尾,使用+= 可能导致越界风险。append 可指定长度,确保拼接安全。

std::string data;
char buffer[BUFFER_SIZE];
while (true) {
    ssize_t read_bytes = recv(socketfd, buffer, BUFFER_SIZE - 1, 0);
    if (read_bytes <= 0) {
        lg.logmessage(Fatal, "recv error");
        return false;
    }
    data.append(buffer, read_bytes);
    if (data.find("

") != std::string::npos) {
        break;
    }
}

在循环中,每次读取后检查data 中是否包含" " 。若存在,则跳出循环,表明已获得完整的请求行与请求头。需要注意的是,此时data 中可能不仅包含请求行与请求头,若为 POST 请求,还可能包含部分请求正文。因此,我们需要从中分离出纯净的请求行与请求头部分。

使用substr 函数进行分割:起始索引为 0,长度为find(" ") 返回值加 4(包含空行),结果存入head 对象。

std::string head = data.substr(0, data.find("

") + 4);

接下来,检查是否存在请求正文。对于 POST 请求,其请求头中包含Content-Length 字段,用于描述正文长度。我们在head 中查找子串"Content-Length:" ,若存在,则表明有请求正文;否则,可能为 GET 请求,无正文部分。

若找到"Content-Length:" ,则进一步提取其属性值。请求头由键值对组成,每对以 CRLF 结尾。我们可从"Content-Length:" 之后查找最近的 CRLF,再使用substr 分割出表示长度的子串。由于键与值之间可能存在空格,而std::stoi 函数会自动忽略前导空格与零,因此可直接转换为整数content_length

size_t pos = data.find("Content-Length:");
if (pos != std::string::npos) {
    ssize_t endpos = head.find("
", pos);
    std::string content_length_str = head.substr(pos + 15, endpos - pos - 15);
    size_t content_length = std::stoi(content_length_str);
    // ...
}

在读取请求行与请求头时,可能已读取部分请求正文。我们计算已读取的正文长度remaining=data.size()-head.size() ,与完整的正文长度content_length 比较:

  • remaining>=content_length ,说明已读取完整正文,可直接从data 中分割出正文。
  • remaining ,则需继续读取剩余正文。先将已读部分存入body ,然后计算仍需读取的字节数to_read 。循环调用recv 读取剩余数据,每次最多读取BUFFER_SIZE-1 字节,并拼接到body ,直到to_read 为 0。
size_t remaining = data.size() - head.size();
if (remaining < content_length) {
    std::string body = data.substr(data.find("

") + 4, remaining);
    int to_read = content_length - remaining;
    char body_buffer[BUFFER_SIZE];
    while (to_read > 0) {
        ssize_t read_bytes = recv(socketfd, body_buffer, 
                                   std::min(BUFFER_SIZE - 1, to_read), 0);
        if (read_bytes <= 0) {
            lg.logmessage(Fatal, "recv error");
            return false;
        }
        body.append(body_buffer, read_bytes);
        to_read -= read_bytes;
    }
    hr.text = body;
} else {
    hr.text = data.substr(data.find("

") + 4, content_length);
}

至此,我们已获取完整的请求行、请求头及请求正文。接下来进行反序列化,将这些字节流转换为结构化的对象,便于后续处理。定义http_request 结构体,其成员包括:

  • std::unordered_map header :存储请求头的键值对。
  • std::string methodurlhttp_version :存储请求行解析出的方法、URL 和协议版本。
  • std::string text :存储请求正文。
class Http_Request
{
public:
//...    
    public:
        std::vector<std::string> header;
        std::string text;
        std::string method;
        std::string url;
        std::string http_version;
};

该结构体提供反序列化成员函数Deserialization ,其功能为解析head 字符串,提取请求行各部分并填充至成员变量,同时将请求头键值对存入哈希表。函数返回bool 类型,表示解析成功与否。

最终,Get_httpRequest 函数在成功读取并解析请求后,调用hr.Deserialization(head) 进行反序列化,并返回结果。

bool Get_HttpRequest(size_t socketfd, Http_Request& hr) {
    std::string data;
    char buffer[BUFFER_SIZE];
    while (true) {
        ssize_t read_bytes = recv(socketfd, buffer, BUFFER_SIZE - 1, 0);
        if (read_bytes <= 0) {
            lg.logmessage(Fatal, "recv error");
            return false;
        }
        data.append(buffer, read_bytes);
        if (data.find("

") != std::string::npos) {
            break;
        }
    }
    std::string head = data.substr(0, data.find("

") + 4);
    size_t pos = data.find("Content-Length:");
    if (pos != std::string::npos) {
        ssize_t endpos = head.find("
", pos);
        std::string content_length_str = head.substr(pos + 15, endpos - pos - 15);
        size_t content_length = std::stoi(content_length_str);
        size_t remaining = data.size() - head.size();
        if (remaining < content_length) {
            std::string body = data.substr(data.find("

") + 4, remaining);
            int to_read = content_length - remaining;
            char body_buffer[BUFFER_SIZE];
            while (to_read > 0) {
                ssize_t read_bytes = recv(socketfd, body_buffer,
                                           std::min(BUFFER_SIZE - 1, to_read), 0);
                if (read_bytes <= 0) {
                    lg.logmessage(Fatal, "recv error");
                    return false;
                }
                body.append(body_buffer, read_bytes);
                to_read -= read_bytes;
            }
            hr.text = body;
        } else {
            hr.text = data.substr(data.find("

") + 4, content_length);
        }
    }
    bool res = hr.Deserialization(head);
    hr.debugprint();
    return res;
}

调用Get_httpRequest 时,需预先定义http_request 对象作为输出型参数,连同已连接套接字的文件描述符一并传入。

接下来是关于http_request 类的Deserialization 函数。该函数的功能是提取请求行和请求头的键值对,并将键值对保存到名为header的哈希表中。由于请求行与请求头之间由回车换行符(CRLF)分隔,首先需要分割出请求行及各请求头字段。具体实现思路是定义一个整型变量start,用于记录分割的起始位置;同时定义一个整型变量end,用于定位回车换行符。

分割逻辑被置于一个循环中。进入循环之前,会定义一个名为_headerstd::vector 数组,该数组用于临时保存分割后的请求行及每个请求头字段(键值对组成的整行字符串),start初始值设为0,随后调用find函数查找子串" " ,该函数接收两个参数:待查找的子串及起始查找位置。若返回std::string::npos ,表示未找到回车换行符,说明客户端构建的请求报文有误,此时返回false。若查找成功,则通过substr函数分割出子串(不包含换行符),并将其插入_header 数组。接着更新start和end的位置,将start移动到所找到的子串" " 之后,即start = end + 2,然后重复上述过程。

需注意的是,请求头部分末尾包含一个空行。当start移动到最后一个回车换行符之后的字符,即空行" " 的起始位置时,再次调用find函数将返回该位置。此时通过substr分割出的子串为空,表明所有请求行和请求头键值对已分割并保存至数组,循环结束。

接下来,我们需要将分割出的请求头键值对插入到哈希表header 中。由于数组_header 的第一个元素是请求行,因此从第二个元素开始遍历。每一行的键和值之间由冒号分隔,首先调用find 函数查找冒号的位置,若未找到(返回npos),则说明客户端构建的请求报文格式错误,函数返回false 。接着,通过substr 分割出冒号前的部分作为键(key)。

冒号之后可能存在一个或多个空格,之后才是值的起始位置。由于某些客户端构建的请求报文可能不规范,空格数量不定,我们通过一个循环跳过所有空白字符,定位到值的起始索引。随后,再次调用substr 分割出剩余部分作为值(value)。最后,将键值对插入到哈希表header 中。

for (size_t i = 1; i < _header.size(); i++)
{
    std::string line = _header[i];
    size_t pos = line.find(":");
    if (pos == std::string::npos)
    {
        return false;
    }
    std::string key = line.substr(0, pos);
    size_t val_start = pos + 1;
    // 跳过冒号后的空白字符,issspace接收一个字符,如果当前字符为空格等空白字符,返回为true
    while (val_start < line.size() && std::isspace(line[val_start]))
    {
        val_start++;
    }
    std::string value = line.substr(val_start);
    headers[key] = value;
}

最后的任务是解析已保存至数组的请求行,即数组的第一个元素。请注意,分割时已去除末尾的回车换行符。请求行由三个部分组成,每部分以空格分隔,依次是请求方法、URL和协议版本。

对于请求行的解析,常见有两种实现方式。第一种做法是定义两个变量,分别记录分割的起始位置和结束位置(即空格所在位置)。具体步骤是:首先调用find 函数定位第一个空格,通过substr 分割出请求方法;然后将起始位置移动到该空格之后,再次调用find 函数查找下一个空格,分割出URL;最后将起始位置移动到第二个空格之后,直接分割剩余部分作为协议版本。

std::string first_line = header[0];
size_t space1 = first_line.find(" ");
if (space1 == std::string::npos)
{
    return false;
}
method = first_line.substr(0, space1);
size_t space2 = first_line.find(" ", space1 + 1);
if (space2 == std::string::npos)
{
    return false;
}
url = first_line.substr(space1 + 1, space2 - space1 - 1);
http_version = first_line.substr(space2 + 1);

但需注意,某些客户端构建的请求报文格式可能不规范,例如各部分之间的空格数量可能不止一个,这会导致解析失败。因此,更推荐的做法是使用stringstream

在C++中,字符串被抽象为流对象。stringstream 内部通过缓冲区存储字符串,并重载了<<>> 运算符。stringstream 对象内部维护两个独立的指针:读指针和写指针,两者默认均指向缓冲区起始位置。调用>> 运算符时,会从读指针当前位置开始解析:若读指针指向分隔符(如空格),则跳过该分隔符,直至遇到非分隔符字符;然后持续读取,直到再次遇到分隔符,将这段子串提取到>> 操作符右侧的string 对象中。若读指针到达缓冲区末尾,会设置EOF标志,后续提取操作将失败。

stringstream要跳过的分割符:

字符 (Character)转义序列 (Escape)ASCII 值 (Hex/Dec)描述 (Description)
空格' '0x20 / 32最常见的单词分隔符
换行符' '0x0A / 10Line Feed,Unix/Linux 系统下的换行
回车符' '0x0D / 13Carriage Return,Windows 换行符( )的一部分
水平制表符' '0x09 / 9Tab 键,通常对应 4 或 8 个空格
垂直制表符' '0x0B / 11Vertical Tab,现代编程中较少使用
换页符' '0x0C / 12Form Feed,常用于控制打印机换页

<< 运算符用于向缓冲区写入字符串,从写指针当前位置开始拼接。需要注意,应避免在同一stringstream 对象上混用读、写操作,因为写入操作会从写指针位置开始覆盖缓冲区内容。若流对象已初始化(缓冲区中已有数据),写入新字符串会覆盖原有数据。

#include 
#include 
int main()
{
    std::stringstream ss("hello world wz
 ");
    std::string a, b, c, d;
    ss >> a >> b >> c;
    std::cout << a << std::endl;
    std::cout << b << std::endl;
    std::cout << c << std::endl;
    ss << "wz";
    std::cout << "Buffer now: " << ss.str() << std::endl; 
    return 0;
}

stringstream 提供了str() 成员函数:无参版本返回临时字符串对象;有参版本会清空缓冲区并重新设置内容,同时将读写指针重置到开头,但不会清除EOF标志。如需清除EOF标志,需额外调用clear() 函数。

  bool Deserialization(std::string& head)
        {
        size_t start=0;
        std::vector<std::string>_header;
                while(true)
        {   
            std::string line;
            size_t end=head.find("
",start);
            if(end==std::string::npos)
            {   
                return false;
            }
            line=head.substr(start,end-start);
            if(line.empty())
            {   
                break;
            }
            start=end+2;
            _header.push_back(line);
        }
        if(_header.size()<1)
        {
            return false;
        }
        for(size_t i=1;i<_header.size();i++)
        {
            std::string line=_header[i];
            ssize_t pos=line.find(":");
            if(pos==std::string::npos)
            {
                return false;
            }
            std::string key=line.substr(0,pos);
            size_t val_start=pos+1;
            while(val_start<line.size() && std::isspace(line[val_start]))
            {
                val_start++;
            }
            std::string value=line.substr(val_start);
            headers[key]=value;
        }
        std::string first_line=_header[0];
        std::stringstream ss(first_line);
        ss>>method>>url>>http_version;
        return true;
        }

为便于调试,在http_request类中还定义了一个debugprint函数。该函数用于打印请求报文,由于请求报文为文本格式,可直接阅读。请求行、请求头及请求正文均已保存至对应变量中,因此只需按HTTP报文的格式打印各部分即可。

 void debugprint()
    {
         std::cout<<std::endl;
        std::cout<<method<<" "<<url<<" "<<http_version<<std::endl;
        for(auto it=headers.begin();it!=headers.end();it++)
        {
            std::cout<<it->first<<": "<<it->second<<std::endl;
        }
        std::cout<<std::endl;
        std::cout<<text<<std::endl;
        std::cout<<std::endl;
    }

因此,在http_Get_HttpRequest函数中调用反序列化函数后,特意调用了debugprint函数,以输出客户端发送的请求报文内容,辅助验证解析结果。


成功获取完整TCP报文段并反序列化后,各字段将解析并存储到一个http_request 对象中,该过程通过调用Get_HttpRequest 函数实现。若函数返回false ,则表示获取完整TCP报文失败或反序列化过程出现错误,此时记录日志并退出处理流程;若返回true ,则进入请求报文处理阶段。

在反序列化过程中,请求行的各个参数已被解析并存入http_request 对象的对应字段,后续逻辑进入业务分发阶段。系统依据method 字段判断请求类型:若为GET方法,则优先尝试静态资源映射;若为POST方法,则触发动态业务逻辑处理。通过这种基于HTTP方法的分流机制,确保不同类型的请求能够被路由至相应的处理模块。

为此,这里定义了两个处理函数模块:Http_Get_HandlerHttp_Post_Handler 。根据http_request 对象中的method 字段,调用对应的处理函数。这两个函数的返回值为srting 类型,即构造好的响应报文。获取响应报文后,调用send 接口将其发送回客户端,随后关闭套接字并正常结束当前连接处理流程。

void run()
{
    Http_Request hr;
    bool get_result = Get_HttpRequest(socketfd, hr);
    if (get_result == false)
    {
        lg.logmessage(Fatal, "get http request error");
        close(socketfd);
        return;
    }
    

std::string res;
if (hr.method == "GET")
{
    res = Http_Get_Handler(hr);
}
else if (hr.method == "POST")
{
    res = Http_Post_Handler(hr);
}
else
{
    lg.logmessage(warning, "unsupported method: %s", hr.method.c_str());
    close(socketfd);
    return;
}

int send_bytes = send(socketfd, res.c_str(), res.size(), 0);
if (send_bytes < 0)
{
    lg.logmessage(Fatal, "send error");
    close(socketfd);
    return;
}

close(socketfd);

}

由于浏览器与服务端通信的场景中,绝大多数请求为GET或POST方法,若接收到其它类型的HTTP方法请求,此处将直接记录错误日志,随后关闭套接字并退出该连接的处理流程。


首先介绍的是Http_Get_Handler 处理模块。GET请求通常用于从服务器获取静态资源,即文件。在此上下文中,URL对应于服务器文件系统下的一个相对路径。我们以服务器所在目录作为根目录,将URL映射为该目录下的相对路径。具体而言,映射操作通过字符串拼接实现。为此,我定义了一个全局字符串变量path ,其值为./wwwroot 。通过将URL与path 拼接,我们可以在服务器目录下创建名为wwwroot 的文件夹,用于存放各类静态资源,包括HTML文本文件及图片等二进制文件。

需注意,观察输入的URL,某些情况下路径可能仅为根目录"/" ,而非具体路径。URL本应用于定位特定静态资源,但与"./wwwroot" 拼接后,其含义变为wwwroot目录下的所有内容。显然,我们无法将整个目录内容返回给客户端。因此,当URL为根目录时,实际表示获取站点首页。

一个站点由众多资源构成。访问站点时,首先获取的是首页资源,其中包含链接以跳转至其他页面。首页本质上是一个网页,服务器已在特定路径下保存对应的HTML文档。按照惯例,首页HTML文档的文件名通常为"index" ,并存放于wwwroot目录下。客户端访问首页时,既可显式指定路径(如"/index.html" ),也可省略URL,浏览器会默认访问首页并自动将URL设为"/"。例如,在地址栏输入www.baidu.comwww.baidu.com/index.html 效果相同。若未显式指定协议或端口,浏览器默认采用HTTPS协议及443端口。

在先前反序列化过程中,我们已提取请求行中的各个参数。因此,此处首先需判断URL是否为根目录或首页文件。若是,则直接在path 后拼接字符串"/index.html" 。由于响应报文将携带响应正文,必须在响应头中设置Content-Type 字段,以告知客户端响应正文的数据类型,确保正确解析。每种文件类型均有对应的MIME类型映射,HTML的MIME类型为"text/html"。

std::string Http_Get_Handler(Http_Request& hr)
{
    std::string file_path;
    std::string content_type;
    std::string res;
    if (hr.url == "/" || hr.url == "/index.html")
    {
        file_path = path + "/index.html";
        content_type = "text/html";
    }
    // ...
}

若非上述情况,则表明URL是一个具体路径。此时,我们直接将URL拼接到path对象上。由于该路径用于定位特定文件,其终点应为一个具体文件名,格式通常为"文件名.扩展名",其中扩展名指示文件类型。为正确设置
Content-Type 字段,需获取文件扩展名。此处调用rfind 函数从后向前查找"."字符,若找到则返回其索引;若未找到(即返回npos ),则说明该路径可能为虚拟路径,用于路由至预定义函数。在本Web服务器的设计中,GET请求仅处理获取静态资源的情况。尽管GET请求也可用于提交表单(此时URL可能携带查询参数),但再本web服务器中暂不处理这种情况。

rfind 返回npos,在此场景下,表示客户端请求的URL为虚拟路径,服务器将返回错误页面,指示请求的资源不存在。因此,将Content-Type 设置为text/html

若找到"." 符,则使用substr 函数分割出子串,即文件扩展名。我定义了一个专用函数,用于将文件扩展名映射为对应的MIME类型。在Task 类中,维护了一个静态哈希表,键和值均为字符串类型,键为文件扩展名,值为MIME类型。由于Http_Get_Handler 为类外定义的函数,为访问类内成员变量,需在Task 类中定义静态成员函数suffix_handler 。静态成员函数仅能访问静态成员变量,因此该哈希表也需声明为静态成员变量,并在类外进行初始化,插入文件扩展名与MIME类型的键值对。

std::string Http_Get_Handler(Http_Request& hr)
{
    std::string file_path;
    std::string content_type;
    std::string res;
    if (hr.url == "/" || hr.url == "/index.html")
    {
        file_path = path + "/index.html";
        content_type = "text/html";
    }
    else
    {
        file_path = path + hr.url;
        size_t pos = file_path.rfind(".");
        if (pos == std::string::npos)
        {
            content_type = "text/html";
        }
        else
        {
            std::string suffix = file_path.substr(pos);
            content_type = Task::suffix_handler(suffix);
        }
    }
    // ...
}

Task 类的suffix_handler 函数负责查询哈希表:若存在对应扩展名的键,则返回其MIME类型;否则返回HTML类型对应的值(通常用于返回错误页面)。该函数返回字符串类型,Http_Get_Handler 调用此函数获取MIME类型并设置Content-Type 。对于具体路径,接下来需定位静态资源并读取其内容,因此我实现了read_file 函数。

read_file 函数接收文件路径,根据路径定位文件。文件读取涉及文件操作,此处使用C++的ifstream 流对象。其构造函数接受一个字符串参数,表示相对或绝对路径。若为相对路径,则基于当前工作目录进行解析,并打开文件将内容加载至内存。构造函数可接受两个参数:第一个为路径,第二个为打开模式,默认为文本模式。但此处统一以二进制模式打开,即使对于文本文件也是如此。二进制模式会忽略特殊字符(如换行符)的处理,确保完整读取所有数据;而文本模式下使用getline 等函数可能忽略某些控制字符。

为获取文件大小,需配合使用tellgseekg 函数。seekg 用于设置读指针位置,接受两个参数:偏移量和相对位置。标准库提供了三种相对位置:std::ios::beg (文件开头)、std::ios::end (文件结尾)和std::ios::cur (当前位置)。tellg 不接受任何参数,其返回当前读指针距文件开头的偏移量

首先调用seekg 函数将文件流对象的读指针设置到文件开头。随后调用tellg 函数(该函数不接受参数),返回当前读指针的位置。由于此时读指针已位于文件开头,返回的位置即为起始位置。接着再次调用seekg 将读指针设置到文件结尾,然后通过tellg 获取当前读指针位置,即文件结束位置。

需要注意的是,tellg 的返回值类型为std::streampos 。这是一个类类型,封装了文件状态和偏移量信息,但在实际使用中可将其视为类似size_t 的偏移量表示。计算文件大小时,需要从结束位置减去起始位置,这涉及到算术运算。std::streampos 重载了减法运算符,其返回类型为std::streamoff ,这是一个有符号整数类型,在标准库的实现中,std::streamoff 通常定义为long long 类型别名。

istream& read (char* s, streamsize n);

获取文件大小后,调用ifstreamread 函数,该函数接收一个char 类型缓冲区及文件大小参数,以二进制方式读取文件内容至缓冲区。此处,我使用string 对象content 作为缓冲区,在读取前通过resize 预分配足够空间,然后传递给read 函数。最后调用close 关闭文件并返回content

std::string read_file(std::string file_path)
{
    std::ifstream file(file_path, std::ios::binary);
    if (!file.is_open())
    {
        lg.logmessage(info, "file not found: %s", file_path.c_str());
        return "";
    }
    file.seekg(0, std::ios::beg);
    std::streampos start = file.tellg();
    file.seekg(0, std::ios::end);
    std::streampos end = file.tellg();
    std::streamoff file_size = end - start;
    std::string content;
    content.resize(static_cast<size_t>(file_size));
    file.seekg(0, std::ios::beg);
    file.read(&content[0], file_size);
    file.close();
    return content;
}

获取到文件内容后,接下来是构建响应报文。此时可能有读者会想:之前对于请求报文,我们会进行反序列化,用Http_request 对象来描述并保存解析得到的各个字段;那么对于响应报文,我们是否也需要定义一个类似的Http_reponse 对象来存储其各个字段呢?

这里我先说明:可以,但没必要。定义Http_request 对象的主要目的是为了更快速、更方便地访问请求报文的各个字段,比如请求行中的请求方法以及请求头的相关字段等。而对于响应报文,在当前服务端的上下文中,我们并没有访问其字段的需求——服务端只负责生成响应,并不需要解析或使用它。相反,客户端的行为与服务端是对称的,客户端可能需要解析响应报文,因此客户端才有必要定义一个Http_reponse 对象来存储响应报文的各个字段。由于服务端仅生成响应而不处理响应,所以在这里定义Http_reponse 对象并无必要。

因此,接下来我们直接构建响应报文即可。首先检查read_file 函数的返回值,即返回的string 对象是否为空。如果为空,说明read_file 打开文件失败,意味着路径无效或资源不存在。此时,响应报文的状态码应为 404,状态描述为“Not Found”。在资源未找到的情况下,我们应当向用户返回一个错误页面来提供提示。

为此,我们应在wwwroot 目录下准备一个对应的错误页面 HTML 文件。这里我在该目录下创建了一个404.html 文件。在拼接出该文件的完整路径后,再次调用read_file 读取其内容,并将其作为响应正文。获取 404 页面的内容后,依次构建响应行与响应头。响应头中必须包含描述响应正文的字段:Content-Type 用于告知客户端正文的 MIME 类型,确保正确解析;Content-Length 用于指示消息边界。此外,由于当前使用 HTTP/1.0 并采用短连接,还需添加Connection 字段,并将其值设为close

std::string body = read_file(file_path);
std::string header_line;
std::string header;
if (body.empty())
{
    header_line = "HTTP/1.0 404 Not Found
";
    header += "Connection: close
";
    std::string content = read_file(path + "/404.html");
    header += "Content-Length: " + std::to_string(content.size()) + "
";
    header += "Content-Type: text/html
";
    header += "
";
    res = header_line + header + content;
}

string 对象不为空,说明成功获取到资源,此时构建的响应报文状态码为 200,状态描述为“OK”。响应头字段与之前相同,包含 Content_LengthContent_TypeConnection ,最后再拼接响应正文。完成响应报文的构建后,将最终得到的string 对象返回。

std::string Http_Get_Handler(Http_Request& hr)
{
    std::string file_path;
    std::string content_type;
    std::string res;
    if (hr.url == "/" || hr.url == "/index.html")
    {
        file_path = path + "/index.html";
        content_type = "text/html";
    }
    else
    {
        file_path = path + hr.url;
        ssize_t pos = file_path.rfind(".");
        if (pos == std::string::npos)
        {
            content_type = "text/html";
        }
        else
        {
            std::string suffix = file_path.substr(pos);
            content_type = Task::suffix_handler(suffix);
        }
    }
    std::string body = read_file(file_path);
    std::string header_line;
    std::string header;
    if (body.empty())
    {
        header_line = "HTTP/1.0 404 Not Found
";
        header += "Connection: close
";
        std::string content = read_file(path + "/404.html");
        header += "Content-Length: " + std::to_string(content.size()) + "
";
        header += "Content-Type: text/html
";
        header += "
";
        res = header_line + header + content;
    }
    else
    {
        header_line = "HTTP/1.0 200 OK
";
        header += "Content-Length: " + std::to_string(body.size()) + "
";
        header += "Connection: close
";
        header += "Content-Type: " + content_type + "
";
        header += "
";
        res = header_line + header + body;
    }
    return res;
}

上文介绍了Http_Get_handler ,接下来介绍Http_Post_handler 。我们知道 POST 请求会携带请求正文,并且其请求行中的 URL 是一个虚拟路径,用于映射到服务器预定义的函数,进而调用该函数处理参数并返回结果。因此,在设计时,我们将 Web 服务器提供的服务设定为一个计算器。站点首页会提供输入框,用于接收两个操作数和一个操作符,并通过表单上传至服务器。表单内容存放在请求正文中,以键值对格式存储。

该函数的逻辑是:首先解析请求正文。由于我们知道正文格式是以 “&” 分隔的键值对,且每个键值对的顺序是不确定的(第一个不一定是操作符,也可能是操作数),但每个键的名称与 HTML 文档中对应输入项的name 属性一致:

<form action="/calc" method="POST">
    <input type="number" name="a" placeholder="输入数字 a" required>
    <br>
    <select name="op">
        <option value="+">+option>
        <option value="-">-option>
        <option value="*">*option>
        <option value="/">/option>
    select>
    <br>
    <input type="number" name="b" placeholder="输入数字 b" required>
    <br><br>
    <button type="submit">开始计算button>
form>

表单提交的目标虚拟路径是/calc 。客户端识别该路径后,即可确定调用对应的处理函数。为此,我们需要准备一个键和值均为string 类型的哈希表,其中键为每个表单数据的名称。

接下来解析请求正文:先调用find 函数查找第一个 "&" 以分割键值对。如果find 返回npos ,说明客户端构建的请求报文有误,此时应返回状态码 400(Bad Request)的响应报文。我们专门定义了process_bad_request 函数来生成此类响应,其返回类型为string

std::string process_bad_request()
{
    lg.logmessage(Fatal, "bad request body");
    std::string headler_line = "HTTP/1.0 400 Bad Request
";
    std::string header = "Connection: close
";
    std::string content = "Bad Request";
    header += "Content-Length: " + std::to_string(content.size()) + "
";
    header += "Content-Type: text/plain
";
    header += "
";
    return headler_line + header + content;
}

若未返回npos ,则调用substr 分割出第一个键值对。每个键值对的格式为“键=值”,因此再次调用find 在子串中查找 “=”。若未找到,同样调用process_bad_request 并返回。找到后,继续用substr 分离键和值。若值前存在前导空格也无需担心,因为stoi 会自动忽略它们。将键和值插入哈希表后,重复上述过程直至解析完毕。

std::string Http_Post_Handler(Http_Request& hr) {
    std::string res;
    std::unordered_map<std::string, std::string> val;
    size_t start = 0;
    if (hr.url == "/calc") {
        std::string body = hr.text;
        size_t pos1 = body.find("&");
        if (pos1 == std::string::npos) {
            return process_bad_request();
        }
        std::string expression = body.substr(start, pos1);
        size_t pos2 = expression.find("=");
        if (pos2 == std::string::npos) {
            return process_bad_request();
        }
        std::string result_key_str = expression.substr(start, pos2);
        std::string result_value_str = expression.substr(pos2 + 1);
        val[result_key_str] = result_value_str;
        start = pos1 + 1;
        pos1 = body.find("&", start);
        if (pos1 == std::string::npos) {
            return process_bad_request();
        }
        pos2 = body.find("=", start);
        if (pos2 == std::string::npos || pos2 > pos1) {
            return process_bad_request();
        }
        result_key_str = body.substr(start, pos2 - start);
        result_value_str = body.substr(pos2 + 1, pos1 - pos2 - 1);
        val[result_key_str] = result_value_str;
        start = pos1 + 1;
        pos2 = body.find("=", start);
        if (pos2 == std::string::npos) {
            return process_bad_request();
        }
        result_key_str = body.substr(start, pos2 - start);
        result_value_str = body.substr(pos2 + 1);
        val[result_key_str] = result_value_str;
        int calc_result;
        //...
    }

哈希表初始化完成后,计算逻辑由专门的process_calculation 函数处理,该函数即为虚拟路径映射的目标函数。其内容为:先从哈希表中获取值,调用stoi 将操作数转换为整型。由于键或值可能包含特殊字符(如空格会被编码为"+""/" 等符号也会进行 URL 编码,即转换为% 后跟两个十六进制数字),因此操作符需要先解码为原始字符。解码完成后,进入switch-case 语句执行计算,并将结果存入输出型参数。

bool process_calculation(std::unordered_map<std::string, std::string>& val, int& result)
{
    int a = std::stoi(val["a"]);
    int b = std::stoi(val["b"]);
    std::string op = val["op"];
    if (op == "+" || op == "%2B" || op == "%2b") {
        op = "+";
    } else if (op == "-" || op == "%2D" || op == "%2d") {
        op = "-";
    } else if (op == "*" || op == "%2A" || op == "%2a") {
        op = "*";
    } else if (op == "/" || op == "%2F" || op == "%2f") {
        op = "/";
    } else {
        lg.logmessage(Fatal, "unsupported operator: %s", op.c_str());
        return false;
    }
    switch (op[0]) {
        case '+':
            result = a + b;
            break;
        case '-':
            result = a - b;
            break;
        case '*':
            result = a * b;
            break;
        case '/':
            if (b == 0) {
                lg.logmessage(warning, "division by zero");
                return false;
            }
            result = a / b;
    }
    return true;
}

若计算成功,函数返回true;否则返回false ,并调用process_bad_request 返回错误响应。计算成功后,构建状态码为 200 的响应报文,并将硬编码的成功页面 HTML 文本作为响应正文返回。

由于本 Web 服务器仅提供计算服务,若 URL 不是/calc ,则视为无效路径,同样调用process_cad_request 并返回。

std::string Http_Post_Handler(Http_Request& hr) {
    std::string res;
    std::unordered_map<std::string, std::string> val;
    size_t start = 0;
    if (hr.url == "/calc") {
        std::string body = hr.text;
        size_t pos1 = body.find("&");
        if (pos1 == std::string::npos) {
            return process_bad_request();
        }
        std::string expression = body.substr(start, pos1);
        size_t pos2 = expression.find("=");
        if (pos2 == std::string::npos) {
            return process_bad_request();
        }
        std::string result_key_str = expression.substr(start, pos2);
        std::string result_value_str = expression.substr(pos2 + 1);
        val[result_key_str] = result_value_str;
        start = pos1 + 1;
        pos1 = body.find("&", start);
        if (pos1 == std::string::npos) {
            return process_bad_request();
        }
        pos2 = body.find("=", start);
        if (pos2 == std::string::npos || pos2 > pos1) {
            return process_bad_request();
        }
        result_key_str = body.substr(start, pos2 - start);
        result_value_str = body.substr(pos2 + 1, pos1 - pos2 - 1);
        val[result_key_str] = result_value_str;
        start = pos1 + 1;
        pos2 = body.find("=", start);
        if (pos2 == std::string::npos) {
            return process_bad_request();
        }
        result_key_str = body.substr(start, pos2 - start);
        result_value_str = body.substr(pos2 + 1);
        val[result_key_str] = result_value_str;
        int calc_result;
        if (process_calculation(val, calc_result) == false) {
            return process_bad_request();
        }
        std::string headler_line = "HTTP/1.0 200 OK
";
        std::string header = "Connection: close
";
        header += "Content-Type: text/html
";
        std::string content = "";
        content += "

计算结果展示

"
; content += "

结果为: " + std::to_string(calc_result) + "

"
; content += "返回首页"; content += ""; header += "Content-Length: " + std::to_string(content.size()) + " "; header += " "; res = headler_line + header + content; return res; } else { lg.logmessage(Fatal, "unsupported post url: %s", hr.url.c_str()); return process_bad_request(); } }

至此,两个核心请求处理函数已介绍完毕。接下来只需将这两个函数返回的响应正文通过send
接口发送出去,并检查其返回值。若返回值小于 0,则记录错误日志、关闭套接字并退出;若发送成功,则关闭套接字并正常退出。


httpserver.cpp:

最后一部分是服务器主函数的实现。该服务器运行于Linux环境,通常通过命令行启动进程,配置信息可通过命令行参数传递。由于IP地址已默认设为"0.0.0.0" ,此处仅需指定端口号。命令行本质上是一个字符串,由bash进程接收,解析出命令名和参数,保存到字符串数组中,并传递给目标进程。

因此,主函数首先检查参数个数:包括命令名及其参数,应为2。若非2,则打印错误信息并退出程序。接着从字符串数组中获取端口号参数,转换为整型,随后创建Httpserver 对象,依次调用其initstart 函数。

#include"Httpserver.hpp"
#include"log.hpp"
#include
extern log lg;
void usage(std::string progmaname)
{
        std::cout << "usage wrong: " << progmaname << " " << std::endl;
}
int main(int argc, char* argv[])
{
        if (argc != 2)
        {
                usage(argv[0]);
                exit(-1);
        }
        uint16_t port = std::stoi(argv[1]);
        Httpserver hs(_default,port);
        hs.init();
        hs.start();
        return 0;
}

源码

Httpserver.hpp:

#pragma once
#include"Socket.hpp"
#include"log.hpp"
#include"Threadpool.h"
#include
#include
#include
#include
#include
std::string _default = "0.0.0.0";
extern log lg;
class Httpserver
{
public:
        Httpserver(std::string _ip = _default, uint16_t _port=80)
                :ip(_ip)
                , port(_port)
                ,islistening(false)
        {

        }
/**
 * 初始化函数
 * 用于创建并绑定监听套接字
 */
        void init()
        {
        // 创建套接字
                listen_socket.socket();
        // 绑定IP地址和端口号
                listen_socket.bind(ip, port);
        }
/**
 * 启动服务器监听功能
 * 该函数用于启动服务器,开始接受客户端连接
 */
        void start()
        {
    // 开始监听端口
                listen_socket.listen();
    // 检查服务器是否已经在监听状态
                if (islistening)
                {
        // 如果已经在监听,记录警告日志并返回
            lg.logmessage(warning,"server is already listening");
                        return;
                }
    // 设置监听状态为true
                islistening = true;
    // 获取线程池单例并启动线程池
                threadpool& tp = threadpool::getinstance();
                tp.start();
    // 定义客户端地址结构体
 struct sockaddr_in client;
    // 设置地址结构体大小
                socklen_t client_len = sizeof(client);
    // 清零客户端地址结构体
                memset(&client, 0, client_len);
                while (islistening)
                {
                        size_t client_fd=listen_socket.accept(&client,&client_len);
                        Task t(client_fd);
                        tp.push(t);
                }
        }
private:
        uint16_t port;
        std::string ip;
        sock listen_socket;
        bool islistening;
};



Socket.hpp:

#include
#include
#include
#include
#include
#include
#include"log.hpp"
extern log lg;
enum
{   
    Socket_Error = 1,
    Bind_Error,
    Listen_Error,
    Accept_Error,
    Connect_Error,
    Usage_Error,
};

class sock
{
public:
    sock()
        :socketfd(-1)
    {

    }
    ~sock()
    {
        if (socketfd >= 0)
        {
            ::close(socketfd);
        }
    }
/**
 * 创建并配置socket
 * 该函数用于创建一个TCP套接字,并设置地址和端口重用选项
 */
    void socket()
    {
    // 使用系统调用socket创建TCP套接字
    // AF_INET表示使用IPv4地址
    // SOCK_STREAM表示使用TCP协议
    // 0表示自动选择合适的协议
        socketfd = ::socket(AF_INET, SOCK_STREAM, 0);
    // 检查socket创建是否成功
        if (socketfd < 0)
{
        // 如果创建失败,记录错误日志并退出程序
            lg.logmessage(Fatal, "socket error");
            socketfd = -1;
            exit(Socket_Error);
        }
    // 设置socket选项,允许地址和端口重用
    // opt为1,表示启用SO_REUSEADDR和SO_REUSEPORT选项
    // 这样可以避免在服务器重启时出现地址已被占用的错误
       int opt = 1;
    setsockopt(socketfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));
    // 记录socket创建成功的日志
        lg.logmessage(info, "socket successfully");
    }

/**
 *  绑定IP地址和端口号到套接字
 *  ip 要绑定的IP地址,字符串形式
 *  port 要绑定的端口号,16位无符号整数
 */
    void bind(std::string ip, uint16_t port)
    {
    // 检查套接字是否有效
        if (socketfd < 0)  // 如果套接字描述符无效(小于0)
        {
            lg.logmessage(Fatal, "socket not created");  // 记录致命错误:套接字未创建
            exit(Socket_Error);  // 退出程序,套接字错误码
        }

    // 创建并清空服务器地址结构体
        struct sockaddr_in server;  // 定义IPv4地址结构体
        memset(&server, 0, sizeof(server));  // 将server结构体清零,确保所有字段初始化为0

    // 设置地址族为IPv4
        server.sin_family = AF_INET;  // 设置地址族为IPv4
    // 将端口号从主机字节序转换为网络字节序
        server.sin_port = htons(port);  // 使用htons函数将端口号转换为网络字节序

    // 处理IP地址
        if (ip == "0.0.0.0")  // 检查是否为通配地址
        {
        // 如果IP是0.0.0.0,绑定到所有可用的网络接口
            server.sin_addr.s_addr = INADDR_ANY;  // 绑定到所有可用的网络接口
        }
        else if (inet_pton(AF_INET, ip.c_str(), &server.sin_addr) <= 0)  // 尝试将IP字符串转换为网络地址格式
        {
        // 尝试将IP字符串转换为网络地址格式,如果失败则记录错误
            lg.logmessage(Fatal, "inet_pton fail");  // 记录致命错误:IP地址转换失败
            ::close(socketfd);  // 关闭套接字
            socketfd = -1;  // 将套接字描述符设置为无效值
            exit(Bind_Error);  // 退出程序,绑定错误码
        }
        socklen_t serverlen = sizeof(server);  // 获取服务器地址结构体的大小
        int n = ::bind(socketfd, (struct sockaddr*)&server, serverlen);  // 调用bind函数绑定地址
 if (n < 0)  // 检查bind函数是否执行成功
        {
            lg.logmessage(Fatal, "bind error");  // 记录致命错误:绑定失败
            ::close(socketfd);  // 关闭套接字
            socketfd = -1;  // 将套接字描述符设置为无效值
            exit(Bind_Error);  // 退出程序,绑定错误码

        }
        lg.logmessage(info, "bind successfully");  // 记录信息:绑定成功
    }
/**
 * 监听函数,用于开始监听客户端连接请求
 * 该函数首先检查socket是否已创建,然后调用listen函数开始监听
 * 如果监听失败,会进行错误处理并退出程序
 */
    void listen()
    {
    // 检查socket是否已创建,如果socketfd小于0表示socket未创建
        if (socketfd < 0)
        {
        // 记录致命错误日志:socket未创建
            lg.logmessage(Fatal, "socket not created");
        // 退出程序,退出码为Socket_Error
            exit(Socket_Error);
        }
    // 调用系统listen函数开始监听,第二个参数5表示最大连接队列长度
        int n = ::listen(socketfd, 5);
    // 检查listen函数是否成功执行
        if (n < 0)
        {
        // 记录致命错误日志:监听失败
            lg.logmessage(Fatal, "listen error");
        // 关闭socket文件描述符
            ::close(socketfd);
        // 将socketfd重置为-1,表示socket未创建
            socketfd = -1;
        // 退出程序,退出码为Listen_Error
            exit(Listen_Error);
        }
    // 记录信息日志:监听成功
        lg.logmessage(info, "listen successfully");
    }
    
	/** 接受一个客户端的连接请求*/
int accept(struct sockaddr_in* client, socklen_t* clientlen)
{
    // 检查监听套接字 socketfd 是否有效
    // 如果无效,说明服务器套接字未被正确初始化,这是一个致命错误
    if (socketfd < 0)
    {
        // 记录一条致命级别的日志信息
        lg.logmessage(Fatal, "socket not created");
        // 终止程序,并返回一个特定的错误码 Socket_Error
        exit(Socket_Error);
    }

    // 调用系统提供的 accept 函数,阻塞等待客户端连接
    // socketfd: 监听套接字描述符
    // (struct sockaddr*)client: 将客户端地址信息存入 client 指向的结构体
    // clientlen: 传入 client 结构体的大小,并接收实际地址结构的大小
    int client_fd = ::accept(socketfd, (struct sockaddr*)client, clientlen);

    // 检查 accept 是否成功
    if (client_fd < 0)
    {
        // 如果 accept 失败,记录一条致命错误日志
        lg.logmessage(Fatal, "accept error");
        // 返回 -1 表示接受连接失败
        return -1;
    }

    // 如果 accept 成功,记录一条信息日志
    lg.logmessage(info, "accept successfully");

    // 返回新创建的、用于与该客户端通信的套接字描述符
    return client_fd;
}

/**
 * 连接到服务器的函数
 */
    void connect(struct sockaddr_in* server, socklen_t serverlen)
    {
    // 检查socket是否已创建
        if (socketfd < 0)
        {
        // 记录socket未创建的致命错误日志
            lg.logmessage(Fatal, "socket not created");
        // 退出程序,错误码为Socket_Error
            exit(Socket_Error);
        }
    // 尝试连接到服务器
        int n = ::connect(socketfd, (struct sockaddr*)server, serverlen);
    // 检查连接是否成功
        if (n < 0)
        {
        // 记录连接失败的致命错误日志
            lg.logmessage(Fatal, "connect error");
        // 关闭socket
            ::close(socketfd);
        // 重置socket文件描述符
            socketfd = -1;
        // 退出程序,错误码为Connect_Error
            exit(Connect_Error);
        }
    // 记录连接成功的信息日志
        lg.logmessage(info, "connect successfully");
    }
    void close()
    {
        if (socketfd >= 0)
 {
            ::close(socketfd);
            socketfd = -1;
        }
    }
    sock(const sock&) = delete;
    sock& operator=(const sock&) = delete;
private:
    int socketfd;
};

Threadpool.h:

#pragma once
#include
#include
#include
#include
#include
#include"Task.hpp"
#define max_size 10

class threadpool
{
public:

    static threadpool& getinstance()
    {

        static threadpool instance;
        return instance;
    }

/**
 * 启动函数,用于创建多个线程执行任务
 * 该函数会创建Max_size个线程,每个线程都执行handlertask函数
 */
    void start()
    {

    // 循环创建Max_size个线程
        for (int i = 0; i < Max_size; i++)
        {

        // 声明线程标识符tid
            pthread_t tid;
        // 创建线程,执行handlertask函数,并将当前对象指针作为参数传递
        // 第一个参数:线程标识符指针
        // 第二个参数:线程属性,设为NULL表示使用默认属性
        // 第三个参数:线程处理函数,即handlertask
        // 第四个参数:传递给线程处理函数的参数,这里传递当前对象的this指针
            pthread_create(&tid, NULL, handlertask, this);
        }
    }


/**
 * 从队列中弹出一个任务
 */
    Task pop()
    {  // 从队列中弹出任务的函数
        sem_wait(&element);  // 等待有元素可用,信号量element减1
        pthread_mutex_lock(&mutex);  // 加锁,确保线程安全
        Task data = q[c_index];  // 获取当前索引处的任务
    c_index = (c_index + 1) % Max_task_size;  // 更新消费者索引,实现循环队列
        pthread_mutex_unlock(&mutex);  // 解锁
        sem_post(&space);  // 释放空间,信号量space加1
        return data;  // 返回获取的任务
    }
    // 向任务队列中添加一个任务
    void push(const Task& T)
    {
        sem_wait(&space);  // 等待空间信号量,表示队列中有空闲位置
        q[p_index] = T;    // 将任务T存入队列的当前位置
        p_index = (p_index + 1) % Max_task_size;  // 更新写入位置,使用模运算实现循环队列
        sem_post(&element);  // 发送元素信号量,表示队列中新增了一个元素
    }

    ~threadpool()
    {
        pthread_mutex_destroy(&mutex);
        sem_destroy(&element);
        sem_destroy(&space);
    }
    threadpool(const threadpool&) = delete;
    threadpool& operator=(const threadpool&) = delete;
private:
/**
 * 线程池的构造函数,用于初始化线程池
 */
    threadpool(int max_num = max_size, int max_task_size = max_size)
        :Max_size(max_num)        // 初始化最大线程数
        , c_index(0)             // 初始化消费者索引,用于任务队列的环形缓冲区
        , p_index(0)             // 初始化生产者索引,用于任务队列的环形缓冲区
        , Max_task_size(max_task_size) // 初始化任务队列的最大容量
    {
        q.resize(Max_task_size); // 调整任务队列的大小为最大容量
    // 初始化互斥锁,用于保护共享资源的访问
        pthread_mutex_init(&mutex, NULL);
    // 初始化信号量element,表示队列中的任务数量,初始为0
        sem_init(&element, 0, 0);
    // 初始化信号量space,表示队列中的可用空间,初始为Max_task_size
        sem_init(&space, 0, Max_task_size);
    }

/**
 * 线程池中工作线程的任务处理函数
 */
    static void* handlertask(void* args)
    {
    // 将传入的参数转换为线程池对象指针
       threadpool* tp = (threadpool*)args;

    // 无限循环,持续从任务队列中获取并执行任务
        while (1)
        {


        // 从线程池的任务队列中弹出一个任务
            Task task = tp->pop();
        // 执行获取到的任务
            task.run();
        }

    // 理论上不会执行到这里,因为while(1)是无限循环
        return NULL;
    }

    std::vector<Task> q;
    int Max_size;
    pthread_mutex_t mutex;
    int Max_task_size;
    int c_index;
    int p_index;
    sem_t element;
    sem_t space;
};

Task.hpp:

#pragma once
#include
#include
#include
#include
#include
#include
#include"protocol.hpp"
#include"log.hpp"
#define BUFFER_SIZE 1024
extern log lg;
std::string path="./wwwroot";
/**
 *  从指定的套接字接收完整的HTTP请求,并将其解析到Http_Request对象中
 *
 *            如果成功接收并解析了HTTP请求头(无论是否有正文),返回 true。
 *              如果在接收数据时发生错误(如recv失败),返回 false。
 * 
 * 此函数处理了两种情况:
 *       1. 没有正文的请求(如GET请求)。
 *       2. 有正文的请求(如POST请求),它会根据`Content-Length`头确保接收完整的正文。
 *       函数会阻塞,直到接收到完整的HTTP头(以"

"为标志)和完整的正文(如果存在)。
 */
bool Get_HttpRequest(size_t socketfd, Http_Request& hr)
{
    // 用于存储从套接字接收到的所有原始HTTP请求数据
    std::string data;
    // 临时缓冲区,用于每次recv调用
    char buffer[BUFFER_SIZE];

    // --- 第一阶段:循环接收数据,直到收到完整的HTTP请求头 ---
    // HTTP请求头以 "

" 结尾,这是一个明确的分界符
    while (true)
    {
        // 从套接字读取数据,最多读取 BUFFER_SIZE-1 字节,为 '' 预留一个位置
        ssize_t read_bytes = recv(socketfd, buffer, BUFFER_SIZE - 1, 0);

        // recv 的返回值 <= 0 表示错误或连接被对端关闭
        if (read_bytes <= 0)
        {
            // 记录一条致命错误日志
            lg.logmessage(Fatal, "recv error");
            // 接收失败,返回 false
            return false;
        }

        // 将本次读取到的数据追加到总数据字符串 data 中
        data.append(buffer, read_bytes);

        // 检查 data 中是否已包含完整的HTTP请求头结束符
        if (data.find("

") != std::string::npos)
        {
            // 找到了,说明请求头已完整,跳出循环
            break;
        }
    }

    // 从完整的数据中提取出HTTP请求头部分(包括结束符"

")
    std::string head = data.substr(0, data.find("

") + 4);

    // --- 第二阶段:检查是否有请求正文,并处理 ---
    // 在请求头中查找 "Content-Length:" 字段,这是判断是否有正文以及正文长度的关键
    size_t pos = data.find("Content-Length:");
    if (pos != std::string::npos)
    {
        // 找到了 Content-Length 字段,说明这是一个带有正文的请求(通常是POST)
        
        // 1. 解析 Content-Length 的值
        // 找到该字段所在行的行尾
        ssize_t endpos = head.find("
", pos);
        // 提取 "Content-Length:" 后面的数字字符串
        std::string content_length_str = head.substr(pos + 15, endpos - pos - 15); // "Content-Length:" 长度为15
        // 将字符串转换为整数,得到正文的期望长度
        size_t content_length = std::stoi(content_length_str);

        // 2. 检查当前已接收的数据中是否包含了完整的正文
        // 计算当前已接收的正文部分的长度
        size_t remaining = data.size() - head.size();

        if (remaining < content_length)
        {
            // 情况A:正文不完整,需要继续从套接字读取剩余的正文数据
            
            // 提取已接收的部分正文
            std::string body = data.substr(data.find("

") + 4, remaining);
            // 计算还需要读取的字节数
            int to_read = content_length - remaining;
            char body_buffer[BUFFER_SIZE];

            // 循环读取,直到读完所有剩余的正文数据
            while (to_read > 0)
            {
                // 计算本次最多能读取多少字节(防止读取超过需求)
                ssize_t bytes_to_recv = std::min(BUFFER_SIZE - 1, to_read);
                ssize_t read_bytes = recv(socketfd, body_buffer, bytes_to_recv, 0);

                if (read_bytes <= 0)
                {
                    lg.logmessage(Fatal, "recv error");
                    return false;
                }

                // 将新读取的数据追加到正文字符串中
                body.append(body_buffer, read_bytes);
                // 更新还需要读取的字节数
                to_read -= read_bytes;
            }
            // 将完整的正文存入 hr 对象
            hr.text = body;
        }
        else
        {
            // 情况B:正文已经完整地包含在第一次接收的数据中
            // 直接从 data 中提取指定长度的正文
            hr.text = data.substr(data.find("

") + 4, content_length);
        }
    }
    // 如果没有找到 "Content-Length",则认为请求没有正文,hr.text 保持为空

    // --- 第三阶段:解析请求头 ---
    // 调用 Http_Request 对象的 Deserialization 方法来解析请求头字符串
    bool res = hr.Deserialization(head);

    // 调试打印:输出解析后的 Http_Request 对象的内容
    hr.debugprint();

    // 返回请求头的解析结果
    return res;
}

/**
 *  从指定路径读取文件内容
 * 
 * 返回文件内容字符串,如果文件打开失败则返回空字符串
 */
std::string read_file(std::string file_path)
{
    // 以二进制模式打开文件
       std::ifstream file(file_path,std::ios::binary);
    // 检查文件是否成功打开
       if(!file.is_open())
       {
        // 记录文件未找到的日志
        lg.logmessage(info,"file not found:%s",file_path.c_str());
        return "";
       }
    // 获取文件开始位置
       std::streampos start=file.tellg();
    // 移动文件指针到末尾
 file.seekg(0,std::ios::end);
    // 获取文件结束位置
       std::streampos end=file.tellg();
    // 计算文件大小
       size_t file_size=end-start;
    // 创建并调整字符串大小以容纳文件内容
       std::string content;
       content.resize(file_size);
    // 将文件指针移回开始位置
       file.seekg(0,std::ios::beg);
    // 读取文件内容到字符串
       file.read(&content[0],file_size);
    // 关闭文件
       file.close();
    // 返回读取的文件内容
       return content;
}

std::string Http_Get_Handler(Http_Request& hr);

/**
 * 处理错误的HTTP请求,返回400 Bad Request响应
 * 
 *  std::string 包含HTTP响应头的完整HTTP响应消息
 */
std::string process_bad_request()
{
    // 记录错误日志,级别为Fatal
            lg.logmessage(Fatal,"bad request body");
    // 构建HTTP状态行
            std::string headler_line="HTTP/1.0 400 Bad Request
";
    // 构建HTTP头部字段
            std::string header="Connection: close
";
    // 设置响应内容
            std::string content="Bad Request";
    // 添加内容长度和内容类型头部
            header+="Content-Length: "+std::to_string(content.size())+"
";
            header+="Content-Type: text/plain
";
    // 添加空行表示头部结束
            header+="
";
    // 返回完整的HTTP响应
            return headler_line+header+content;
}
/**
 *  根据输入的操作数和运算符执行基本的算术计算
 * 
 *  如果计算成功执行,返回 true。
 *  如果遇到不支持的运算符或除零错误,返回 false。
 * 
 * 此函数会处理运算符的URL编码形式,例如 "%2B" 会被解码为 "+"。
 *  它会检查除零错误,但不会检查其他潜在的整数溢出问题。
 */
bool process_calculation(std::unordered_map<std::string, std::string>& val, int& result)
{
    // 从映射中提取操作数 "a" 和 "b",并将它们从字符串转换为整数
    int a = std::stoi(val["a"]);
    int b = std::stoi(val["b"]);

    // 从映射中提取运算符 "op"
    std::string op = val["op"];

    // --- 运算符规范化 ---
    // 将URL编码的运算符或不同大小写的表示统一为标准的单字符运算符
    if (op == "+" || op == "%2B" || op == "%2b") // 检查加号
    {
        op = "+";
    }
    else if (op == "-" || op == "%2D" || op == "%2d") // 检查减号
    {
        op = "-";
    }
    else if (op == "*" || op == "%2A" || op == "%2a") // 检查乘号
    {
        op = "*";
    }
    else if (op == "/" || op == "%2F" || op == "%2f") // 检查除号
    {
        op = "/";
    }
    else
    {
        // 如果运算符不是以上任何一种,则记录一条致命错误日志并返回失败
        lg.logmessage(Fatal, "unsupported operator:%s", op.c_str());
        return false;
    }

    // --- 执行计算 ---
    // 使用 switch 语句根据规范化后的运算符执行相应的计算
    switch (op[0]) // 使用 op[0] 进行比较,因为此时 op 已是单字符
    {
        case '+': // 加法
            result = a + b;
            break;
        case '-': // 减法
            result = a - b;
            break;
        case '*': // 乘法
            result = a * b;
            break;
        case '/': // 除法
            // 安全检查:防止除以零
            if (b == 0)
            {
                // 如果除数为0,记录一条警告日志并返回失败
                lg.logmessage(warning, "division by zero");
                return false;
            }
            // 执行整数除法
            result = a / b;
            break; // 为 switch 语句添加了缺失的 break
    }

    // 所有操作成功完成,返回 true
    return true;
}

/**
 *  处理HTTP POST请求,目前主要支持一个计算器功能
 * 
 * 此函数专门处理发往 `/calc` URL的POST请求。它期望请求体中包含三个URL编码的
 * 键值对(例如 "a=10&op=+&b=20"),分别代表第一个操作数、运算符和第二个操作数。
 * 它会解析这些参数,执行计算,并将结果嵌入到一个HTML页面中返回给客户端。
 * 对于任何其他URL或不合法的请求格式,它会返回一个400 Bad Request错误。
 * 
 *  此函数依赖于 `process_calculation` 函数来执行实际的数学运算,
 *       和 `process_bad_request` 函数来生成错误响应。
 *       请求体的解析逻辑较为硬编码,期望固定的三个键值对格式。
 */
std::string Http_Post_Handler(Http_Request& hr)
{
    // --- 1. 初始化 ---
    std::string res;  // 存储最终要返回的HTTP响应字符串
    std::unordered_map<std::string, std::string> val;  // 用于存储从请求体解析出的键值对
    size_t start = 0;  // 辅助变量,用于在解析请求体时标记当前处理的起始位置

    // --- 2. 检查并处理特定的POST URL ---
    // 检查请求的URL路径是否为 "/calc"
    if (hr.url == "/calc")
    {
        // --- 2a. 获取并解析请求体 ---
        // 从 Http_Request 对象中获取POST请求的原始请求体
        std::string body = hr.text;

        // 手动解析请求体,期望格式为 "key1=value1&key2=value2&key3=value3"
        // 这是一个硬编码的解析过程,假设有三个键值对

        // --- 解析第一个键值对 ---
        size_t pos1 = body.find("&"); // 查找第一个键值对的结束符
        if (pos1 == std::string::npos)
        {
            // 如果没有找到,说明格式错误,返回400 Bad Request
            return process_bad_request();
        }
        std::string expression = body.substr(start, pos1); // 提取第一个键值对字符串
        size_t pos2 = expression.find("="); // 在键值对中查找 "="
        if (pos2 == std::string::npos)
        {
            // 没找到 "=",格式错误
            return process_bad_request();
        }
        // 提取键和值,并存入 map
        std::string result_key_str = expression.substr(start, pos2);
        std::string result_value_str = expression.substr(pos2 + 1);
        val[result_key_str] = result_value_str;

        // --- 解析第二个键值对 ---
        start = pos1 + 1; // 更新起始位置到第二个键值对的开头
        pos1 = body.find("&", start); // 查找第二个键值对的结束符
        if (pos1 == std::string::npos)
        {
            return process_bad_request();
        }
        pos2 = body.find("=", start); // 查找 "="
        // 检查 "=" 是否存在且在正确的范围内(在当前 "&" 之前)
        if (pos2 == std::string::npos || pos2 > pos1)
        {
            return process_bad_request();
        }
        // 提取键和值
        result_key_str = body.substr(start, pos2 - start);
        result_value_str = body.substr(pos2 + 1, pos1 - pos2 - 1);
        val[result_key_str] = result_value_str;

        // --- 解析第三个键值对 ---
        start = pos1 + 1; // 更新起始位置
        pos2 = body.find("=", start); // 查找 "="
        if (pos2 == std::string::npos)
        {
            return process_bad_request();
        }
        // 提取键和值,直到字符串末尾
        result_key_str = body.substr(start, pos2 - start);
        result_value_str = body.substr(pos2 + 1);
        val[result_key_str] = result_value_str;

        // --- 2b. 执行计算 ---
        int calc_result;
        // 调用辅助函数进行计算,传入解析出的键值对 map
        if (process_calculation(val, calc_result) == false)
        {
            // 如果计算失败(例如不支持的运算符或除零),返回错误响应
            return process_bad_request();
        }

        // --- 2c. 构建成功的HTTP响应 ---
        // 构建HTTP状态行
        std::string headler_line = "HTTP/1.0 200 OK
";
        // 构建HTTP响应头
        std::string header = "Connection: close
";
        header += "Content-Type: text/html
";

        // 动态生成HTML响应体,用于展示计算结果
        std::string content = "";
        content += "

计算结果展示

"
; content += "

结果为: " + std::to_string(calc_result) + "

"
; content += "返回首页"; content += ""; // 完成响应头,添加内容长度和结束符 header += "Content-Length: " + std::to_string(content.size()) + " "; header += " "; // 空行,标志HTTP头部结束 // 拼接完整的HTTP响应 res = headler_line + header + content; return res; } else { // --- 3. 处理不支持的POST URL --- // 如果URL不是 "/calc",记录一条致命错误日志 lg.logmessage(Fatal, "unsupported post url:%s", hr.url.c_str()); // 并返回一个通用的错误响应 return process_bad_request(); } } class Task { public: Task() :socketfd(-1) { } Task(int _socketfd) :socketfd(_socketfd) { } /** * 处理文件后缀名,返回对应的MIME类型 * 返回对应的MIME类型字符串,如果找不到则返回默认的html类型 */ static std::string suffix_handler(std::string suffix) { // 在map中查找对应的后缀名 auto pos=map.find(suffix); // 如果找不到对应的后缀名 if (pos == map.end()) { // 返回默认的html类型 return map[".html"]; } // 返回找到的对应MIME类型 return map[suffix]; } /** * 运行HTTP请求处理函数 * 该函数负责接收HTTP请求、根据请求类型(GET/POST)调用相应的处理函数, * 并将处理结果发送回客户端,最后关闭socket连接 */ void run() { // 创建HTTP请求对象 Http_Request hr; // 获取HTTP请求 bool get_result = Get_HttpRequest(socketfd,hr); // 如果获取请求失败,记录错误日志并关闭socket连接 if (get_result == false) { lg.logmessage(Fatal,"get http request error"); close(socketfd); return; } std::string res; // 用于存储HTTP响应结果 // 根据HTTP方法类型调用相应的处理函数 if (hr.method == "GET") { res=Http_Get_Handler(hr); // 处理GET请求 } else if (hr.method == "POST") { res=Http_Post_Handler(hr); // 处理POST请求 } else { // 如果是不支持的HTTP方法,记录警告日志并关闭socket连接 lg.logmessage(warning,"unsupported method:%s",hr.method.c_str()); close(socketfd); return; } // 发送HTTP响应 int send_bytes=send(socketfd,res.c_str(),res.size(),0); // 如果发送失败,记录错误日志并关闭socket连接 if(send_bytes<0) { lg.logmessage(Fatal,"send error"); close(socketfd); return; } // 正常处理完成后关闭socket连接 close(socketfd); } private: int socketfd; static std::unordered_map<std::string, std::string> map; }; std::unordered_map<std::string, std::string> Task::map={ {".html","text/html"}, {".css","text/css"}, {".png","image/png"}, {".jpg","image/jpeg"} }; /** * 处理HTTP GET请求并生成相应的HTTP响应 * * 该函数根据请求的URL路径,从服务器文件系统中读取对应的静态文件, * 构建并返回一个完整的HTTP响应字符串。它能处理首页请求,也能处理其他 * 类型的静态资源(如CSS、JavaScript、图片等),并自动设置正确的 * Content-Type。如果请求的文件不存在,它会返回一个404 Not Found错误页面。 * */ std::string Http_Get_Handler(Http_Request& hr) { // --- 1. 初始化变量 --- // 定义用于构建响应的关键变量 std::string file_path; // 存储请求文件的完整服务器路径 std::string content_type; // 存储文件的MIME类型(如 "text/html") std::string res; // 存储最终构建好的完整HTTP响应字符串 // --- 2. 确定文件路径和内容类型 --- // 检查请求的URL是否为根目录 "/" 或首页 "/index.html" if (hr.url == "/" || hr.url == "/index.html") { // 如果是首页请求,则拼接出首页的完整路径 file_path = path + "/index.html"; // 假设 `path` 是服务器根目录 // 首页的内容类型明确为HTML content_type = "text/html"; } else { // 如果是其他文件请求,直接将URL拼接到根目录后 file_path = path + hr.url; // --- 2a. 根据文件后缀确定MIME类型 --- // 查找文件路径中最后一个'.'的位置,以提取文件扩展名 ssize_t pos = file_path.rfind("."); if (pos == std::string::npos) { // 如果找不到后缀(如请求一个没有扩展名的文件),则默认按HTML处理 content_type = "text/html"; } else { // 提取文件后缀(包括点,如 ".html") std::string suffix = file_path.substr(pos); // 调用辅助函数根据后缀获取正确的MIME类型 content_type = Task::suffix_handler(suffix); } } // --- 3. 尝试读取请求的文件 --- // 调用 `read_file` 函数读取文件内容到 `body` 字符串中 std::string body = read_file(file_path); // --- 4. 构建HTTP响应 --- // 初始化状态行和头部字段 std::string headler_line; // HTTP响应状态行(如 "HTTP/1.0 200 OK") std::string header; // HTTP响应头部字段 // 检查文件是否成功读取(`body` 是否为空) if (body.empty()) { // --- 4a. 文件不存在,构建404 Not Found响应 --- // 设置404状态行 headler_line = "HTTP/1.0 404 Not Found "; // 添加连接关闭头部,告知客户端此响应后连接将关闭 header += "Connection: close "; // 读取自定义的404错误页面内容 std::string content = read_file(path + "/404.html"); // 添加Content-Length头部,指明响应体大小 header += "Content-Length: " + std::to_string(content.size()) + " "; // 添加Content-Type头部,指明响应体是HTML header += "Content-Type: text/html "; // 头部结束标志 header += " "; // 拼接完整的404响应:状态行 + 头部 + 错误页面内容 res = headler_line + header + content; } else { // --- 4b. 文件存在,构建200 OK响应 --- // 设置200成功状态行 headler_line = "HTTP/1.0 200 OK "; // 添加Content-Length头部 header += "Content-Length: " + std::to_string(body.size()) + " "; // 添加Connection头部 header += "Connection: close "; // 添加Content-Type头部,使用之前确定的MIME类型 header += "Content-Type: " + content_type + " "; // 头部结束标志 header += " "; // 拼接完整的成功响应:状态行 + 头部 + 文件内容 res = headler_line + header + body; } // --- 5. 返回构建好的HTTP响应 --- return res; }

protocol.hpp:

#pragma once
#include"log.hpp"
#include
#include
#include
#include
#include
extern log lg;
class Http_Request
{
public:
/**
 * 反序列化HTTP请求头
 *  反序列化成功返回true,失败返回false
 */
        bool Deserialization(std::string& head)
        {
    // 记录当前处理的起始位置
        size_t start=0;
    // 存储解析出的HTTP头信息
        std::vector<std::string>_header;
    // 循环解析HTTP头,直到遇到空行
                while(true)
        {
            std::string line;    // 存储当前行
        // 查找行结束符的位置
            size_t end=head.find("
",start);
        // 如果找不到行结束符,说明格式错误
            if(end==std::string::npos)
            {
                return false;
            }
        // 提取当前行内容
            line=head.substr(start,end-start);
        // 如果遇到空行,结束头信息解析
            if(line.empty())
            {
                break;
            }
        // 更新起始位置到下一行
            start=end+2;
        // 将解析出的行加入头信息列表
            _header.push_back(line);
        }
    // 如果头信息为空,返回错误
        if(_header.size()<1)
        {
            return false;
        }
    // 解析具体的头信息字段
        for(size_t i=1;i<_header.size();i++)
        {
std::string line=_header[i];    // 当前头信息行
        // 查找键值分隔符的位置
            ssize_t pos=line.find(":");
        // 如果找不到分隔符,格式错误
            if(pos==std::string::npos)
            {
                return false;
            }
        // 提取键
            std::string key=line.substr(0,pos);
        // 计算值的起始位置(跳过分隔符后的空格)
            size_t val_start=pos+1;
            while(val_start<line.size() && std::isspace(line[val_start]))
            {
                val_start++;
            }
        // 提取值并存储到headers映射中
            std::string value=line.substr(val_start);
            headers[key]=value;
        }
    // 解析请求行(第一行)
        std::string first_line=_header[0];
        std::stringstream ss(first_line);
    // 提取方法、URL和HTTP版本
        ss>>method>>url>>http_version;
        return true;
        }
/** 调试打印函数,用于输出HTTP请求的详细信息
 * 该函数会打印请求方法、URL、HTTP版本、头部字段和请求体内容
 */
    void debugprint()
    {
    // 输出一个空行,用于分隔不同部分的输出
         std::cout<<std::endl;
    // 打印HTTP请求的方法、URL和HTTP版本
        std::cout<<method<<" "<<url<<" "<<http_version<<std::endl;
    // 遍历并打印所有的HTTP头部字段
        for(auto it=headers.begin();it!=headers.end();it++)
        {
        // 打印每个头部字段的键值对
            std::cout<<it->first<<": "<<it->second<<std::endl;
        }
    // 输出一个空行,用于分隔头部和请求体
        std::cout<<std::endl;
    // 打印HTTP请求的文本内容(请求体)
        std::cout<<text<<std::endl;
    // 输出一个空行,用于结束本次调试输出
        std::cout<<std::endl;
}
public:
        std::unordered_map<std::string,std::string> headers;
        std::string text;
        std::string method;
        std::string url;
        std::string http_version;
};

httpserver.cpp:

#include"Httpserver.hpp"
#include"log.hpp"
#include
extern log lg;
void usage(std::string progmaname)
{
        std::cout << "usage wrong: " << progmaname << " " << std::endl;
}
/**
 程序的主入口点,负责启动HTTP服务器
 */
int main(int argc, char* argv[])
{
    // --- 1. 检查命令行参数 ---
    // 检查用户是否提供了正确数量的参数(程序名 + 端口号)
    if (argc != 2)
    {
        // 如果参数数量不正确,调用 usage 函数打印正确的使用方法
        // argv[0] 是程序名,这样用户就知道他们输错了什么
        usage(argv[0]);
        // 以错误状态码 -1 终止程序
        exit(-1);
    }

    // --- 2. 解析端口号 ---
    // 将用户提供的端口号(字符串)转换为整数
    // std::stoi 可能会抛出 std::invalid_argument 或 std::out_of_range 异常
    // 在这个简单的实现中,我们没有捕获这些异常,程序会因此崩溃
    uint16_t port = std::stoi(argv[1]);

    // --- 3. 创建并初始化HTTP服务器 ---
    // 创建一个名为 `hs` 的 HTTPserver 对象
    // `_default` 设置为“0.0.0.0”,监听所有网络接口
    // `port` 是上一步解析出的端口号
    Httpserver hs(_default, port);

    // 调用 `init` 方法对服务器进行初始化
    // 这包括创建套接字、绑定地址
    hs.init();

    // --- 4. 启动服务器 ---
    // 调用 `start` 方法启动服务器
    // 这个方法通常会进入一个无限循环(事件循环),接受客户端连接并处理请求
    // 程序会阻塞在这里,直到服务器被手动终止(例如通过Ctrl+C)
    hs.start();

    // --- 5. 程序退出 ---
    // 如果 `start` 方法返回(通常意味着服务器已停止),程序将执行到这里
    // 返回 0 表示程序正常结束
    return 0;
}

log.hpp:

#pragma once
#include
#include
#include
#include
#include
#define SIZE 1024
#define screen 0
#define File 1
#define ClassFile 2
enum
{   
    info,
    debug,
    warning,
    Fatal,
};
class log
{
private:
    std::string memssage;
    int method;
public:
    log(int _method = File)
        :method(_method)
    {
    
    }
    void logmessage(int leval, char* format, ...)
    {
        char* _leval;
        switch (leval)
        {
        case info: 
            _leval = "info";
            break;
        case debug:
     _leval = "debug";
            break;
        case warning:
            _leval = "warning";
            break;
        case Fatal:
            _leval = "Fatal";
            break;
        }
        char timebuffer[SIZE];
        time_t t = time(NULL);
        struct tm* localTime = localtime(&t);
        snprintf(timebuffer, SIZE, "[%d-%d-%d-%d:%d]", localTime->tm_year + 1900, localTime->tm_mon + 1, localTime->tm_mday, localTime->tm_hour, localTime->tm_min);
        char rightbuffer[SIZE];
        va_list arg;
        va_start(arg, format);
        vsnprintf(rightbuffer, SIZE, format, arg);
        char finalbuffer[2 * SIZE];
        int len=snprintf(finalbuffer, sizeof(finalbuffer), "[%s]%s:%s
", _leval, timebuffer, rightbuffer);
        int fd;
        switch (method)
        {
        case screen:
            std::cout << finalbuffer;
            break;
        case File:
            fd = open("log.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);
            if (fd >= 0)
            {
                write(fd, finalbuffer, len);
                close(fd);
            }
            break;
        case ClassFile:
            switch (leval)
 {
            case info:
                fd = open("log/info.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
                write(fd, finalbuffer, sizeof(finalbuffer)); 
                break;
            case debug:
                fd = open("log/debug.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
                write(fd, finalbuffer, sizeof(finalbuffer));
                break;
            case warning:
                fd = open("log/Warning.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
                write(fd, finalbuffer, sizeof(finalbuffer));
                break;
            case Fatal:
                fd = open("log/Fat.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
                break;
            }   
            if (fd > 0)
            {
                write(fd, finalbuffer, sizeof(finalbuffer));
                close(fd);
            }   
        }   
    }   
};
log lg;


运行截图:



结语

那么这就是本篇文章的全部内容,带你全面认识以及掌握json以及http,并且实现web计算器服务器,那么下一期博客我会更新https,我会持续更新,希望你能够多多关注,如果本文有帮助到你的话,还请三连加关注,你的支持就是我创作的最大动力!

本文地址:https://www.vps345.com/26678.html

搜索文章

Tags

docker 容器 运维 java-rabbitmq java 智能驾驶 BEVFusion Ubuntu 服务器带宽 上行带宽 上行速率 什么是上行带宽? PV计算 带宽计算 流量带宽 #docker #centos #容器 #windows #自动化 #运维 linux pytorch tensorflow macos windows 服务器 嵌入式硬件 网络 远程连接 vscode #git #ssh #windows 流量攻击 DDOS攻击 服务器被攻击怎么办 源IP ubuntu24.04 todesk CC攻击 攻击怎么办 AI Dify 大模型应用 #linux #命令 #网络 ide github ubuntu 无人机 机器人 #ubuntu #linux python c++ php ssh漏洞 ssh9.9p2 CVE-2025-23419 #harmonyos #android #ios ai nlp harmonyos 华为 react native #java #华为 c# 开发语言 网络协议 网络安全 centos 人工智能 云原生 #macos #EasyConnect 安全 #javascript #开发语言 #ecmascript #区块链 #智能合约 #jenkins #人工智能 #云原生 ssh remote-ssh 部署 边缘计算 具身智能 强化学习 ollama llm conda ROS 自动驾驶 经验分享 自动化 wireshark prometheus grafana 深度学习 IPMI micropython esp32 单片机 mqtt 物联网 远程工作 web3 区块链 区块链项目 tomcat 中间件 web安全 可信计算技术 安全架构 网络攻击模型 debian wps 安卓 CosyVoice fpga开发 zabbix LLM 大模型面经 大模型 职场和发展 Deepseek 大模型学习 服务器安全 网络安全策略 防御服务器攻击 安全威胁和解决方案 程序员博客保护 数据保护 安全最佳实践 语言模型 AI大模型 DeepSeek agi 数据库 mysql adb 智能合约 压力测试 哈希算法 #激光雷达 #览沃 #ubuntu22.04 #ros2 #大疆 网络药理学 生信 分子对接 autodock mgltools PDB PubChem uni-app m3u8 HLS 小程序 移动端H5网页 APP安卓苹果ios 监控画面 直播视频流 火绒安全 mybase n8n AIGC comfyui comfyui教程 游戏引擎 学习 r语言 数据挖掘 数据可视化 数据分析 机器学习 存储维护 NetApp存储 EMC存储 Playwright pythonai PlaywrightMCP kylin stm32 tcp/ip 华为云 华为od 快捷键 旋转屏幕 自动操作 虚拟机 intellij-idea flutter Google pay Apple pay 开源 dity make 1024程序员节 计算机视觉 持续部署 jenkins apache pdf xml ddos Linux 维护模式 Agent llama CrewAI 3d gpu算力 MVS 海康威视相机 bash 编辑器 pip 网络工程师 华为认证 #网络 #dify ssl #人工智能 #深度学习 #机器学习 #考研 #计算机视觉 YOLOv8 NPU Atlas800 A300I pro 智能路由器 Java进程管理 DevOps自动化 脚本执行 跨平台开发 远程运维 Apache Exec JSch 策略模式 mac mac安装软件 mac卸载软件 mac book golang YOLO 目标检测 rk3588 npu rknn-toolkit2 群晖 低代码 阿里云 云计算 ruoyi yolov5 ESXi Dell HPE 联想 浪潮 kvm qemu libvirt unity GameFramework HybridCLR Unity编辑器扩展 自动化工具 性能优化 arm开发 搜索引擎 程序员 prompt ffmpeg 音视频 视频编解码 负载均衡 unix dash json 正则表达式 DevEco Studio HarmonyOS OpenHarmony 真机调试 appium 软件测试 自动化测试 功能测试 程序人生 svn 电路仿真 multisim 硬件工程师 硬件工程师学习 电路图 电路分析 仪器仪表 MacMini Mac 迷你主机 mini Apple 算法 spring boot spring 神经网络 pycharm django fastapi 后端 springsecurity6 oauth2 授权服务器 前后端分离 html http css #算法 #数据清洗 Kylin-Server 国产操作系统 服务器安装 git onlyoffice 在线office gnu iot rpa qt linuxdeployqt 打包部署程序 appimagetool 自然语言处理 rag ragflow 大模型部署 deepseek 鲲鹏 昇腾 c语言 计算机网络 oceanbase 传统数据库升级 银行 redis 飞牛NAS 飞牛OS MacBook Pro 鸿蒙 混合开发 环境安装 JDK SenseVoice typescript 微信 #chrome #mac #拓展程序 #禁用 AI编程 visual studio code 单例模式 前端 nginx MCP EVE-NG milvus Docker Docker Compose Kubernetes chatgpt open webui bug 运维开发 #c++ 课程设计 嵌入式Linux IPC Autoware 辅助驾驶 opencv Cline CUDA PyTorch GCC aarch64 编译安装 HPC crosstool-ng 其他 流程图 mermaid #embedding linux环境变量 nvcc cuda A100 #IntelliJ IDEA #Java #Kotlin MobaXterm 文件传输 动态库 GCC编译器 -fPIC -shared word图片自动上传 word一键转存 复制word图片 复制word图文 复制word公式 粘贴word图文 粘贴word公式 uniapp vue 银河麒麟 信创国产化 达梦数据库 vmamba #字体 #安装 #微软雅黑 #office Qwen3 qwen3 32b vllm 本地部署 设计模式 vite Svelte 架构 向量数据库 #centos #vscode #ubuntu lsb_release /etc/issue /proc/version uname -r 查看ubuntu版本 mcu 程序 编程 内存 性能分析 microsoft 硬件工程 实时音视频 实时互动 searxng AI提示词优化 rpc 远程过程调用 Windows环境 信息与通信 图像处理 mybatis DevOps 大数据 软件交付 数据驱动 应用场景 数据安全 大模型压力测试 EvalScope ansible playbook 自动化运维 rtsp h.265 #服务器 #开发语言 #c语言 爬虫 SSH Linux Xterminal MS Materials postgresql pgpool chrome chrome devtools selenium chromedriver okhttp android mamba 智能手机 dify dify部署 三维重建 HTTP 服务器控制 ESP32 DeepSeek FunASR ASR android studio 交互 websocket AutoDL Qwen2.5-VL 笔记 个人开发 gpt transformer 截图 录屏 gif 工具 技能大赛 cron crontab日志 小智 springboot容器部署 springboot容器化部署 微服务容器化负载均衡配置 微服务容器多节点部署 微服务多节点部署配置负载均衡 交换机 硬件 设备 GPU PCI-Express LLM Web APP Streamlit 游戏 iventoy VmWare OpenEuler 鸿蒙系统 hdc 鸿蒙NEXT zephyr Claude Desktop Claude MCP Windows Cli MCP #游戏 #服务器 #云计算 政务 分布式系统 监控运维 Prometheus Grafana elasticsearch 大模型入门 大模型教程 知识图谱 麒麟OS oracle 关系型 分布式 UOS 开机自启动 桌面快捷方式 arcgis gitlab jupyter kubernetes k8s openEuler 欧拉系统 coze #自动化 华为机试 C++ Java Python glibc 游戏服务器 Minecraft rust HTTP状态码 客户端错误 服务器端错误 API设计 openssl 嵌入式 linux驱动开发 javascript eclipse 5G Portainer搭建 Portainer使用 Portainer使用详解 Portainer详解 Portainer portainer wsl docker desktop image 蓝桥杯 智能体开发 工作流自动化工具 bushujiaocheng 部署教程 算家云 AI算力 租算力 到算家云 rc.local 开机自启 systemd 麒麟 vue.js 服务器配置 Deepseek-R1 私有化部署 推理模型 进程 操作系统 进程控制 CH340 串口驱动 CH341 uart 485 WSL2 上安装 Ubuntu 数据集 node.js 计算机学习路线 编程语言选择 SSE 代码调试 ipdb 安全漏洞 信息安全 飞腾处理器 硬件架构 国产化 环境部署 pygame MLLMs VLM gpt-4v 远程桌面 llama3 Chatglm 开源大模型 三级等保 服务器审计日志备份 企业微信 LVS cmake 网页服务器 web服务器 Nginx openjdk IPv4/IPv6双栈 双栈技术 网路规划设计 ensp综合实验 IPv4过渡IPv6 IPv4与IPv6 vim 毕设 Windsurf chatbox 软件工程 软件构建 LLMs GenAI LLM 推理优化 LLM serving 测试工具 seatunnel nohup 异步执行 Apache Flume 数据采集 安装部署 配置优化 高级功能 大数据工具集成 王者荣耀 系统架构 EasyConnect ragflow 源码启动 vue3 HCIE 数通 opensearch helm 大大通 第三代半导体 碳化硅 监控 计算机外设 Docker Hub docker pull 镜像源 daemon.json 多线程服务器 Linux网络编程 FTP服务器 驱动开发 嵌入式实习 dubbo 科技 Windows ai工具 视频平台 录像 RTSP 视频转发 性能测试 视频流 存储 信号处理 tcpdump gitee gitee go 云计算面试题 umeditor粘贴word ueditor粘贴word ueditor复制word ueditor上传word图片 chromium dpi 华为鸿蒙系统 ArkTS语言 Component 生命周期 条件渲染 Image图片组件 YOLOv12 网络结构图 yaml Ultralytics 可视化 openwrt USB网络共享 进程信号 知识库 本地化部署 ACL 流量控制 基本ACL 网络管理 规则配置 安全威胁分析 安全性测试 eureka 环境迁移 数据库架构 数据管理 数据治理 数据编织 数据虚拟化 程序化交易 量化交易 高频交易 mcp mcp协议 go-zero mcp服务器 harmonyOS面试题 UEFI Legacy MBR GPT U盘安装操作系统 进程间通信 cursor webpack 开发工具 基础指令 指令 nac 802.1 portal visualstudio 语音识别 mariadb servlet muduo 网络库 hadoop big data Spring AI 大模型应用开发 AI 应用商业化 #提示词注入 #防护 #安全 #大模型 sdkman 安卓模拟器 arkUI arkTs GPU训练 langchain deep learning ui 金融 相机 主从复制 计算生物学 生物信息学 生物信息 基因组 ShapeFile GeoJSON devops 防火墙 ufw word 学习方法 xrdp GIS 遥感 WebGIS Cursor iNode Macos burpsuite 安全工具 mac安全工具 burp安装教程 渗透工具 电脑桌面出现linux图标 电脑桌面linux图标删除不了 电脑桌面Liunx图标删不掉 linux图标删不掉 RTX5090 torch2.7.0 Claude 微服务 数据结构 rabbitmq 换源 国内源 Debian Alist rclone mount 挂载 网盘 考研 MCP server agent C/S FTP 服务器 DNS jar 统信 UOS1070e Ollama Chatbox PyQt PySide6 重启 排查 系统重启 日志 原因 多线程 pthread 系统 SecureCRT Bug解决 Qt platform OpenCV ros 树莓派项目 latex 模块测试 wsl2 RAGFlow 本地知识库部署 DeepSeek R1 模型 QQ bot Vmamba spring cloud flash-attention 报错 iBMC UltraISO maven 云服务器 open Euler dde deepin 统信UOS 统信操作系统 yum 宝塔面板 HarmonyOS Next can 线程池 镜像 Kali 渗透 物理地址 页表 虚拟地址 缓存 微信公众平台 RagFlow RAG 雨云 NPS 虚拟机安装 triton 模型分析 服务器扩容没有扩容成功 matlab archlinux kde plasma RAID RAID技术 磁盘 程序员创富 perl 百度 paddlepaddle 集成学习 集成测试 stm32项目 系统安全 kind flask web3.py ESP32 Java Applet URL操作 服务器建立 Socket编程 网络文件读取 grub 版本升级 扩容 微信开放平台 微信公众号配置 话题通信 服务通信 windows 服务器安装 Pyppeteer element-ui 上传视频并预览视频 vue上传本地视频及进度条功能 vue2选择视频上传到服务器 upload上传视频组件插件 批量上传视频 限制单个上传视频 jvm 源代码管理 vmware tools VMware 计算机系统 网络编程 电子信息 通信工程 毕业 软件商店 信创 livecd systemtools 产品经理 lvgl8.3 lvgl9.2 lvgl lvgl安装 工作流 workflow 安装MySQL postgres Docker Desktop Dify重启后重新初始化 deepseek-v3 ktransformers Multi-Agent #redis arm 服务器无法访问 ip地址无法访问 无法访问宝塔面板 宝塔面板打不开 智能硬件 外网访问 内网穿透 端口映射 gemini gemini国内访问 gemini api gemini中转搭建 Cloudflare NFS react.js list gcc centos 7 cudnn nvidia docker compose 烟雾检测 yolo检测 消防检测 开源软件 tidb GLIBC elk glm4 电脑 NVML nvidia-smi 客户端-服务器架构 点对点网络 服务协议 网络虚拟化 网络安全防御 抽象工厂模式 apt 前端框架 vmware nvm node ArkTS RockyLinux #git #vim Logstash 日志采集 文心一言 Trae IDE AI 原生集成开发环境 Trae AI NAS Termux Samba 密码学 微信小程序 notepad++ ip nftables kernel function address 函数 地址 内核 迁移 openeuler libreoffice word转pdf 安装 代码复审 codereview code-review eNSP 企业网络规划 华为eNSP 网络规划 线程 远程 命令 执行 sshpass 操作 ai小智 语音助手 ai小智配网 ai小智教程 esp32语音助手 diy语音助手 宝塔 卷积神经网络 k8s部署 MySQL8.0 高可用集群(1主2从) 论文笔记 升级 CVE-2024-7347 漏洞 H3C 自定义客户端 SAS IMM https 反向代理 ecmascript 生成对抗网络 数学建模 embedding stable diffusion AI作画 FS 文件系统 bootfs rootfs linux目录 KVM 代理模式 udp 物联网开发 MQTT RustDesk自建服务器 rustdesk服务器 docker rustdesk 大模型微调 我的世界 我的世界联机 数码 无桌面 命令行 Dell R750XS 弹性计算 裸金属服务器 弹性裸金属服务器 虚拟化 KylinV10 麒麟操作系统 Vmware bigdata Uvicorn trae sequoiaDB 软件需求 隐藏文件 环境配置 网络建设与运维 网络搭建 神州数码 神州数码云平台 云平台 android-studio nohup后台启动 健康医疗 常用命令 文本命令 目录命令 ISO镜像作为本地源 sql 游戏程序 wordpress 无法访问wordpess后台 打开网站页面错乱 linux宝塔面板 wordpress更换服务器 矩阵乘法 3D深度学习 centos-root /dev/mapper yum clean all df -h / du -sh 虚拟显示器 远程控制 我的世界服务器搭建 minecraft 安装教程 GPU环境配置 Ubuntu22 Anaconda安装 zookeeper 实时内核 jdk Obsidian Dataview vsxsrv easyTier 组网 yolov8 rnn 抓包工具 前端面试题 fiddler Crawlee 线程互斥与同步 ukui 麒麟kylinos 上传视频至服务器代码 vue3批量上传多个视频并预览 如何实现将本地视频上传到网页 element plu视频上传 ant design vue vue3本地上传视频及预览移除 webrtc hive ranger MySQL8.0 ftp服务 文件上传 树莓派 Navidrome Python 视频爬取教程 Python 视频爬取 Python教程 Python 视频教程 华为昇腾910b3 rsync 性能监控 ArkTs ArkUI maxkb ARG 桌面环境 开发环境 VSCode 微软 文档 DBeaver 数据仓库 kerberos miniapp 调试 debug 断点 网络API请求调试方法 终端工具 远程工具 记账软件 springboot 容器部署 计算机八股 网络配置 路由配置 框架搭建 v10 软件 C++软件实战问题排查经验分享 0xfeeefeee 0xcdcdcdcd 动态库加载失败 程序启动失败 程序运行权限 标准用户权限与管理员权限 midjourney AI写作 jmeter postman aws 分布式账本 信任链 共识算法 云电脑 5090 显卡 AI性能 开发效率 Windmill anaconda protobuf 序列化和反序列化 jellyfin nas rocketmq 人工智能生成内容 miniconda paddle virtualenv 增强现实 沉浸式体验 技术实现 案例分析 AR 监控k8s集群 集群内prometheus 性能调优 安全代理 etcd cfssl Mermaid 可视化图表 自动化生成 PX4 Linux Vim 网络爬虫 dns是什么 如何设置电脑dns dns应该如何设置 lstm LSTM-SVM 时间序列预测 issue C语言 豆瓣 追剧助手 迅雷 adobe 音乐服务器 音流 mosquitto 消息队列 致远OA OA服务器 服务器磁盘扩容 asp.net大文件上传 asp.net大文件上传源码 ASP.NET断点续传 asp.net上传文件夹 asp.net上传大文件 .net core断点续传 .net mvc断点续传 IM即时通讯 剪切板对通 HTML FORMAT HP Anyware AI-native 7-zip ipython C 环境变量 进程地址空间 neo4j 数据库开发 database python2 gpt-3 ollama api ollama外网访问 IP地址 IPv4 IPv6 端口号 计算机基础 更换镜像源 shell minio ShenTong minicom 串口调试工具 docker搭建nacos详解 docker部署nacos docker安装nacos 腾讯云搭建nacos centos7搭建nacos gromacs 分子动力学模拟 MD 动力学模拟 ROS2 Masshunter 质谱采集分析软件 使用教程 科研软件 创业创新 GPUGEEK STP 生成树协议 PVST RSTP MSTP 防环路 网络基础 仙盟大衍灵机 东方仙盟 仙盟创梦IDE Apache OpenNLP 句子检测 分词 词性标注 核心指代解析 僵尸世界大战 游戏服务器搭建 string模拟实现 深拷贝 浅拷贝 经典的string类问题 三个swap thingsboard 计算虚拟化 弹性裸金属 游戏开发 html5 less .netcore .net core .net Netty OpenGL 图形渲染 状态模式 cocoapods xcode ubuntu20.04 开机黑屏 notepad CPU 使用率 系统监控工具 linux 命令 burp suite 抓包 打不开xxx软件 无法检查其是否包含恶意软件 css3 js 渗透测试 系统内核 Linux版本 Kali Linux TRAE 腾讯云大模型知识引擎 DocFlow 监控k8s 监控kubernetes go LSTM unionFS OverlayFS OCI docker架构 写时复制 单一职责原则 rust腐蚀 测试用例 ios python3.11 pyside6 界面 localhost 毕业设计 struts 物联网嵌入式开发实训室 物联网实训室 嵌入式开发实训室 物联网应用技术专业实训室 MacOS 向日葵 g++ g++13 Ubuntu 22.04 MySql 算力租赁 Android ANDROID_HOME zshrc charles HP打印机 怎么卸载MySQL MySQL怎么卸载干净 MySQL卸载重新安装教程 MySQL5.7卸载 Linux卸载MySQL8.0 如何卸载MySQL教程 MySQL卸载与安装 拓扑图 PPI String Cytoscape CytoHubba ip命令 新增网卡 新增IP 启动网卡 rdp 远程服务 脚本 软考设计师 中级设计师 SQL 软件设计师 gru Docker 部署es9 Docker部署es Docker搭建es9 Elasticsearch9 Docker搭建es pppoe radius 微信小程序域名配置 微信小程序服务器域名 微信小程序合法域名 小程序配置业务域名 微信小程序需要域名吗 微信小程序添加域名 odoo 服务器动作 Server action 大模型推理 frp 内网服务器 内网代理 内网通信 国标28181 视频监控 监控接入 语音广播 流程 SIP SDP express 聊天室 AP配网 AK配网 小程序AP配网和AK配网教程 WIFI设备配网小程序UDP开 媒体 Jellyfin 虚幻 锁屏不生效 p2p massa sui aptos sei LangGraph CLI JavaScript langgraph.json ECS API 源码 perf linux内核 iTerm2 sqlserver 磁盘挂载 新盘添加 partedUtil tftp nfs NVIDIA显卡安装 Ubuntu开机黑屏 鸿蒙项目 #数据库 AI代码编辑器 车载系统 3GPP 卫星通信 mcp-proxy mcp-inspector fastapi-mcp sse yum源切换 更换国内yum源 虚拟局域网 Node-Red 编程工具 流编程 yum换源 开放端口 访问列表 kubeless 超级终端 多任务操作 提高工作效率 面试 写时拷贝 Linux的进程调度队列 活动队列 spark HistoryServer Spark YARN jobhistory armbian u-boot mount挂载磁盘 wrong fs type LVM挂载磁盘 Centos7.9 云电竞 unity3d CORS 跨域 服务器繁忙 nuxt3 文件分享 WebDAV tailscale derp derper 中转 bonding 链路聚合 kafka 网站搭建 serv00 博客 图形化界面 笔灵AI AI工具 CPU架构 服务器cpu Featurize Mobilenet 分割 cn2 带宽 prometheus数据采集 prometheus数据模型 prometheus特点 openvpn server openvpn配置教程 centos安装openvpn Isaac Sim 虚拟仿真 Charles chrome历史版本下载 chrominum下载 软硬链接 文件 蓝耘科技 元生代平台工作流 ComfyUI 自动化任务管理 seleium ip协议 计算机科学与技术 网卡 autoware ros2 卸载 列表 转换 ajax Quixel Fab Unity UE5 游戏商城 虚幻引擎 数字化转型 团队开发 #运维 #openssh升级 #银河麒麟V10 SP10 实验 技术 网工 Anolis nginx安装 linux插件下载 剧本 LDAP AD 域管理 服务器管理 配置教程 网站管理 NFC 近场通讯 智能门锁 显示器 finebi 若依 内存不足 outofmemory Key exchange 主包过大 HarmonyOS5 make命令 makefile文件 ArcTS 登录 ArcUI GridItem 命名管道 客户端与服务端通信 c/c++ 串口 fstab initramfs Linux内核 Grub WSL resolv.conf 苹果电脑装windows系统 mac安装windows系统 mac装双系统 macbook安装win10双 mac安装win10双系统 苹果电脑上安装双系统 mac air安装win vnc Python基础 Python技巧 openstack Xen Hyper-V WSL2 deekseek 显卡驱动持久化 GPU持久化 #llama #docker #kimi xop RTP RTSPServer 推流 视频 autodl dell服务器 HTML audio 控件组件 vue3 audio音乐播放器 Audio标签自定义样式默认 vue3播放音频文件音效音乐 自定义audio播放器样式 播放暂停调整声音大小下载文件 gaussdb 云服务 分布式训练 CDN saltstack 机柜 1U 2U 录音麦克风权限判断检测 录音功能 录音文件mp3播放 小程序实现录音及播放功能 RecorderManager 解决录音报错播放没声音问题 AI员工 aac fpga janus chrome 浏览器下载 chrome 下载安装 谷歌浏览器下载 稳定性 看门狗 Echarts图表 折线图 柱状图 异步动态数据 鸿蒙开发 可视化效果 Ubuntu20.04 2.35 mac设置host pycharm安装 SystemV RAGflow 深度求索 私域 SSH 服务 SSH Server OpenSSH Server Open WebUI RoboVLM 通用机器人策略 VLA设计哲学 vlm fot robot 视觉语言动作模型 moveit 机器人运动 easyui 云原生开发 接口优化 k8s二次开发 zip unzip kali 共享文件夹 mongodb edge MAVROS 四旋翼无人机 Agentic Web NLWeb 自然语言网络 微软build大会 二级页表 数据链路层 即时通信 NIO 黑客 计算机 SWAT 配置文件 服务管理 网络共享 磁盘镜像 服务器镜像 服务器实时复制 实时文件备份 dba 客户端 java-ee gitlab服务器 Arduino 电子信息工程 进程优先级 调度队列 进程切换 智慧农业 开源鸿蒙 动静态库 harmonyosnext homebrew windows转mac ssh密匙 Mac配brew环境变量 #pytorch Linux24.04 网络用户购物行为分析可视化平台 大数据毕业设计 intellij idea 宝塔面板无法访问 隐藏目录 管理器 通配符 冯诺依曼体系 企业风控系统 互联网反欺诈 DDoS攻击 SQL注入攻击 恶意软件和病毒攻击 切换root 大语言模型 CAN 多总线 Typescript 局域网 shell编程 lua #java #maven #java-ee #spring boot #jvm #kafka #tomcat react next.js 部署next.js rustdesk SSL证书 excel 在线预览 xlsx xls文件 在浏览器直接打开解析xls表格 前端实现vue3打开excel 文件地址url或接口文档流二进 数据库系统 PVE iis vue-i18n 国际化多语言 vue2中英文切换详细教程 如何动态加载i18n语言包 把语言json放到服务器调用 前端调用api获取语言配置文件 腾讯云 XCC Lenovo llama.cpp 信创终端 中科方德 大模型训练/推理 推理问题 mindie 网络接口 时间间隔 所有接口 多网口 teamspeak 客户端/服务器架构 分布式应用 三层架构 Web应用 跨平台兼容性 京东云 移动开发 bcompare Beyond Compare 系统开发 binder framework 源码环境 docker-compose pyicu IP 地址 Metastore Catalog vm OpenSSH OpenManus opengl 私有化 Helm k8s集群 ubuntu安装 linux入门小白 文件共享 lvs audio vue音乐播放器 vue播放音频文件 Audio音频播放器自定义样式 播放暂停进度条音量调节快进快退 自定义audio覆盖默认样式 信息收集 银河麒麟高级服务器 外接硬盘 Kylin 状态管理的 UDP 服务器 Arduino RTOS React Next.js 开源框架 硅基流动 ChatBox 概率论 sqlite 串口服务器 万物互联 工业自动化 工厂改造 材料工程 全文检索 服务发现 滑动验证码 反爬虫 uni-popup报错 连接服务器超时 点击屏幕重试 uniapp编译报错 uniapp vue3 imported module TypeError #金融 #科技 #大数据 灵办AI 版本 HiCar CarLife+ CarPlay QT RK3588 华为证书 HarmonyOS认证 华为证书考试 Ardupilot 星河版 brew milvus安装 代码 对比 meld DiffMerge orbslam2 ubuntu22.04 药品管理 空Ability示例项目 讲解 视觉检测 Docker引擎已经停止 Docker无法使用 WSL进度一直是0 镜像加速地址 单元测试 路径解析 docker部署翻译组件 docker部署deepl docker搭建deepl java对接deepl 翻译组件使用 x64 SIGSEGV xmm0 RBAC numpy docker搭建pg docker搭建pgsql pg授权 postgresql使用 postgresql搭建 显示过滤器 ICMP Wireshark安装 System V共享内存 进程通信 7z top Linux top top命令详解 top命令重点 top常用参数 ros1 Noetic 20.04 apt 安装 Apache Beam 批流统一 案例展示 数据分区 容错机制 软考 2024 2024年上半年 下午真题 答案 cnn VGG网络 卷积层 池化层 Xshell client-go 百度云 模拟器 pnet pnetlab powerpoint 磁盘满 cpu 实时 使用 tcp pyqt firefox VNC outlook Carla 浪潮信息 AI服务器 gunicorn 实时传输 过期连接 flink flinkcdc 多端开发 智慧分发 应用生态 鸿蒙OS Qwen2.5-coder 离线部署 网络文件系统 NVM Node Yarn PM2 swift gstreamer 流媒体 更新apt 安装hadoop前的准备工作 办公自动化 pdf教程 ue4 着色器 ue5 Xinference 设置代理 实用教程 DeepSeek r1 大屏端 LVM 磁盘分区 lvresize 磁盘扩容 pvcreate Linux权限 xshell 权限掩码 粘滞位 双系统 多系统 嵌入式实时数据库 CKA pandas matplotlib pillow Ubuntu 24.04.1 轻量级服务器 模型联网 CherryStudio X11 Xming 毕昇JDK 日志分析 系统取证 商用密码产品体系 logstash linq http状态码 请求协议 AudioLM scrapy DICOM mcp client mcp server 模型上下文协议 iftop 网络流量监控 GaN HEMT 氮化镓 单粒子烧毁 辐射损伤 辐照效应 ArtTS kotlin iphone 推荐算法 rime 线程同步 线程互斥 条件变量 管道 pipe函数 匿名管道 管道的大小 匿名管道的四种情况 linux/cmake vr 数码相机 全景相机 设备选择 实用技巧 数字空间 上架 VLAN 企业网络 iperf3 带宽测试 Reactor qt5 客户端开发 RDP 进程池实现 OS ocr 可用性测试 去中心化 驱动器映射 批量映射 win32wnet模块 网络驱动器映射工具 弹性 AD域 Headless Linux 直播推流 项目部署到linux服务器 项目部署过程 网络穿透 大文件分片上传断点续传及进度条 如何批量上传超大文件并显示进度 axios大文件切片上传详细教 node服务器合并切片 vue3大文件上传报错提示错误 vu大文件秒传跨域报错cors Web服务器 多线程下载工具 PYTHON es6 qt6.3 g726 solidworks安装 PTrade QMT 量化股票 ueditor导入word SRS 显卡驱动 nvidia驱动 Tesla显卡 SFTP SFTP服务端 漫展 邮件APP 免费软件 沙盒 mysql安装报错 windows拒绝安装 CUPS 打印机 Qt5 可执行程序 firewalld OpenCore 鸿蒙面试 面试题 vr看房 在线看房系统 房产营销 房产经济 三维空间 podman 北亚数据恢复 数据恢复 服务器数据恢复 数据库数据恢复 oracle数据恢复 美食 #apache 热榜 卡死 camera Typore 蓝牙 GeneCards OMIM TTD tar 图搜索算法 考试 裸机装机 linux磁盘分区 裸机安装linux 裸机安装ubuntu 裸机安装kali 裸机 python高级编程 Ansible elk stack 智能体 深度强化学习 深度Q网络 Q_Learning 经验回收 authorized_keys 密钥 MySQL 汇编 sse_starlette Starlette FastAPI Server-Sent Eve 服务器推送事件 csapp 缓冲区 kylin v10 麒麟 v10 rsyslog windows日志 api ldap token sas 模拟退火算法 思科模拟器 思科 Cisco dns 上传视频文件到服务器 uniApp本地上传视频并预览 uniapp移动端h5网页 uniapp微信小程序上传视频 uniapp app端视频上传 uniapp uview组件库 samba 机架式服务器 1U工控机 国产工控机 linux cpu负载异常 Cache Aside Read/Write Write Behind cs144 高考 省份 年份 分数线 数据 SQI iOS Server Trust Authentication Challenge jvm调优 内存泄漏 LRU策略 内存增长 垃圾回收 黑苹果 edge浏览器 Linux的基础指令 VMware安装mocOS macOS系统安装 嵌入式系统开发 NVIDIA 权限 npm dnf illustrator #华为 #harmonyos #手机 ubuntu24.04.1 互联网医院 运维监控 WireGuard 异地组网 Reactor反应堆 risc-v nacos Jenkins流水线 声明式流水线 SoC 原子操作 AXI 机器人操作系统 进程状态 僵尸进程 oneapi gitea TrinityCore 魔兽世界 mock mock server 模拟服务器 mock服务器 Postman内置变量 Postman随机数据 YashanDB 崖山数据库 yashandb OSB Oracle中间件 SOA 接口隔离原则 零售 deepseek r1 Hive环境搭建 hive3环境 Hive远程模式 ci/cd 本地部署AI大模型 Mac内存不够用怎么办 beautifulsoup STL termux IMX317 MIPI H265 VCU Ubuntu DeepSeek DeepSeek Ubuntu DeepSeek 本地部署 DeepSeek 知识库 DeepSeek 私有化知识库 本地部署 DeepSeek DeepSeek 私有化部署 P2P HDLC Zoertier 内网组网 visual studio d3d12 axure 轮播图 Nginx报错413 Request Entity Too Large 的客户端请求体限制 Spring Boot es 容器化 lsof linux命令 #apache #flink 直播 IIS Hosting Bundle .NET Framework vs2022 磁盘监控 IIS服务器 IIS性能 日志监控 db 云桌面 AD域控 证书服务器 服务器ssl异常解决 科勘海洋 数据采集浮标 浮标数据采集模块 深度优先 blender three.js 数字孪生 大学大模型可视化教学 全球气象可视化 大学气象可视化 华为OD机考 机考真题 需要广播的服务器数量 JavaWeb 回显服务器 Echo 前端项目部署 微前端 VMware安装Ubuntu Ubuntu安装k8s 教程 中兴光猫 换光猫 网络桥接 自己换光猫 fd 文件描述符 AList webdav fnOS Linux无人智慧超市 LInux多线程服务器 QT项目 LInux项目 单片机项目 设备树 环境搭建 免密 公钥 私钥 photoshop 进程等待 调试方法 Valgrind 内存分析工具 sublime text 自动化编程 Doris搭建 docker搭建Doris Doris搭建过程 linux搭建Doris Doris搭建详细步骤 Doris部署 VMware Tools vmware tools安装 vmwaretools安装步骤 vmwaretools安装失败 vmware tool安装步骤 vm tools安装步骤 vm tools安装后不能拖 vmware tools安装步骤 autogen openai Serverless 内存管理 学习路线 量子计算 飞书 #python3.11 #YOLO #目标检测 #YOLOv13 #python zotero 同步失败 VM搭建win2012 win2012应急响应靶机搭建 攻击者获取服务器权限 上传wakaung病毒 应急响应并溯源 挖矿病毒处置 应急响应综合性靶场 流式接口 echarts 信息可视化 网页设计 ecm bpm kamailio sip VoIP 远程看看 远程协助 半虚拟化 硬件虚拟化 Hypervisor web 规格说明书 设计规范 vCenter服务器 ESXi主机 监控与管理 故障排除 日志记录 工厂方法模式 MultiServerMCPC load_mcp_tools load_mcp_prompt 排序算法 TCP 多进程 TCP回显服务器 HTTP3 全双工通信 多路复用 实时数据传输 sonoma 自动更新 用户缓冲区 csrf firewall tar.gz tar.xz linux压缩 CTE AGE libtorch uboot 部署方案 IMX6ULL MAC地址 pyautogui QT 5.12.12 QT开发环境 Ubuntu18.04 ruby 漏洞报告生成 llamafactory 微调 Qwen BCLinux Arduino下载开发板 esp32开发板 esp32-s3 wpf dsp开发 MinerU Makefile Make 客户端和服务器端 BMS 储能 dataworks maxcompute TraeAgent pytorch3d #笔记 DeepSeek-R1 API接口 银河麒麟桌面操作系统 Kylin OS TCP服务器 qt项目 qt项目实战 qt教程 大数据平台 音乐库 飞牛 MQTT协议 消息服务器 联机 僵尸毁灭工程 游戏联机 开服 lighttpd安装 Ubuntu配置 Windows安装 服务器优化 框架 证书 签名 Scoket 套接字 log4j RAGFLOW 检索增强生成 文档解析 大模型垂直应用 Ubuntu Server Ubuntu 22.04.5 Redis Desktop Ubuntu共享文件夹 共享目录 Linux共享文件夹 图文教程 VMware虚拟机 macOS系统安装教程 macOS最新版 虚拟机安装macOS Sequoia debezium 数据变更 数据迁移 进程程序替换 execl函数 execv函数 execvp函数 execvpe函数 putenv函数 bpf bpfjit pcap openssh 编译器 I/O 设备管理 #tomcat #架构 #servlet #kali 离线部署dify DenseNet springboot远程调试 java项目远程debug docker远程debug java项目远程调试 springboot远程 bat mq 大模型技术 本地部署大模型 输入系统 AzureDataStudio compose webview rtc 蜂窝网络 频率复用 射频单元 无线协议接口RAN 主同步信号PSS LLaMA-Factory NLP Linux的进程概念 开发 软件安装 分类 mysql 8 mysql 8 忘记密码 #python #信息可视化 #大数据 #毕业设计 #Hadoop #SPark #数据挖掘 #n8n #n8n工作流 #n8n教程 #n8n本地部署 #n8n自动化工作流 #n8n使用教程 #n8n工作流实战案例 #php Invalid Host allowedHosts 做raid 装系统 BMC Flask Waitress Gunicorn uWSGI DeepSeek行业应用 Heroku 网站部署 nextjs reactjs 银河麒麟服务器操作系统 系统激活 黑客技术 存储数据恢复 raid5数据恢复 磁盘阵列数据恢复 freebsd 银河麒麟操作系统 联想开天P90Z装win10 ABAP 小番茄C盘清理 便捷易用C盘清理工具 小番茄C盘清理的优势尽显何处? 教你深度体验小番茄C盘清理 C盘变红?!不知所措? C盘瘦身后电脑会发生什么变化? client close 弹性服务器 netty 内网渗透 靶机渗透 EtherCAT转Modbus EtherCAT转485网关 ECT转485串口服务器 ECT转Modbus485协议 ECT转Modbus串口网关 ECT转Modbus串口服务器 跨域请求 pxe ECS服务器 raid 相机标定 USB转串口 lio-sam SLAM devmem cuda驱动 VUE Mysql 单用户模式 docker run 数据卷挂载 交互模式 实习 curl wget 读写锁 rancher VM虚拟机 桥接模式 windows虚拟机 虚拟机联网 web环境 冯诺依曼体系结构 gin WebRTC 带外管理 备份SQL Server数据库 数据库备份 傲梅企业备份网络版 gateway Clion Nova ResharperC++引擎 Centos7 远程开发 智能音箱 智能家居 CNNs 图像分类 Webserver 异步 ftp proto actor actor model Actor 模型 英语六级 键盘 支付 微信支付 开放平台 大版本升 升级Ubuntu系统 powerbi JAVA GKI KMI Mac部署 Ollama模型 Openwebui 配置教程 AI模型 uni-app x 根目录 #DevEco Studio #HarmonyOS Next GoogLeNet composer 金仓数据库 2025 征文 数据库平替用金仓 ssh远程登录 输入法 自学笔记 小米 澎湃OS GRE Linux系统编程 电子器件 二极管 三极管 #adb #数据库开发 #mysql #sql EMQX 通信协议 CPU file server http server web server uni-file-picker 拍摄从相册选择 uni.uploadFile H5上传图片 微信小程序上传图片 asm 端口测试 金仓数据库概述 金仓数据库的产品优化提案 RK3568 匿名FTP 邮件传输代理 SSL支持 chroot监狱技术 socket mysql离线安装 mysql8.0 英语 export env 变量 Linux find grep 泰山派 根文件系统 PATH 命令行参数 main的三个参数 react Native 源码软件 Ubuntu22.04 Alexnet qps 高并发 IO 本地知识库 Playwright MCP 自动化测试框架 WebFuture c EtherNet/IP串口网关 EIP转RS485 EIP转Modbus EtherNet/IP网关协议 EIP转RS485网关 EIP串口服务器 阿里云ECS 镜像下载 配置原理 mvc cocos2d 3dcoat 全栈 Lenovo System X GNOME 加密 观察者模式 小游戏 五子棋 移动端开发 Maven Linux的基础开发工具 数据库管理 nano direct12 Linux的进程控制 octomap_server 实战项目 入门 精通 electron iptables #数据结构 #c++ #链表 目标跟踪 OpenVINO 推理应用 virtualbox LInux 4 - 分布式通信、分布式张量 软路由 AOD-PONO-Net 图像去雾技术 开启关闭防火墙 服务 重构 c盘 磁盘清理 #计算机网络 #tcp/ip 大文件秒传跨域报错cors swoole C# MQTTS 双向认证 emqx 支持向量机 pyscenic 生信教程 大厂程序员 硅基计算 碳基计算 认知计算 生物计算 AGI 系统架构设计 软件哲学 程序员实现财富自由 多媒体 笔记本电脑 医疗APP开发 app开发 开发人员主页 软链接 硬链接 MAC 高德地图 鸿蒙接入高德地图 HarmonyOS5.0 PostgreSQL15数据库 编译 烧录 #分区 #mobaxterm #termius #electerm #tabby #termcc AnythingLLM AnythingLLM安装 telnet 远程登录 论文阅读 import save load 迁移镜像 命令模式 电脑操作 SSM 项目实战 页面放行 time时间函数 UDP的API使用 孤岛惊魂4 live555 源码剖析 rtsp实现步骤 流媒体开发 asp.net大文件上传下载 wsgiref Web 服务器网关接口 服务器主板 AI芯片 OD机试真题 华为OD机试真题 服务器能耗统计 Web应用服务器 deepseek-r1 大模型本地部署 券商 股票交易接口api 类型 特点 股票量化接口 股票API接口 文件存储服务器组件 web开发 教育电商 零日漏洞 CVE 网络IO 队列 数据库占用空间 磁盘IO iostat 医药 SPP 代理服务器 csrutil mac恢复模式进入方法 恢复模式 grep win向maOS迁移数据 SPI Termius Vultr 远程服务器 #nacos Ubuntu 24 常用命令 Ubuntu 24 Ubuntu vi 异常处理 青少年编程 编程与数学 思科实验 高级网络互联 coze扣子 AI口播视频 飞影数字人 coze实战 #debian vasp安装 宝塔面板访问不了 宝塔面板网站访问不了 宝塔面板怎么配置网站能访问 宝塔面板配置ip访问 宝塔面板配置域名访问教程 宝塔面板配置教程 rtp W5500 OLED u8g2 小智AI服务端 xiaozhi TTS 网站 软件开发 惠普服务器 惠普ML310e Gen8 惠普ML310e Gen8V2 DELL R730XD维修 全国服务器故障维修 #飞算Java炫技赛 #Java开发 Linux PID diskgenius 一切皆文件 导航栏 热键 文件权限 linux常用命令 ubuntu18.04 子网掩码 公网IP 私有IP ceph WebVM 集成 聚类 muduo库 ipv6 光猫设置 路由器设置 vscode1.86 1.86版本 ssh远程连接 ping++ VPS VMware创建虚拟机 动态规划 行情服务器 股票交易 速度慢 切换 java-zookeeper Putty 花生壳 messages dmesg conda配置 conda镜像源 sublime text3 容器清理 大文件清理 空间清理 机床 仿真 课件 虚拟现实 教学 课程 苹果 体验鸿蒙电脑操作系统 Windows电脑能装鸿蒙吗 CAN总线 linux安装配置 免费域名 域名解析 Sealos 国产数据库 瀚高数据库 下载安装 图片增强 增强数据 netlink libnl3 su sudo sudo原理 su切换 finalsheel 能效分析 Unlocker 打包工具 高效远程协作 TrustViewer体验 跨设备操作便利 智能远程控制 laravel googlecloud 聊天服务器 Socket 备选 调用 示例 rtsp服务器 rtsp server android rtsp服务 安卓rtsp服务器 移动端rtsp服务 大牛直播SDK LORA 主板 电源 智能电视 nosql 免费 GPU状态 网络带宽 问题排查 bug定位 缺陷管理 决策树 服务器租用 物理机 #合成孔径雷达 #GAMMA #InSAR 粘包问题 IP配置 netplan jdk11安装 jdk安装 openjdk11 openjdk11安装 mac cocoapods macos cocoapods elementui 若依框架 进程创建 进程退出 #conda efficientVIT YOLOv8替换主干网络 TOLOv8 飞牛nas fnos deployment daemonset statefulset cronjob 电视剧收视率分析与可视化平台 post.io 企业邮箱 搭建邮箱 DrissionPage 链表 迭代器模式 dnn vb 个人博客 filezilla 无法连接服务器 连接被服务器拒绝 vsftpd 331/530 同步 备份 建站 服务器时间 充电桩 欧标 OCPP ECT转Modbus协议 EtherCAT转485协议 ECT转Modbus网关 服务器部署 本地拉取打包 deepseak 豆包 KIMI 腾讯元宝 宠物 免费学习 宠物领养 宠物平台 openvino 分析解读 mm-wiki搭建 linux搭建mm-wiki mm-wiki搭建与使用 mm-wiki使用 mm-wiki详解 Linux的权限 shell脚本免交互 expect linux免交互 lvm Maxkb RAG技术 GRANT REVOKE Python学习 Python编程 端口 原创作者 vscode-server K8S k8s管理系统 IPv6测试 IPv6测速 IPv6检测 IPv6查询 linux子系统 忘记密码 本地环回 bind 服务注册与发现 xfce 图论 hibernate vSphere vCenter java-rocketmq 创意 社区 ardunio BLE 温湿度数据上传到服务器 Arduino HTTP 网络原理 光电器件 LED 雾锁王国 序列化反序列化 EMUI 回退 降级 java毕业设计 微信小程序医院预约挂号 医院预约 医院预约挂号 小程序挂号 搜狗输入法 ai编程 #vue.js #ansible #role #galaxy #ansible-galaxy 烟花代码 烟花 元旦 copilot scapy Docker快速入门 docker安装mysql win下载mysql镜像 mysql基本操作 docker登陆私仓 docker容器 deepseek与mysql TiDB测试集群 2025一带一路金砖国家 金砖国家技能大赛 技能发展与技术创新大赛 首届网络系统虚拟化管理与运维 比赛样题 openGauss SVN Server tortoise svn 雨云服务器 能力提升 面试宝典 IT信息化 VS Code ueditor导入pdf ueditor导入ppt 腾讯云服务器 轻量应用服务器 linux系统入门 slave js逆向 进度条 #vscode #编辑器 #ide #AI #MCP VR手套 数据手套 动捕手套 动捕数据手套 玩机技巧 软件分享 软件图标 keepalived 云盘 安全组 #电脑 #经验分享 SSH 密钥生成 SSH 公钥 私钥 生成 抗锯齿 流水线 脚本式流水线 redhat MinIO BitTorrent 搜索 huggingface dockercompose安装 compose.yml文件详解 dockercompose使用 PCB fork 进程管理 mcp-server #bright data #kubernetes #数据结构 网卡的名称修改 eth0 ens33 ssrf 失效的访问控制 junit 多个客户端访问 IO多路复用 TCP相关API 移动云 windwos防火墙 defender防火墙 win防火墙白名单 防火墙白名单效果 防火墙只允许指定应用上网 防火墙允许指定上网其它禁止 Dedicated Host Client 无头主机 DOIT 四博智联 access blocked 破解 gradle 快速入门 #默认分类 基础环境 Cookie 权限命令 特殊权限 wait waitpid exit Tabs组件 TabContent TabBar TabsController 导航页签栏 滚动导航栏 systemctl Navigation 路由跳转 鸿蒙官方推荐方式 鸿蒙原生开发 桶装水小程序 在线下单送水小程序源码 桶装水送货上门小程序 送水小程序 订水线上商城 CUDA Toolkit CentOS 影刀 #影刀RPA# MDK 嵌入式开发工具 产测工具框架 管理框架 ubuntu24 vivado24 k8s集群资源管理 实战案例 富文本编辑器 solr 源代码 高可用 openresty registries HBase分布式集群 HBase环境搭建 HBase安装 HBase完全分布式环境 麒麟kos 网络检测 ping pi0 lerobot aloha act 人工智能作画 rtsp转rtmp 海康rtsp转rtmp 摄像头rtsp到rtmp rtsp转发 rtsp摄像头转rtmp rtsp2rtmp #stm32 #单片机 #freeRTOS 游戏机 DigitalOcean GPU服务器购买 GPU服务器哪里有 GPU服务器 显示管理器 lightdm gdm hugo 繁忙 解决办法 替代网站 汇总推荐 AI推理 banner WebServer 蓝桥杯C++组 Qualcomm WoS QNN AppBuilder 三次握手 caddy 自定义登录信息展示 motd 美化登录 asp.net bootstrap 鼠标 Web测试 RustDesk 搭建服务器 材质 贴图 #阿里云 Bluedroid 信号 小艺 Pura X cmos vpn 高级IO epoll 用户管理 杂质 leetcode ebpf #VMware #虚拟机 #OCCT #Qt Linux环境 GRUB引导 Linux技巧 anythingllm open-webui docker国内镜像 fabric 回归 network NetworkManager ROS1/ROS2 dockerfile Wayland #安全 #nginx #web安全 #udp #网络协议 #web安全 #网络安全 #渗透测试 #计算机 #转行 #职场发展 #干货分享 #arm开发 #嵌入式软件开发 干货分享 黑客工具 密码爆破 加解密 Yakit yaklang oracle fusion oracle中间件 站群服务器 支付宝小程序 云开发 tengine web负载均衡 WAF proteus #其他 基础入门 Linux awk awk函数 awk结构 awk内置变量 awk参数 awk脚本 awk详解 线程安全 分布式总线 EulerOS 版本对应 Linux 发行版 企业级操作系统 RHEL 开源社区 #rockylinux #rhel #操作系统 #系统安装 TCP协议 并查集 问题解决 lb 协议 手机 pavucontrol 蓝牙耳机 WIFI7 无线射频 高通 射频校准 射频调试 射频匹配 #经验分享 #chrome #redis #缓存 #macos 阻塞队列 生产者消费者模型 服务器崩坏原因 NAT转发 NAT Server UDP Spring Security 高效日志打印 串口通信日志 服务器日志 系统状态监控日志 异常记录日志 IDEA sqlite3 skynet AimRT qwen2vl 矩池云 数据下载 数据传输 accept rtmp 访问公司内网 uv alias unalias 别名 termius iterm2 HarmonyOS NEXT 原生鸿蒙 Bandizip Mac解压 Mac压缩 压缩菜单 AppLinking 应用间跳转 文件清理 webgis cesium 程序地址空间 分布式锁 #机器人 多路转接 cd 目录切换 AI Agent 字节智能运维 CLion NLP模型 工具分享 Trae叒更新了? proxy_pass 触觉传感器 GelSight GelSightMini GelSight触觉传感器 BiSheng Jenkins 配置凭证 #openssh #电脑上不了网 #IP设置 #网卡驱动 #路由器设置 #wifi设置 #网络防火墙 #无法连接到这个网络 #网络协议 #ip #gpt #chatgpt WinRM TrustedHosts 并集查找 换根法 树上倍增 jetty undertow MI300x 宕机切换 服务器宕机 执法记录仪 智能安全帽 smarteye 像素流送api 像素流送UE4 像素流送卡顿 像素流送并发支持 Qt QModbus AWS 环境 非root eventfd 高性能 MateBook MQTT Broker GMQT 算法协商 故障排查 ESP8266简单API服务器 Arduino JSON siteground siteground安装wp 一键安装wordpress 服务器安装wordpress 国产芯片 微信分享 Image wxopensdk trea idea 强制清理 强制删除 mac废纸篓 多产物 终端 空间 查错 cpolar 影视app 红黑树 CKEditor5 应急响应 CTF 系统升级 16.04 #矫平机 #校平机 #铁 #钢 IO模型 浏览器开发 AI浏览器 软负载 钉钉 端口聚合 win11 windows11 UFW 静态IP 需求分析 知行EDI 电子数据交换 知行之桥 EDI hosts hosts文件管理工具 数字比特流 模拟信号 将二进制数据映射到模拟波形上 频谱资源 振幅频率相位 载波高频正弦波 nacos容器环境变量 docker启动nacos参数 nacos镜像下载 中文输入法 简单工厂模式 工作流自动化 AI智能体 线程同步与互斥 #database #爬虫 Radius webstorm code-server 软件定义数据中心 sddc XFS xfs文件系统损坏 I_O error MNN iDRAC R720xd Erlang OTP gen_server 热代码交换 事务语义 算力 技术共享 西门子PLC 通讯 超融合 代码托管服务 数字证书 签署证书 华为OD 可以组成网络的服务器 实时云渲染 云渲染 3D推流 anonymous 视频服务器 funasr asr 语音转文字 Java 日志框架 Log4j2 Logback SLF4J 结构化日志 企业级应用 BIO Java socket Java BIO Java NIO Java 网络编程 pve 科研绘图 生信服务器 软件高CPU占用 ProcessExplorer Process Hacker System Informer Windbg 线程的函数调用堆栈 #哈希算法 #散列表 #ai #AI编程 服务网格 istio 自定义shell当中管道的实现 匿名和命名管道 Bluetooth 配对 C/C++ 底层实现 对话框showDialog showActionMenu 操作列表ActionSheet CustomDialog 文本滑动选择器弹窗 消息提示框 警告弹窗 嵌入式软件 RTOS CMake 自动化编译工具 Bilibili B站 HDC2025 HarmonyOS 6 #信息可视化 #qml #qt CAD瓦片化 栅格瓦片 矢量瓦片 Web可视化 DWG解析 金字塔模型 通用环境搭建 rxjava #部署配置docker #容器化 #kubernetes VPN wireguard c/s 事件驱动 vue在线预览excel和编辑 vue2打开解析xls电子表格 浏览器新开页签或弹框内加载预览 文件url地址或接口二进制文档 解决网页打不开白屏报错问题 风扇散热策略 曙光 海光 宁畅 中科可控 独立服务器 #RAG springcloud 查看显卡进程 fuser 互联网实用编程指南 CodeBuddy首席试玩官 时序数据库 iotdb mysql8.4.5 #Dify 恒玄BES 影刀证书 分享 scala #云原生 #阿里云 网络犯罪 人工智能 opcua opcda KEPServer安装 KingBase 流量运营 联网 easyconnect 代理 错误代码2603 无网络连接 2603 #STC8 #STM32 MCP 服务器 JADX-AI 插件 EF Core 客户端与服务器评估 查询优化 数据传输对象 查询对象模式 aiohttp asyncio gpu alphafold3 KingbaseES #aws #搜索引擎 #elasticsearch #全文检索 regedit 开机启动 多层架构 解耦 safari etl 软件卸载 系统清理 子系统 wifi驱动 ps命令 本地不受DeepSeek Windows应急响应 webshell 网络攻击防御 网络攻击 恢复 nvm安装 #Linux #Ubuntu #ubuntu24 #ubuntu2404 #ubuntu安装 #sudo #前端 jina WebUI DeepSeek V3 容器技术 Web3 Telegram 5分钟快速学 docker入门 代理配置 企业级DevOps AI控制浏览器 Browser user Cilium 进程操作 理解进程 线性代数 asi_bench AI agent SEO 社交电子 代码规范 负载测试 OpenManage cordova 跨域开发 SpringBoot 视频直播物理服务器租用 物理服务器 物理机租用 selete 捆绑 链接 谷歌浏览器 youtube google gmail DIFY fonts-noto-cjk retry 重试机制 Office 顽固图标 启动台 端口开放 系统完整性 越狱设备 PDF 图片 表格 文档扫描 发票扫描 GDB调试 Ubuntu环境 四层二叉树 断点设置 南向开发 北向开发 MVVM 鸿蒙5.0 备忘录应用 #技能认证 #macos26 #启动台 docker部署Python 网易邮箱大师 机械臂 红黑树封装map和set ubantu sql注入 NAT 系统架构设计师 机床主轴 热误差补偿 风电齿轮箱 故障诊断 物理-数据融合 预测性维护 #网络通信 #Socket #飞书 RTMP 应用层 zerotier 充电桩平台 充电桩开源平台 RNG 状态 可复现性 随机数生成 机器人仿真 模拟仿真 CSDN开发云 集群 元服务 应用上架 Attention 跨平台 提示词 broadcom 时间轮 coffeescript 解决方案 FCN requests python库 Searxng #nginx #性能优化 Excel转json Excel转换json Excel累加转json python办公 服务器部署ai模型 SysBench 基准测试 服务器正确解析请求体 云耀服务器 浏览器自动化 海康 流量 Windows 11 重装电脑系统 报警主机 豪恩 VISTA120 乐可利 霍尼韦尔 枫叶 时刻 参数服务器 分布式计算 数据并行 哥sika webserver webgl MacOS录屏软件 CentOS Stream qtcreator 命令键 C++11 lambda 包装类 HarmonyOS SDK Map Kit 地图 云解析 云CDN SLS日志服务 云监控 nmcli 效率 #计算机网络 #网络攻击模型 #sql #学习 #自动化测试 #软件测试 k8s资源监控 annotations自动化 自动化监控 监控service 监控jvm 达梦 DM8 docker search containerd 分布式数据库 集中式数据库 业务需求 选型误 ICMPv6 VAD 视频异常检测 VAR 视频异常推理 推理数据集 强化微调 GRPO 物理层 myeclipse #图像处理 #端口 Wi-Fi SSL 域名 直流充电桩 矩阵 电商平台 clickhouse 架构与原理 域名服务 DHCP 符号链接 配置 shard rtcp AI导航站 cangjie openlayers bmap tile server 泛微OA #美食 #django #flask #node.js 魔百盒刷机 移动魔百盒 机顶盒ROM macOS 玩游戏 nginx默认共享目录 Linux指令 路径规划 English arkts arkui infini-synapse FreeFileSync 定时备份 #http #小程序 #jvm 李心怡 docker 失效 docker pull失效 docker search超时 改行学it 最新微服务 #mcp #浏览器自动化 #vue.js 根服务器 IPMITOOL 硬件管理 搭建个人相关服务器 互信 云服务器租用 udp回显服务器 zipkin 开闭原则 navicat Ark-TS语言 极限编程 实时日志 logs clipboard 剪贴板 剪贴板增强 ohmyzsh skywalking #comfyui dock 加速 ubuntu 18.04 智能问答 Milvus mapreduce 定义 核心特点 优缺点 适用场景 微信自动化工具 微信消息定时发送 实时语音识别 流式语音识别 #神经网络 #时序数据库 #物联网 #iotdb #重构 无法解析服务器的名称或地址 压测 查询数据库服务IP地址 SQL Server 相差8小时 UTC 时间 工业4.0 jQuery 事件分析 边缘服务器 利旧 AI识别 统信uos 转流 rtsp取流 rtmp推流 #c# #OPCUA #系统架构 #数据库架构 #安全架构 连接失败 Mosquitto 模拟实现 历史版本 下载 ollama下载加速 接口返回 Github加速 Mac上Github加速 Chrome浏览器插件 bert Ubuntu 24.04 搜狗输入法闪屏 Ubuntu中文输入法 学习笔记 tvm安装 深度学习编译器 Eigen HarmonyOS 5开发环境 FreeLearning vmvare linux上传下载 docker命令大全 集群管理 usb typec 扩展错误 vue2 敏捷开发 PP-OCRv5 ubuntu20.04 OCR NGINX POD #毕设 #租房管理系统 #论文 #自然语言处理 #语言模型 #目标跟踪 h.264 chfs ubuntu 16.04 授时服务 北斗授时 OpenAI 机架式 IDC #统信uos macbook 手动分区 android-ndk Windows Hello 摄像头 指纹 生物识别 九天画芯 铁电液晶 显示技术 液晶产业 技术超越 视频号 黑马 苍穹外卖 黑屏 Win10修改MAC 查看 ss WLAN uprobe scikit-learn isaacgym MobileNetV3 共享 设置 pow 指数函数 优化 概率与统计 随机化 位运算 几何计算 数论 ux URL sentinel 田俊楠 迁移指南 win服务器架设 windows server Async注解 动态域名 labview EasyTier #面试 #职场和发展 av1 电视盒子 合成模型 扩散模型 图像生成 threejs 3D gerrit RHCE 智能手表 Pura80 WATCH 5 安全整改 #华为云 #云服务部署 #搭建AI #Flexus X实例 #后端 #jdk #编程 proxy模式 交叉编译 #PG处理POI分类数据 #Java处理POI分类数据 #ApachePOI数据处理 #高德POI分类数据存储 #POI数据分类存储 #golang sysctl.conf vm.nr_hugepages AISphereButler hexo 火山引擎 站群 多IP Modbustcp服务器 FS100P 虚拟主机 物理机服务器 食用文档 #笔记 #intellij-idea #idea #intellij idea #需求分析 #区块链 #数据分析 Mac软件 亲测 redisson #WSL #截图工具 #驱动 #嵌入式 figma 语法 电子学会 集合 List 中文分词 #iotdb #时序数据库 #excel HAProxy 内网环境 业界资讯 xss Unity插件 安防软件 WINCC 制造 FreeRTOS 小亦平台 运维问题解决方法 gaussdb问题解决 物理服务器租用 #测评 #CCE #Dify-LLM #Flexus IT 护眼模式 mac完美终端 #进程优先级 #进程切换 #Linux调度算法 #寄存器 生活 idm #数据库 #c语言 #程序人生 Nuxt.js 佛山戴尔服务器维修 佛山三水服务器维修 cpp-httplib 静态NAT 开启黑屏 协作 java18 vsode 路由器 #Linux的基础IO ELF加载 医院门诊管理系统 仓库 N8N 视频会议 细胞分割 计数自动化 图像分析 vscode 1.86 能源 #unity #着色器 #iot whistle mujoco 重置密码 激光雷达 镭眸 #shell #脚本 #VNC xpath定位元素 linux 命令 sed 命令 WinCC OT与IT SCADA 智能制造 MES #Agent #智能运维 #AI开发平台 #AI工具链 #rocketmq #零拷贝 高效I/O authing 模板 泛型编程 #jenkins #spring boot Modbus TCP 基本指令 几何绘图 三角函数 UDS Bootloader #架构 #分布式 #单机架构 #微服务 #HTML #核心知识点 #web #知识点 #网页开发 #postgresql #GESP C++ #C++程序竞赛 #洛谷 #信奥赛 恒源云 TrueLicense 选择排序 地平线5 服务器托管 云托管 数据中心 idc机房 #MCP协议 #typescript #实战指南 #MCP服务器 NTP服务器 #tensorflow #pip #node.js watchtower homeassistant #AI编程 #低代码 #pycharm #Ollama #agent #向量库 #fastAPI #langchain #嵌入式硬件 lrzsz pikachu靶场 XSS漏洞 XSS DOM型XSS dos 批处理 日期 风扇控制软件 汽车 #CMake #Debian #CentOS 项目部署 OpenTiny #智能路由器 #NAT #信息与通信 #inlong #系统架构 tty2 mobaxterm #mc #服务器搭建 #mc服务器搭建 #mc服务器 cp 进度显示 #算法 #图论 #深度优先 责任链模式 #struts #echarts #强连通分量 #缩点 A2A 低成本 #VMware #VMWare Tool #腾讯云 #端口占用 #系统详情 fast 信奥 #react.js #javascript #React 编译器 #自动优化 #记忆化技术 #重新渲染优化 #https #虚拟地址空间 #进程 #fork #进程状态 #僵尸进程 #孤儿进程 #挂起 #RBAC桎梏 #角色爆炸 #静态僵化 #授权对象体系 #组织维度 #业务维度 #leetcode #elasticsearch guava SonarQube #开源 #AIGC #AI写作 #虚拟地址 #写时拷贝 #nginx配置 #nginx案例 #nginx详解 #tcp/ip #深信服运维安全管理系统 #远程命令执行漏洞 #intellij-idea #内网穿透 #音视频 #开源 #神经网络 #LoTDB #Apache IoTDB #DeepSeek #蓝耘智算 #android #缓冲区 #Linux #eureka #微信小程序 #gitlab #github #哈希表 #beego #go1.19 #beautifulsoup #list #stl #物联网 #Cookie #Session #HTTP dfs #github #Linux的进程间通信 #gitee #权限 #Linux的进程信号 #实时流处理 #设备故障预测 #Flink #配置教程 #入门教程 #安装教程 #图文教程 #raid #raid阵列 #深度学习 #概率论 #硬盘读取 #硬盘读取失败 #MAC电脑读取硬盘 #unix #使用教程 #ruby #Deepoc #具身模型 #具身模型开发板 #开发板 #机器人 #机械狗 #uni-app #微信小程序 #H5 #手机h5网页浏览器 #安卓app #苹果ios APP #手机电脑开启摄像头并排查 #矩阵 #限流算法 #Redisson #码上羽毛球馆 #羽毛球专业培训 #吴忠羽毛球学习俱乐部 #信息安全 #密码破解原理 #John the Ripper #Fun-ASR # 硬件配置 # 语音识别 #微信 #gpt #pytorch #1024程序员节 #应用推荐 #WiFi Explorer #工作流 #无人机 #边缘计算 #PyTorch #CUDA #cuDNN #C/C++ #Linux文件 #IO库 #Glibc #Google Play #Android出海 #APP出海 #游戏出海 #短剧出海 #海外短剧 #出海开发者 #moontv #oriontv #websocket #串口服务器 #工业级串口服务器 #串口转以太网 #串口设备联网通讯模块 #串口服务器选型 #bash #php #VS Code调试配置 #SaaS #spring cloud #sci #sci收录 #电脑 #word #计算机图形学 #C++ #MFC #podman #信号处理 #cpolar #国产操作系统 #Linux桌面系统 #统信 #银河麒麟 #国产硬件平台 #国产CPU #软件开发 #生产排产 #APS排产 #生产管理系统 #ai #YOLOv8 # 目标检测 # Docker镜像 #mybatis #mvc #vue #r语言 #智慧工地人员定位 #机器学习 #推荐算法 #网络防火墙阻止连接 #防火墙阻止连接网络 #阻止软件连接网络 #网络流量控制 #局域网连接 #允许程序访问网络 #RustFS #企业存储 #对象存储 #前端 #全栈 #日志分析 #UU远程 #flutter #交互 #OpenHarmony #跨平台开发 #训练营 #Linux开发 #系统编程 #文件IO #重定向 #运维开发 #autoware #opencv #dnn #其他 #libwebkit2gtk-4.1-0安装 #chatgpt #负载均衡 #bash #RPA #影刀RPA #AI工作流 #数学建模 #预测模型 #matlab #计算机视觉 #游戏 #Terraria #游戏开服 #arm #nvidia #cuda #mamba #wsl2 #环境配置 #Z-Image #图片大模型 #图片生成 #开源图片生成 #开源无限图片生成 #开源免费图片生成 #游戏引擎 #unity #ShaderGraph #图形 #SimpleWood #LLM #llama #RAGFlow #本地化部署 #芯片制造 #3D动画 #芯片动画 #半导体3D动画 #课程设计 #楼宇联盟 #楼宇运维 #权限提升 #azure #postgresql #AI电商客服 #蓝桥杯 #夏天云 #夏天云数据 #react native #react.js #wpf #clawdbot #信号处理 #opencv #策略模式 #材料工程 #密码学 #电容感应技术 #二代旋钮 #国内源 #openhands #AI 软件开发代理平台 #本地部署 #鸿蒙 #仓颉 #CVE-2017-7494 #linux永恒之蓝漏洞 #openvino #科技 #UWB #智能家居 #超宽带 #nvidia #显卡驱动安装 #嵌入式硬件 #ssl #fpga开发 #射频工程 #UltraScale+RF #rfdc #字符串 #保姆级教程 #strlen #strcpy #strcmp #sprintf #rs485 #pdf #AI赋能 #挖漏洞入门到精通 #挖漏洞 #挖漏洞赚钱 #挖漏洞的平台 #挖漏洞赚钱合法吗 #挖SRC漏洞 #网络安全挖漏洞 #内容管理系统 #网站管理系统 #企业建站 #企业网站建设 #CMS #网站建设 #网站制作 #语音识别 #lstm #京东云 #conda #3DGS #colmap #复现记录 #产品经理 #程序员 #stm32 #Linux时间同步 #手动同步 #mac mini #边缘算力 #知识库应用 #PATH #环境变量 #Lenyiin #vivado安装包 #鸿蒙NDK UI #关键帧动画 #中小企业进销存 #象过河软件 #库存预警 #多仓库管理 #五金建材进销存 #五金建材管理软件 #标尺 #标杆 #矿 #煤 #数据集 #鸿蒙 #随机森林 #遥感 #GEE #土地利用 #arm开发 #鸿蒙系统 #ios #iphone #ui #mysql #学习 #数据仓库 #jetson #PX4 #fastlio2 #ROS2 #生活 #rabbitmq #sqlite #个人开发 #JDK17 #jdk21 #java-ee #节点小宝 #远程访问 #NAS #网关 #工具 #Dynamics 365 #音频格式转换 #视频格式转换 #音视频格式转换 #格式转换软件 #生活 #claude #android studio #transformer #大模型学习 #大模型教程 #微服务 #高并发 #大流量 #微服务性能 #中台 #零售中台 #laravel #政务 #单元测试 #neo4j #etcd #经济学 #命令行参数 #z-image base整合包 #z-image整合包下载 #z-image生图 #z-image文生图 #z-image最新整合包 #ai文生图 #z image base #办公软件 #KMS激活 #rpa #AI自动化 #ssh #本地部署AI #AI工具 #个人AI # HiChatBox # 离线AI #脚本 #gitblit #数信院生信服务器 #r语言 #vmware #win11 for Arm #html5 #CP2K #量子化学 #eureka #驱动开发 #MMC #SDIO #SD #EMMC #Burpsuite #Burpsuite漏洞扫描 #Burpsuite网络抓包 #Burpsuite实战教程 #Burpsuite入门到精通 #Burpsuite安装教程 #Burpsuite渗透测试 #android studio #Android开发 #Android开发所属操作系统 #策略模式 #k8s #libusb #matlab #电路板上电子元件检测检测系统 #YOLOV8 #EasyConnect下载安装 #EasyConnect下载 #SSL VPN #远程办公 #驱动开发 #DIY机器人工房 #YOLO #运维 #服务器 #健康医疗 #知识图谱 #迭代加深 #支持向量机 #cpolar #轮廓检测 #模板匹配 #边缘检测 #人脸核身 #thinkphp #腾讯人脸核身SDK #ABAQUS #abaqus教程 #达索仿真 #qwen3-vl #rabbitmq #rknn-toolkit2 #1024程序员节 #考研 #AI大模型 #mybatis #mcu #壁画 #烟熏壁画 #壁画修复 #烟熏壁画修复 #壁画保护 #文物修复 #文物保护 #Ubuntu24.04LTS #向日葵 #远控端黑屏 #远控端不能登录 #nanobanana #AITOP100工具 #AI资讯 #AITOP100 #进程控制 #软件推荐 #ci/cd #Linux运行Win程序 #云计算 #devops #平面 #SEW变频器 #影刀rpa教程 #影刀rpa #教程 #信息与通信 #3d #visual studio #ocr #系统安全 #亚马逊申诉 #跨境电商 #亚马逊 #ubuntu升级 #远程更新 #缓存更新 #多指令适配 #物料关联计划 #Power Platform #源代码管理 #taro #海外服务器安装宝塔面板 #网络配置实战 #Web/FTP 服务访问 #计算机网络实验 #外网访问内网服务器 #Cisco 路由器配置 #静态端口映射 #网络运维 #SSH # 代理转发 # 跳板机 #公共MQTT服务器 #sv #芯片 #CosyVoice3 # IP配置 # 0.0.0.0 #golang #开源鸿蒙 #electron #wpf #arduino下载安装教程 #文件I/O #系统文件 #系统调用 #makfile #C++开发 #GMU Make #EtherCAT #速腾聚创3d激光雷达使用 #张量 #能源 #能源管理 #能源监测 #ue5 #自动驾驶 #游戏引擎 #开源软件 #html #课程设计 #css #大学生 #大作业 #二叉树 #深度优先搜索 #广度优先搜索 #DFS #BFS #ros2 #moveit #编译 #King Base #Portainer #远程 #ffmpeg #chrome devtools #xss #智慧工地人员定位系统 #信号产生 #键盘 #软件条件 #统一白名单控制 #旅游 #旅游攻略 #景区讲解 #航拍 #5A级景区 #全国旅行 #走遍中国 #游戏程序 #laya #edge #webrtc #实时音视频 #eclipse #音频策略 #Audio Policy #音频架构 #语言模型 #spring #智能仓储 #仓储未来 #自动化仓库 #设备选型 #堆垛机 #穿梭车 #AMR #学习方法 #远程工作 #c# #Linux用户管理 #权限控制 #用户管理 #Linux安全运维 #智能充电桩 #自动化枚举 #WinPEAS #bug #就业 #创业创新 #百度 #vllm #gradio #api #mineru2.7.1 #docker compose #新浪微博 #Bacteria #微信 #JSR-107 #java高级面试 #容量规划 #故障诊断 #成本控制 #pdf #bootstrap #html #商城 #面试 #gateway #epoll #高级IO #TFTP #客户端 #智能体搭建 #强化学习 #大模型工程师 #kmeans #聚类 #ocr #nivida #langchain #rag #ai agent #ai智能体 #ai开发 #智能体开发 #大模型AI #数学建模 #spark #hive #时间频率传递 #光学 #计量 #时间频率计量 #光频梳 #光学频率梳 #Linux的Ext系列文件系统 #flutter #cangjie #基础入门 #基础知识 #状态模式 #asp.net #迁移学习 #鸿蒙领航者计划 #SOEM #EtherCAT主站 #eclipse #qt #postman #深度相机 #realsense #vins #dify部署 #journalctl #aarch64-linux #arm交叉编译工具 #virtualenv #pyenv #多版本 #虚拟环境 #Anaconda #政务 #金融 #idapro下载 #共享内存 #System V #ubuntu22.04 #Fast_livo2 #生产者消费者模型 #线程同步 #阻塞队列 #k8s #AI转型 #数字化转型 #AI大模型工程师 #asp.net大文件上传 #asp.net大文件上传下载 #asp.net大文件上传源码 #ASP.NET断点续传 #asp.net上传文件夹 #asp.net上传大文件 #.net core断点续传 #华为od #华为OD机试真题 #华为OD机考 #华为OD上机考试 #华为OD机试双机位C卷 #华为OD机考双机位C卷 #计算机毕业设计 #计算机专业 #选题推荐 #源码分享 #计算机毕设 #Ping不通 #企业经营 #盈利 #销售额 #成本 #利润 #流动资产 #固定资产 #ext2文件系统 #硬件 #inode #单片机 #autosar #UOS #统信操作系统 #yum #zabbix #中间件 #kylin #NSP #下一状态预测 #aigc #YOLO26 #知识图谱 #pandoc #markdown #VSCode #MPE #mdev #udev #电脑网卡驱动下载 #节点小宝 #Linux网络 #协议定制 #JosnCpp库 #序列与反序列化 #毕业设计 #程序开发 #程序设计 #源码 #传感器 #霍尔电流传感器 #光伏逆变器 #线性回归 #回归 #模型 #训练 #vivado2020.2安装教程 #能源 #软件工程 #低代码平台 #低代码搭建OA系统方法 #低代码OA系统实用技巧 #低代码OA系统关键考量 #TPFLOW工作流引擎应用案例 #Gadmin低代码平台优势 #gadmin #论文阅读 #GPS #北斗 #定位系统 #lbssoft #gin #go # CSPDarknet #自动驾驶 #决策树 # PyTorch # CUDA #流量监控 #麦克风权限 #访问麦克风并录制音频 #麦克风录制音频后在线播放 #用户拒绝访问麦克风权限怎么办 #uniapp 安卓 苹果ios #将音频保存本地或上传服务器 #ppt #powerpoint #session #网络编程 #muduo #TcpServer #accept #高并发服务器 #ssl #黑客技术 #文件上传漏洞 #项目 #agi #RXT4090显卡 #RTX4090 #深度学习服务器 #硬件选型 #AI智能棋盘 #Rock Pi S #青少年编程 #递归 #Flutter运行到鸿蒙 #组合导航 #精准农业 #低成本组合导航 #MEMS #鸿蒙开平台应用 #lbsfoft #智慧景区 #microsoft #ODOO #开源ERP #ODOO社区版 #ODOO企业版 #版本控制 #Git入门 #开发工具 #代码托管 #备份 #松鼠备份 #数据备份 #自动备份 #中小企业 #防勒索 #python3.11 #FHSS #transformer #electron #录屏 #record #node #共绩算力 #模版 #函数 #类 #笔试 #零基础 #DSP #车载系统 #selenium #压力测试 #TCP协议 #日志 #池化技术 #影刀RPA #流程自动化 #智能运营 #效率提升 #mongodb #MacOS #ansible #adb #流程图 #论文阅读 #sentinel #hibernate #模拟退火算法 #教程 #扣子技能 #Agent Skills #Coze #智能手机 #USB共享 #共享上网 #手机热点 #C语言 #模数电 #spark #hadoop #空间隔离 #镜像仓库 #jmeter #无人机 #embedding #测试工具 #音视频 #ADS 自动化仿真 #ADS 程控 #ADS python #Windows 11 #TPM #Secure Boot #CPU #RAM #OOBE #网络验证 #安全漏洞 #智能路由器 #爬虫 #JS #智能手机 #vlan #独臂路由 #DHCP #网络安全 #信息安全 #powershell #windows日志 #无人机工地巡检 #挖掘机识别 #叉车识别 #立即下载 #智能硬件应用 #深度优先 #2025年终总结 #mtgsig #美团医药 #美团医药mtgsig #美团医药mtgsig1.2 #开发 #pytest #企业开发 #ERP #项目实践 #.NET开发 #C#编程 #编程与数学 #网站 #SpringBoot #DS随心转 #蓝牙 #LE Audio #BAP #tcpdump #低代码 #restful #AudioPolicy #APS #安卓音频 #mqtt #rust #汽车 #spring #SeaTunnel #DeepSeek #娱乐 #pyqt #CANN训练营 #vim #JSR-107 #缓存雪崩 #缓存击穿 #随机过期时间 #缓存高可用 #降级和熔断 #黑群晖 #无U盘 #纯小白 #北京百思可瑞教育 #百思可瑞教育 #北京百思教育 #Llama-Factory # 树莓派 # ARM架构 #Dify #轻量化 #低配服务器 #css3 #游戏服务器断线 #Miniconda #SSH #远程开发 #文件描述符 #RustDesk #远程控制 #Docker #云计算运维 #Ansible # 自动化部署 # VibeThinker #pxe #spring cloud #leetcode # IndexTTS # GPU集群 #dubbo #CVE-2025-61686 #漏洞 #路径遍历高危漏洞 #wordpress #risc-v #skills #软件需求 #AI论文写作 #学术写作工具 #AI模型应用 #论文降重优化 #个人开发 #数据分析 #gnu #parallel #aws #fastapi #蓝耘元生代 #媒体 #ollama #大模型基础 #简单的大模型部署 #实现ollama模型调用 #AI 聊天机器人 #零基础上手 Ollama体验 #vivado2023.2下载安装教程 #websocket #BI #BI选型思考 #kfifo #三层架构 #多端部署 #一多适配 #ArkTS #LED数码管生产厂家 #数码管 #LED数显屏 #七段数码管 #kvm #虚拟化 #hypervisor #FlashAttention #flash_attn #flash-attn #Transformer #FlashAttention2 #FlashAttention3 #milvus #软件 #win #edge #http #vuejs #有限元仿真 #Abaqus软件教程 #2026降AI工具第一梯队 #降AI率工具 #降AI工具TOP5 #MarkText #mark text #marktext #marktext下载 #marktext 中文 #人形机器人 #鸿蒙6.0 #HarmonyOS6.0 #V2装饰器 #once #mid360 #livox #单例模式 #线程安全 #重入问题 #死锁 #懒汉模式 #饿汉模式 #arcgis #多线程 #pthread #线程互斥 #k8s集群 #经济学 #springboot项目 #java源代码 #java毕业设计 #校园资料分享平台 #LazyForEach #懒加载 #分页渲染 #list #csp #提高组 #csp-s #真题 #谐音替换 #组件 #分布式数据库 #云端 #云DB #ComfyUI #文生图工作流 #AI作画 #Leecode #Fcitx #iot-tree #IIoT #rpc #昇腾平台 #大模型推理调优 #SGLang #利用率优化 #vllm-ascend #Atlas 800T A2 #NPU #排序算法 #Docker MySQL部署 #MySQL 8.0容器化 #MySQL远程访问配置 #Docker数据卷挂载 #cnn #WinSCP 下载安装教程 #SFTP #FTP工具 #服务器文件传输 #空间计算 #appium #AINode #.net #.netcore #Python环境管理 #Miniconda教程 #量子计算 #数码相机 #安全性测试 #Dockerfile #镜像 #命令行 #快捷键 #SECE-I串口设备 #半导体SECS-I串口协议 #上位机SECS #上位机SECS协议串口设备 #PLC半导体SECS协议 #PLC与SECS协议 #AutoGen #AutoGenStudio #论文降重 #论文降AI率 #降AI技巧 #论文修改 #microsoft #n8n #开源AI #自动化工具 #Docker #服务器部署Docker #云主机部署Docker #媒体 #小程序 #OkHttp #Retrofit #CSDN #Nginx #DNS #AI-native #语音识别 #paddlepaddle #毕业论文 ## 论文写作 ##科研干货 ##论文技巧 #国企混改 #央企混改 #国企混改咨询 #测试用例 #创业创新 #aippt #ChatPPT #微信公众平台 #UI #AgentSkills #人机交互 #AI生成图像 #生成图像检测 #AIGC检测 #生成图像检测评测 #redis-cluster #gossip协议 #缓存集群 #ib #Ubuntu22.04 #SAP #安卓屏幕触控 #屏幕触控 #Android touch #安卓系列 #生产服务器问题查询 #日志过滤 #好物分享 #实时检测 #卷积神经网络 #页面 #分布式 #Raft #国产 #Windows #MySQL #数据库管理 #服务器运维 #环境搭建 #图像处理 #学术会议 #生物医学 #医学 #练习 #基础练习 #OOP #restful #ajax #程序人生 #博客之星 #缓存 #护网行动 #平板电脑 #智慧城市 #n8n短视频工作流 #n8n监控 #n8n自动化 #n8n爆款公众号 #nas #内网穿透 #ngrok #ddos #投票 #微信投票 #线上投票 #投票系统 #银河麒麟高级服务器操作系统安装 #银河麒麟高级服务器V11配置 #设置基础软件仓库时出错 #银河麒高级服务器系统的实操教程 #生产级部署银河麒麟服务系统教程 #Linux系统的快速上手教程 #ESP32 # OTA升级 # 黄山派 #大模型部署 #mindie #大模型推理 # IndexTTS 2.0 # 自动化运维 #Jetty # CosyVoice3 # 嵌入式服务器 #unity3d #服务器框架 #Fantasy #myeclipse #Langchain-Chatchat # 国产化服务器 # 信创 #homelab #Lattepanda #Jellyfin #Plex #Emby #Kodi #wsl #jupyter #LoRA # RTX 3090 # lora-scripts #流媒体 #飞牛NAS #监控 #NVR #EasyNVR #ceph #MCP #cursor #算力一体机 #ai算力服务器 #gmssh #宝塔 #1panel #内存接口 # 澜起科技 # 服务器主板 #新媒体运营 #ARM架构 #x86_64 #数字人系统 #温湿度二氧化碳三合一传感器 #LORA三合一传感器 #LORA室内环境传感器 #NDIR 二氧化碳检测 #无线环境监测 #低功耗广域网环境监测 #Syslog #系统日志 #日志监控 #编辑器 #ddos #WAF #D盾 #哥斯拉 #多进程 #python技巧 #系统安全 #UDP服务器 #recvfrom函数 #poll #旅游 #DataGrip #高可用 #rust #VoxCPM-1.5-TTS # 云端GPU # PyCharm宕机 #typescript #esb接口 #走处理类报异常 #bug菌问答团队 #小灰一键去水印 #试卷网资源下载器 #小学试卷下载器 #初中试卷下载器 #高中试卷下载器 #学习试卷下载器 #prompt #lua #硬件工程 #宠物 #软件构建 #通信工程师 #初级通信工程师 #中级通信工程师 #高级通信工程师 #软考 #通信工程师备考资料 #通信 #FreeRTOS #移植 #API #函数使用 #todesk #fpga开发 #应急救援平台 #virglrenderer #virtio-gpu #进程等待 #wait #waitpid #pygame #easyconnect #软件下载安装 #Xshell #Todesk #slam #fast_lio2 #建图 #重定位 #鸿蒙2025领航者闯关 #鸿蒙6实战 #rk3588 #main函数参数 #怪奇物语 #windows优化工具 #禁止win11更新 #hibernate #gitcode #流程图 #信号机制 #团队开发 #同花顺 #进程替换 #systemd #esp32 #jtag #串口 #服务发现 #logback #npm #团队领导 #团队开发 #蓝桥杯 #ELF #静态链接 #Stage模型 #鸿蒙开发 #Linux指令 #企业转型 #Rag #oracle #uni-app #视频 #deepseek #llm #docker镜像加速 #docker镜像 #wps #软件安装 #pencil #pencil.dev #设计 #无名杀 #MobaXterm #文本编辑 #远程管理 #scp #408真题解析 #计算机考研 #软件需求 #ethernet-phy #设备树 #Tracker 服务器 #响应最快 #torrent 下载 #2026年 #Aria2 可用 #迅雷可用 #BT工具通用 #scrapy #p2p #nosql #nosql数据库 #系统降级 #华为P30 #交换机 #shell #命令行解释器 #Linux系统编程 #后端 #Java编程 #Java面试 #Java程序员 #学工管理系统 #学工一体化平台 #学生综合管理系统 #学生工作管理平台 #学工软件厂家 #学工系统解决方案 #自友学工管理系统 #信息收集 #Kali Linux #黑客 #黑客工具 #集群 #部署 #Claude Code #实战 #Skill #PuTTY #可信计算技术 #国产数据库 #KingbaseES部署工具 #嵌入式系统开发 #加速 #gitea #numpy #uboot #docker镜像源 #docker镜像下载 #docker国内加速 #mojo #网络示识别 #网络未识别问题 #CSDN成长记录 #MC.JS #WebGL #WebAssembly #我的世界 #前端技术 #ROS #nomachine #远程连接 #AI智能体 #自定义插件 #API集成 #工具调用 #大模型落地 #内建命令 #金仓数据库 #key #API调用 #量子计算 #three.js #pycharm #域名 #url #网址 #浏览器 #链接 #超链接 #AI大模型开发 #大模型应用 #AI测试 #AI智能体 #ci/cd #gitlab #Arbess #docker-compose #项目部署 #制造 #EAP #MES #facebook #twitter #oneapi #JS逆向 #Python学习 #Python零基础 #看漫画学Python #PythonPDF教程 #集合框架 #信号 #非阻塞等待 #流量运营 #转账 #提现 #geant4 #AutoDL #crontab #定时任务 #Cron 表达式 #Shell 脚本 #实战案例 #概率论 #數學 #物理 #Pixhawk #电机 #电调 #CCE高可用 #云服务器单机部署 #DeepSeek商用开通 #贪心算法 #组合模式 #Java #JVM #类加载 #类初始化 #双亲委派 #内存管理 #存储 #网盘 #openlist #WebDAV #正则表达式 #视觉大模型 #信息收集 #亮数据 #城市交通 #鸿蒙pc #lycium_plusplus #AI应用开发工程师 #Trae #接单 #平台 #Topcoder #vp9 #论文笔记 #量化 QMT Ptrade #道德经 #iphone #宽度优先 #CVE-2025-27817 #CVE-2025-27818 #CVE-2025-27819 #远程代码执行 #网络原理 #IP #协议 #OSI七层网络模型 #经管科研 #编程开发 #架构师 #人形机器人 #web3 #去中心化 #ERC标准 #EIP #Linux应用层开发 #应用层开发 #dup函数 #js #deepseek api #AI论文写作 #论文创作提效 #AI写作功能解析 #汇编 #FOC #电机控制 #AIGC实战 #多模态人工智能 #生成对抗网络 #excel #notepad++ #像素流 #Reactor #ET模式 #非阻塞 #html5 #免费教程 #前端开发 #手搓音乐播放器 #代码案例 #mcu #https #gpu算力 #Puppet # IndexTTS2 # TTS #esp32教程 #FASTMCP #turn #java大文件上传 #java大文件秒传 #java大文件上传下载 #java文件传输解决方案 #漏洞修复 #IIS Crypto #扩展屏应用开发 #android runtime #mariadb #kylin #银河麒麟服务器系统 #远程桌面 #openEuler #欧拉 #FaceFusion # Token调度 # 显存优化 #主从式Reactor #仿muduo高并发服务器 #linux操作系统 #http网络协议 #epoll eventfd #timerfd pthread #C++ UA Server #SDK #Windows #oracle #rtmp #新人首发 #虚幻 #ue5 #vnstat #IntelliJ IDEA #Spring Boot #serverless #集成测试 #fabric #SSH跳转 #GPU服务器 #倍增 #树链剖分 #邻接表 #Minecraft #Minecraft服务器 #PaperMC #我的世界服务器 #c #spring ai #oauth2 #Gateway #认证服务器集成详解 # Triton # 高并发 #asp.net #sglang #jquery #光伏 #光伏产业 #梁辰兴 #新材料 #新突破 #目标检测 #星图GPU #AI应用 #ambari #Shiro #反序列化漏洞 #CVE-2016-4437 #51单片机 #SSH Agent Forwarding # 容器化 #fastmcp #SA-PEKS # 关键词猜测攻击 # 盲签名 # 限速机制 #SIP服务器 #语音服务器 #VoIP #SIP协议 #b树 #MinIO服务器启动与配置详解 #go #制造 #HeyGem # 服务器配置 # GPU #求职招聘 #以太网 #交换机 #InfluxDB #与TimescaleDB #的适用场景 #代码 #新手 #java基础 #react #blob #毕设定制 #毕设代做 #程序定制 #web3 #CNSH 中文编程 #notion #UID9622 #线程操作 #进程地址空间 #系统调用号 #线程崩溃 #嵌入式系统开发 #HarmonyOS6.0 #Resuable #组件复用 #ArkUI #Laplace #Laplace边缘提取 #教程4 #mac使用z-image #z-image mac怎么安装 #mac怎么使用z-image #mac ai生图 #mac电脑使用z-image #macbook 生图 #线程 #资源划分 #5G #OBS #抖音直播 #PD虚拟机 #mac直播 #远程控制工具 #ffmpeg #交叉编译 #鸿蒙PC #KingBaseES #z-image 苹果芯片版 #z-image mac #z-image-turbo整合 #z-image苹果电脑怎么安装 #macbook用z-image #mac电脑安装z-image #z-image mac版 #WSL2 #旅游网站 #旅游管理系统 #云南旅游网站 #VPN #网络连接 #代理服务器 #串口 #同步通信 #异步通信 #通信方式 #联合体 #共用体 #枚举 #typedef #位运算 #app #arkui #组件库 #动环监控系统 #机房管理系统 #机房运维 #豆包 #sql注入 #AI技术 #gaussdb #opengauss #动态库 #静态库 #线程池 #单例模式 #LINUX系统 #TCP/IP #三次握手 #网络排查 #openwrt #环境 #hosts #查重率 #降AIGC率 #ubuntu24 #workstation #25H2 #问题 #navicat #Python #Makefile #ros #水泥涵洞隧道基建缺陷病害 #全面对比评估分析 #gcc #数据治理概论 #数据治理框架 #数据战略规划 #数据采集 #数据存储 #数据治理价值评估 #脚本语言 #openstack #蓝耘容器云 #时钟中断 #缺页中断 #硬件中断 #软中断 #tornado #npm #System V信号量 #环形队列 #BIND #智能问数 #macOS #序列号 #机型 #查看 #对应 #macbook #iRMB #视觉检测 #Kali Linux #python安装 #Python卸载 #PyCharm激活 #浏览器开发 #chrome devtools #指纹浏览器 #maven #KingbaseES #电科金仓 #Ubuntu安装 #Oracle #鸿蒙PC #鸿蒙6.0 #响应式布局 #软件工程 #前端框架 #orb-slam3 #D435i #鸿蒙系统 #性能优化 #第三方检测 #软件检测 #检测方法 #初学者 #编译原理 #语法分析 #镜像源 #Antigravity #Latex #论文写作 #AI顶会 #问题解决 #ur5 #robotiq #机械臂 #gazebo #仿真 #Excel技巧 #图表设计 #职场技能 #办公技巧 #vue上传解决方案 #vue断点续传 #vue分片上传下载 #vue分块上传下载 #vue分割上传下载 #vue上传文件夹 #vue大文件上传下载 #Sequoia #哈希算法 #sqlite #c++20 #零售 #二叉树基础算法 #力扣 #tcp #SMP(软件制作平台) #应用系统 #EOM(企业经营模型) #三层交换机 #TCP #socket #智能控制 #lstm #HarmonyOS6 #智感握持 #状态模式 #ros #nlp #图像创意赛 #现金奖励 #宠物爱心组织 #anaconda #qemu #QEMU_EFI.fd #aarch64 #cortex-a72 #Ubuntu 20.04 安装 #编译避坑 #PX4+Gazebo #Mid360 #设计规范 #放大电路 #V4L2 #Zero-Copy #DMA-BUF #DMA-FD #代理模式 #责任链模式 #Simulink #Matlab #AUTOSAR #SWC #runnable #arm64 #x64 #ollama #linux设计思想 #用户体验 #进程调度 #东方仙盟商业开发 #东方仙盟 #仙盟创梦IDE #东方仙盟自动化 #Apache IoTDB #TimechoDB #Kubernetes #中级 #系统集成项目管理工程师 #IoTDB #改行学it #边缘计算 #AI推理 #低延迟网络 #VON #Kanass #国产开源项目管理工具 #一文上手 #busybox #WordPress #性能测试 #管道Pipe #进程池 #OpenJDK #OpenJDK部署 #docker部署OpenJDK #OpenJDK部署文档 #OpenJDK部署教程 #Claude Code #Gemini #Autoconf #Cmake #springboot #visual studio #Ubuntu #麒麟2503 #统信UOS #国产化 #MID360 #激光雷达 #Linux内核 #调度算法 #手持气象站 #电子气象仪 #青少年编程 #讲义 #教材 #考级 #竞赛 #Autoware #Nvidia #索引 #分片 #es6 #AIGC #工作流构建 #意图识别工作流 #gpt-3 #文心一言 #链表 #Hot100 #求职面试 #RH850-U2 TRAP #RH850-U2 TRAP指令 #TRAP指令 #Armbian #DooTask #etl #etl工程师 #火山引擎 #Mamba #网路规划设计 #IPsecVPN #网络工程师 #eNSP综合实验 #医院网络规划 #网络规划 #QGC安装 #m3u8 #视频播放报错 #M3U8文件 #文件异常 #video视频组件 #第三方视频播放器 #nltosql #检索增强 #Cgroup #Kontakt #Kontakt8 #Kontakt7 #Kontakt6 #Kontakt音源 #音源下载 #聊天小程序 #arbess #argocd #elk #系统监控 #性能优化维监 #CentOS 7 #yum 源 #国内镜像源 #阿里云镜像 #EPEL #Base 源 #Linux 运维 #express #线程状态 #配置 #Hudi #DoS攻击实操 #hping3 #RTC #ai写作 #RAG #检索增强生成 #miniconda #type-challenges #nas #机顶盒 #Android #逻辑回归 #scikit-learn #matplotlib #AI #模拟 #路由器 #YOLOv13创新改进点 #visual studio code #第三方检测 #ai大模型 #事务ACID #Commit Rollback #数据库入门 #后端开发 #数据一致性 #devops #显卡驱动 #langchain4j #Clawdbot #Moltbot #小智AI #歌曲播放 #2026年美赛C题代码 #2026年美赛 #bootstrap #测试工具 #windows服务 #uv #铬锐特 #胶粘剂 #UV胶水 #光固化胶水 #紫外线胶水 #短剧H5 #短剧H5开发 #短剧平台开发 #短剧 #短剧h5 #h5开发 #短剧系统开发 #数码相机 #分类 #爬虫实战 #零基础python爬虫教学 #SQLite #论坛社区数据采集 #CSV采集数据导出 #计算机外设 #无线网络 #MTL #市场营销 #线性代数 #智能化测试 #质量效能 #测试策略 #Chaos #几何学 #拓扑学 #时间同步 #NTP #Cron #Jetson Orin #ASUS #肝硬化 #AI系统 #命令模式 #数字营销 #修改rom #打开隐藏功能 #电商 #IPv6 #mcp #memory mcp #Cursor #IPv4 #IPv6 #计算机网络基础 #飞牛nas #fnos #rtsp #转发 #asp.net大文件上传 #微信支付 #收付通 #SSH免密登录 #raid开启 # 远程运维 #SSH别名 #进程创建与终止 #openHiTLS #TLCP #DTLCP #商用密码算法 #pcb工艺 #捷配 #junit #java-rabbitmq #sentinel #ping通服务器 #读不了内网数据库 #dba #CANN #服务器IO模型 #非阻塞轮询模型 #多任务并发模型 #异步信号模型 #多路复用模型 #GPU ##租显卡 #ONLYOFFICE #SSH密钥 #人工智能+ #rtsp服务器 #轻量级rtsp服务 #安卓rtsp服务器 #windows rtsp服务器 #linux rtsp服务器 #KMS #slmgr #serverless #nmodbus4类库使用教程 #Karalon #AI Test #RAID #磁盘 #BIOS #esp32 arduino #java-consul # 数字人系统 # 远程部署 #数信院生信服务器 #Rstudio #生信 #Zabbix #CosyVoice3 #语音合成 #Domain Fronting #域前置 #协议特性 #链路设计 #dlms #dlms协议 #逻辑设备 #逻辑设置间权限 #samba #KVM #mmu notifier #GPA #GVA #影子页表 #服务器操作系统 #win10 # 服务器IP # 端口7860 #django #dash # 语音合成 #开源软件 #odoo #免密登录服务器 #解决方案 #hive #n8n解惑 #mysql集群 #RK3588 #硬件设计 #guava #本地缓存 #密码 #程序员创富 #googlecloud #雨云服务器 #MCSM面板 #腾讯云 #rdp #Android #Bluedroid #群晖 #音乐 #NAS #磁盘配额 #存储管理 #文件服务器 #形考作业 #国家开放大学 #系统运维 #SMARC #ARM #私域运营 #流量运营 #Rust #MetaERP #EBS #EN4FE #嵌入式编译 #ccache #distcc #Linly-Talker # 数字人 # 服务器稳定性 #CNAS #CMA #38634 #异步 #notify #condition #ribbon #任务调度 #层级布局标定 #怎么写论文 #论文写作技巧 #大模型开发 #910B #昇腾 #并发 #Coturn #TURN #STUN #网络编程 #I/O模型 #水平触发、边缘触发 #多路复用 #UClinux #内存 #性能 #优化 #DDR #.netcore #AI论文写作工具 #学术写作效率提升 #AI写论文实战 #论文降重与优化 #Triton #LangFlow # 轻量化镜像 # 边缘计算 #设计模式 #log4j #.NET Core #Identity #少儿编程 #bug #jar #云服务器选购 #Saas #后端框架 #期末作业 #AI赋能智慧奶牛养殖 #实现传统经验到智能精准的跨越 #嵌入式端超轻量级模型 #LeYOLO #AI智慧无人养殖场景 #奶牛、幼崽智能化监管识别 #智慧养殖 #WEB #Go并发 #高并发架构 #Goroutine #系统设计 #透明免抠设计素材 #PNG素材 #设计师素材 #人物素材 #科技素材 #婚庆素材 #图片素材 #azure #TCP服务器 #语音控制 #产品运营 #微PE #硬盘克隆 #DiskGenius #凤希AI伴侣 #net core #kestrel #web-server #asp.net-core #Tokio #异步编程 #Pin #http服务器 #cpu #电机驱动 #TMC2240 #指数平滑 #.NET8 #flask #NS3 #ue4 #DedicatedServer #独立服务器 #专用服务器 #线性回归 #主图视频下载 #电商素材 #淘宝主图下载 #1688主图下载 #电商存图 #firefox #自然语言处理 #鼠大侠网络验证系统源码 #适配器模式 #gateway #vllm #Miniconda #Python3.11 #LVGL图形库移植 #coplar #元服务 #python3 #python语法 #ipython #Python赋值 #文件系统 #Ext2 #openharmony #怎样查看MAC地址 #MAC地址查看方法 #云开发 #云函数 #鸿蒙生态 #心理健康测评 #心理测评 #心理咨询 #AI 量表测评系统 #心理评估 #Socket编程之UDP #HarmonyOS App #开发者年度总结 #安卓 #51单片机 #腾讯云数据库 #TDSQL #OpenTenBase #手动签名 #自定签名 #门禁 #梯控 #电梯门禁 #智能梯控 #智能一卡通 #CPU卡梯控 #CPU卡门禁 #程序 #perl #静态IP #宽度优先 #dfs #bfs #蓝耘MaaS平台 #C文件库 #codex #kimi-k2 #codex cli #glm-4.6 #硬件架构 #双系统 #select #会计 #can #pcan #nodejs #罗马十二帝王传 #苏维托尼乌斯 #Nuendo 14 #Nuendo #Nuendo14 #Nuendo13 #Nuendo12 #cubase #cubase15 #system V #重构 #macOS Sequoia #计算机外设 #acer蓝牙键盘配对 #acer蓝牙键盘切换设备 #虚拟机 #macOS #AMD #H 255 #实例分割 #自动标注 #MicroPython #传感器 #gpt-5 #tar #桃夭权限框 #deb #Mac #XAF #Dev #.NET #毕业答辩PPT #Windows11 #CLion # STM32 # Mac开发 #代码规范 #代码复审 #Flutter #移动开发 #跨平台 #具身智能 #rekep #知识库 #mac版JDK #jdk8 #jdk1.8 #mac版jdk下载 #jdk8下载 #jdk1.8下载 #java17 #几何学 #C语言 #数据结构与算法 #llvm #CLANG #编译器 #StarRocks #MPP引擎 #fast_livo #llamafactory #分布式训练 #调优算法 #微调 #ROS #ROS环境搭建 #鱼香ROS #一键安装ROS #qtwidget #gnu #list模拟实现 #list的使用 #C++STL容器 #大模型应用工程师 #大模型工程师证书 #远程工作 #信号保存 #信号集 #sigpending #packet tracer下载安装 #Kernel #科研 #职业规划 #测试包 #测试 #打包测试 #安装测试包 #紧急模式 #emergency mode #引导加载程序 #xfs #大模型入门 #分词搜索 #分词查询 #gnome #WSL #OpenGL #milvus #proxy模式 #代理模式 #Netty #linux应用层 #Python爬虫零基础入门 #Python爬虫实战 #数据保存与入库 #CSV/JSONL #Python爬虫工程化实战 #桥接模式 #无线驱动 #translate #IndexTTS2 #下载 #harmonyosnext #uv # Conda # 环境变量 #进程终止 #数据 #flink #Claude code #STL #底层 #vector #模板编程 #高频考点 #知识 #模仿学习 #isaac sim #鸿蒙大作业 #奶茶点餐 #物理 #数学 #狭义相对论 #时空 #GRUB #结对编程 #uml #winform #pandas #APT #软件源 #Tahoe #linux内核 #adobe #Affinity #make #cmake #CMakeLists.txt #零代码平台 #AI开发 #阐明原理 #给出标准方案 #提供编程方案 #提及根本解决 #结合主流框架 #opengl #水印 #录制 #办公神器 #效率工具 #Windows技巧 #财务自动化 #发票管理 #职场干货 #批量处理 #自动挂载 #对象 #默认成员函数 #运算符 #boltbot #软考 #高项 #考试 #华为云 #bootloard #搜索引擎 #数字化 #敏捷流程 #快递盒检测检测系统 #数据治理架构设计 ##素数注意力机制 ##稀疏Tranformer ##数论机机器学习 ##黎曼编码 ##数学神经网络 ##长序列建模 ##算法数论 #gdb/cgdb #调试器 #Linux开发工具 #FTP #镜像实战 #Isaac Sim #IsaacSimAssets #Ubuntu 24.04 #命名管道 #影刀 #RPA自动化工具 #AI结合影刀 #规格说明书 #Kalibr #log4j #海外海关报关 #海关代码 #Linux应急响应 #安全排查 #文件审计 #入侵检测 #服务器安全 #基础指令 #昇腾平台 #kafka #pywechat #OpenHarmony #Tree #POC #remote-ssh #离线安装 #VSIX #内网开发 #MinerU #MinerU部署 #MinerU部署文档 #MinerU部署教程 #嵌入式开发 #pull #commit #hun #分层 #unionFS #红蓝对抗 #实战演练 #流计算 #数据处理 #Vulkan #shader #nlp #一切皆文件 #智能体 #spider #智能医疗 #健康档案管理 #Flink 实时计算 #HBase 存储 #糖尿病个性化管理 #医疗数据合规 #Python #系列课程 #镜像构建 #建站 #ssid #anaconda #提示词 #并发编程 #java-rabbitmq #Sign #AI作画 #负载均衡 #claude code #vibe code #工具箱APP #手电筒APP #指南针APP #水平仪APP #分贝仪APP #无广告工具箱APP #工具箱APP开发 #云服务 #可靠传输 #网络行为分析 #进程检测 #启动项排查 #Cron审计 #隐藏进程 #学习记录 #IMX6ULL #plotly #shiro #网络基本概念 #OSI七层模型 #TCP/IP五层模型 #Thinking Claude # 国内加速 #wifi转有线 #电脑wifi 供有线设备上网 #数据订阅 #gru #debian #ROS 2 # Ubuntu # 清华镜像 #太极 #新人首发 #网络爬虫 #prompt #midjourney #stable diffusion #AIGC落地 #国家自然科学基金 #国自然 #学术 #PostgreSQL部署 #PostgreSQL部署文档 #PostgreSQL部署方案 #PostgreSQL部署教程 #进程通信 #匿名管道 #wps #宏开发 #JSA宏 #自动化办公 #Excel #sqli-labs靶场 #sqli-labs #SQL注入漏洞 #SQL注入 #数据库安全 #Web安全 #X11转发 #远程图形 #5G #若依 #ruoyi #frp # 公钥私钥 # 免密登录 #xrandr #显示器 #显示器扩展桌面 #分屏 #Docker Desktop #运维工程师 #运维技术 #RunMethodList #ProcessQueueLis #gitee #智能体来了 #智能制造 #供应链管理 #工业工程 #webpack #cloudflare #nul #word #vivado #FILE #ubuntu24.04 #桌面共享 #进程创建 #历史 #RK35588 #ctyunos #mmu notifier #HMM #hmm_range_fault #hmm_range #QLExpress #控制流程 #从表达式到规则程序 #工程化实践解析 #CVE-2022-49025 #Linux内核安全 #net/mlx5e #UAF漏洞 #safari #N8N #动态住宅代理 #抓取 #图像翻译 #zTasker #容器 #minio #exec #tensorflow2 #echarts #可控AI #dockerdesktop下载 #docker windows #windows docker #dockerdesktop安装 #安装dockerdesktop #dockerdesktop使用 #docker desktop #股票 #新闻 #BringData #亮数据 #jsp大文件上传 #cocos2d #Home #Home assistant #HA #playwright #云桌面 #Citrix #ESXi #AI产品经理 #memcached #wireshark #exec系列 #进程程序替换 #线段树 #nio #Apache Flink #实时计算 #状态管理与 #Exactly-Once语义 #用户评论 #计算机技术 #百度云 #Zibll主题页脚美化 #textln #coze #ribbon #postgis #geoserver #pbf #矢量切片 #图层组 #服务 #效率神器 #打工人必备 #新手入门 #百度 #百度文库 #爱企查 #旋转验证码 #验证码识别 #图像识别 #输入输出流 #mybatis plus #simulink #智能指针实现 #长链接 #短链接 #json #package #codecapsule #羽毛球专业培训 #码上羽毛球馆 #tekton #蓝牙模块 #WIFI模块 #低功耗 #tailscale #Gazebo #网络爬虫 #vue #vue3 #element plus #ant design vue #components组件 #props #vue浏览器报错 #源代码 #免费磁盘清理工具 #电脑磁盘清理工具 #清理系统垃圾 #移除冗余更新文件 #整理磁盘碎片 #深度高级电脑清理 #rkmpp #广告联盟APP开发 #广告联盟APP #广告联盟对接 #广告联盟 #APP开发 #看广告app开发 #广告app开发 #腾讯云COS #HTTP #HTTP/2 #HTTP/3 #Python从手机读取电话声音 #AI外呼机器人 #AI电话外呼 #Python源代码AI外呼 #智能拨号器APP #技术支持 #LTC #期刊 #SCI #概率分布 #最大似然估计 #似然函数 #数理统计 #高考 #PC #论文 #投稿 #发表 #知网 #普刊 #快刊 #消息队列 #QMT #自动交易 #zabbix #硬件工程 #学习 #网络 #Excel函数 #VLOOKUP函数 #数据查找 #精确匹配 #近似匹配 #逆向查找 #kubuntu #Lubuntu #Xubuntu #redhat #OpenSSH升级 #微软 #OpenClaw #结构与算法 #kotlin #数据报系统 #智慧城市 #fiddler #CSDN #MC #MC群组服务器 #Emby #SQL注入主机 #跨域 #发布上线后跨域报错 #请求接口跨域问题解决 #跨域请求代理配置 #request浏览器跨域 #API限流 # 频率限制 # 令牌桶算法 #戴尔服务器 #戴尔730 #装系统 #设计模式 #umeditor粘贴word #ueditor粘贴word #ueditor复制word #ueditor上传word图片 #prometheus #RSO #机器人操作系统 # 双因素认证 # TensorFlow #VSCode # 远程开发 # Qwen3Guard-Gen-8B #业界资讯 #本地客户端和全局服务器更新模型 #联邦学习 #服务器客户端协同工作 #参数更新 #openEuler #node #jetty #junit #java-rocketmq #企业文件安全 #企业文件管理 #IndexTTS 2.0 #AI写作 #Finalshell #生物信息学 #组学 #可再生能源 #绿色算力 #风电 #910B #Proxmox VE #yolov12 #研究生life # 黑屏模式 # TTS服务器 #Qwen3-14B # 服务器迁移 # 回滚方案 #Discord机器人 #云部署 #程序那些事 #人脸识别sdk #视频编解码 #人脸识别 #GPU #算力建设 #SSH复用 # Miniconda #套接字 #I/O多路复用 #字节序 #lua #sqlserver #分布式利器 #group by #row_number #over # WebUI # 网络延迟 #chat #数字基带信号 #suno #suno api #weston #x11 #x11显示服务器 # 大模型部署 # 私有化AI #嵌入式开发 # DIY主机 # 交叉编译 #STUN # TURN # NAT穿透 #Socket网络编程 #工厂模式 #C++11 #联机教程 #局域网联机 #局域网联机教程 #局域网游戏 #上市公司华证ESG #上市公司彭博ESG #上市公司wind esg #上市公司cnrds ESG #esg评级评分 #PyTorch #海光K100 #SSE # AI翻译机 # 实时翻译 #OSS #angular.js #移动端h5网页 #调用浏览器摄像头并拍照 #开启摄像头权限 #拍照后查看与上传服务器端 #摄像头黑屏打不开问题 #排序算法 #分治和策略 #信令服务器 #Janus #MediaSoup #视频格式转换软件 #数据安全 #注入漏洞 #防毒口罩 #防尘口罩 #SSH跳板机 #ORM #.net #jina #远程访问 #飞网 #安全高效 #配置简单 #CSDN镜像 #svn #sourceTree #sourceTree管理svn #身体实验室 #健康认知重构 #系统思维 #微行动 #NEAT效应 #亚健康自救 #ICT人 #UDP协议 #日志模块 #Spring AI #STDIO协议 #Streamable-HTTP #McpTool注解 #服务器能力 #软件 #本地生活 #电商系统 #论文降重 #降AI率 #AI降重 #快降重 #agent skills #claude #AutoDL #租显卡 #训练推理 #鸭科夫 #逃离鸭科夫 #鸭科夫联机 #鸭科夫异地联机 #开服 #服务器开启 TLS v1.2 #IISCrypto 使用教程 #TLS 协议配置 #IIS 安全设置 #服务器运维工具 #超时设置 #客户端/服务器 #C# #PRA #C++ #protobuf #brpc #webrtc #零售 #starrocks #ENSP #实验 #从零开始 #xshell #host key #free #vmstat #sar #google #gemini #generativeai #web服务器 #lvs #UDS诊断 #认证服务 #cangjie #百万并发 #京东云 #树莓派4b安装系统 #CVE-2025-68143 #CVE-2025-68144 #CVE-2025-68145 #elementui #fastapi #包装运输测试 #模拟运输测试 #环境试验 #包装振动测试 #包装跌落测试 #大语言模型 #数据恢复 #视频恢复 #格式化恢复 #删除恢复 #大疆如影4D #大疆如影 #儿童AI #图像生成 #Qwen # Connection refused #RWK35xx #语音流 #实时传输 #动态规划 #结构体 #vLLM #跨站脚本 #flume #cpp #dynadot #域名 #阿里云RDS #mariadb #烟雾报警 #华中农业大学 #期末 #Bricks #数电 #数字电路 #数字逻辑 #logisim #加法器 #实验报告 #游戏美术 #技术美术 #游戏策划 #rpc #wordpress #雨云 #ai编程 #三极管 #开关电路 #开关电源 #恒流源 #Multisim电路仿真 #硬件工程师 #逻辑门 #树莓派 #arcgis #GB28181 #SIP信令 #视频监控 #单片机毕业设计 #gpu算力 #JWT #reactjs #K510 #terraform #剪映 #自媒体 #攻击溯源 #sublime text #sublime #删除偶数行 #删除奇数行 #数字孪生 #三维可视化 #机器人学习 #STDIO传输 #SSE传输 #WebMVC #WebFlux #车用接插件的装配说明 #视觉检测 #虚拟内存管理 #程序地址空间 #进度条 #推荐系统 #AutoGLM #智谱AI #OpenAutoGLM #OFDMA #mmu_notifier #invalidate_seq #amdgpu svm #HarmonyOS 6 #鸿蒙6 #pyenv #Navicat17 #数据库管理工具 #基础IO #namespace #cgroups #LXC #智能硬件 #线程封装 #宇树Go2 #仓颉 #具身智能 #广义相对率 #旋转磁体 #Shell #软件检测 #第三方 #RHEL #ui #ux #Linux包管理 #yum教程 #apt入门 #xcode #twitter #Twitter运营 #Twitter自动化 #社交媒体营销 #引流获客 #虚拟串口 #svc #ddsp #音色克隆 #个人网站 #Linux应用开发 #Linux驱动开发 #FreeRTOS #STM32 #Mate XTs #鸿蒙5.1 #存储维护 #EMC存储 #libc #webpack #矩阵 #线性代数 #虚拟内存 #系统 #入门 #前后端分离 #药材购物系统 #lxc #电脑和ipad怎么传文件 #电脑和ipad传输文件 #电脑和ipad传文件 #ipad传文件 #ipad传输文件 #wap_supplicant #应用层编程 #成长纪实 #分布式软总线 #原理解析 #mac #Infuse Pro #万能播放器 #内核 #网上点餐系统 #Java毕业设计 #java毕设 #计算机专业毕业设计 #页表 #Linux权限管理 #文件权限全解析 #服务器安全配置 #用户与权限实战 #Linux系统安全 #MATLAB #R2023b #Kingbase #驱动安装 #英伟达驱动 #nvidia驱动安装 #利润表 #损益表 #财务 #文件 #二步验证 #谷歌身份验证器 #Authly #yocto #SSM 框架 #学生宿舍管理系统 #高校后勤管理 #navidia #gpu #预处理 #函数调用 #哲学 #交友 #芯片验证 #system verilog #鸿蒙应用开发 #OHOS-SDK #签名 #mac26 #playCover #键盘映射 #sdkman #沙盒隔离 #程序多开 #安全测试环境 #Windows应用隔离 #免费沙盒工具 #开发测试防护 #信号量 #PV #软链接 #硬链接 #正则表达式 #机器翻译 #ZeroMQ #VS2022 #RK3576 #智能家居 #ESP32 #鸿蒙跨平台应用开发 #鸿蒙跨平台应用 #支持向量机 #onedrive #应用层服务 #zimbra #邮件服务器 #邮件 #证书 #pem #crt #财务 #利润表 #资产负债表 #方法 #功能 #操作 #开发者平台 #pandas #博客之星 #Linux的线程池 #模拟器 #Java大数据 #智能教育 #在线考试 #工站流程 #业务流程组装 #产线端 #fail2ban #firewallcmd #主组 #附加组 #混合搜索 #TF-IDF VS BM25 #Rerank重排 #语义分段 #向量数据库抽象适配层 #TTFT延迟缓解 #pdf多模态处理 #5种IO模型 #非阻塞IO #压力测试 #嵌入式软件测试 #医疗软件测试 #医疗设备嵌入式 #Parasoft #IEC 62304 #ISO14971 #arm #es可视化管理工具 #Git #开发流程 #KES #反向兼容 #prometheus #grafana #Dart #清理 #移远通信 #AR588MA #卫星通讯 #GB28181国标部署 #接入GB28181国标平台 #wvp #rstp #FrameNode #组件动态化 #鼠标 # Python 3.11 #miniforge3 #移动硬盘 #boot-repair #引导修复 #文本传输 #跨平台传输 #操作系统原理 #鸿蒙2025领航者试炼 #生态 #studione #studioone7 #studioone6 #studio one pro #studio one 7 #studio one 6 #机架 #hyper-v #增强会话 #linux内存管理 #linux内存申请与释放 #linux内存碎片 #glibc内存管理 #磁盘清理 #信号捕捉 #Linux入门 #自由表达演说平台 #年会 #发言稿 #动态列表 #滚动加载采集条数 #表情识别 #数字信号处理 #CS336 #Assignment #Experiments #TinyStories #Ablation #Android16 #音频性能实战 #音频进阶 #issue #UVC #V4L2 #powerbi #海外短剧系统 #海外短剧系统开发 #海外短剧APP开发 #海外短剧系统搭建 #短剧系统 #海外短剧h5 #openclawd #CNAS #程序文件 #Service #systemctl #HarmonyOS6 #录音 #录音转文字 #望获 #瑞芯微 #Floor #IPIDEA #网页抓取API #蓝耘元生代 #蓝耘MaaS #oss #css #VirtualBox #Ubuntu22.04.5 #CUDA #安全自动化平台 #原子能力 #vc #omnibox #IP地址 #MAC地址 #TCP/IP协议 #网络传输流程 #yum源 #DevOps #Linux 多阶段构建 #数据链路 #数据同步 #KALI #c5全栈 #docker部署coze #coze部署详解 #docker搭建coze #docker部署Coze #coze部署 #先进数据库 #openGauss #蓝耘agent ##影刀RPA# ##影刀RPA #触发器 #obsidian #分布式计算 #Java 大数据 #基因测序 #精准医疗 #Spark #Qwen3-32B # 生物信息学 # 大语言模型 #慕课线上课程推荐 #慕课线上课程推荐可视化系统 #Pytho线上课程推荐可视化 #线上课程推荐数据分析可视化系统 #储存卷 #前后台进程 #大O1调度 #g++ #gdb #实验室 #selenium #LangGraph #AI Agent #接口测试 #即梦4.0 #opencascade #样条曲线 #xml #医学图像分割 #CNN #人工智能论文 #宝塔云服务器 #deepseek #zookeeper #在线商城 #购物商城小程序 #购物小程序 #联机 #PostIn #网络变压器 #图像识别自动化 #AirtestIDE #腾讯轻量云AI创想家 #连接错误 #connect #ainode #Android15 #统信uos #麒麟 #中科方德 #FastCGI #PHP-FPM #缓冲区溢出 #程序断点调试 #推荐算法 #TradingView #wsl子系统 #solidity #CS144 #TCP/IP 四层模型 #VPN技术 #AI教材生成工具 #AI写作技术 #教材创作效率提升 #教材编写问题解决 #Yocto #buildroot #ISO #构网 #构网控制策略 #变流器 #新能源 #构网控制 #Filter过滤器 #Interceptor拦截器 #登录 #请求拦截 #javaweb #图神经网络 #池化 #hystrix #机器翻译 #动态规划 #远程软件 #逻辑分析仪 #隔离模块 #隔离芯片 #高压隔离 #数字 #解码器 #MinIO #运维工具 #Hexhub #数据库运维 #复习资料 #题库 #期末考试 #DBA #rocketmq #AutoCAD替代 #银河麒麟操作系统 #QCAD #中望CAD #开源社区版QCAD #金蝶 #国产化技术 #AAS #笔记本电脑 #SMP(软件制作平台) #uml #UML建模 #设计方案 #量化软件 #PTrade #量化交易 #量化炒股 #PID算法 #PLC #Modbuc_TCP #DIN #注意力单元 #创新点 #改进点 #兴趣表征 #激活函数 #正则化 #魔改YOLO #claude code #OBS #zerotier #内网 #公网 #穿透 #sshd #广告 #创意广告 #网页设计 #语音合成 #ESPnet #时频传递 #Flutter #Flutter教程 #移动开发教程 #HNU #计算机系统 #实时音视频 #xshell免费版 #Xshell下载 #xshell电脑版 #xshell 中文 #neovim完全卸载 #libc库 #自动化脚本 #企业微信 #融资 #贷款 #股权融资 #债权融资 #财务管理 #企业获利 #数模 #缓存调度 #hbase #PyWin32 #CANape #COM API #swift #自动化爬虫 #ai爬虫 #弹簧报价软件 #紫垣商驿 #大模型 #SpringAi #卡尔曼滤波 #RTS平滑 #正向滤波 #反向平滑 #单调栈 #分类 #编码 #乱码 #Prometheus #AI工具 #CBR #ISP调优 #watchdog #C++面经 #关键帧 #音画同步 #财报 #现金流量表 #融资杠杆系数 #资产周转率 #销售利润率 #Windows高级文件管理器 #高级文件管理器 #Windows文件管理器 #电脑文件管理器 #多窗格文件管理器 #批量重命名工具 #tdengine #涛思数据 #海思ss928 #SVP_NNN #NNN #数据结构初阶 #时间复杂度 #空间复杂度 #网络共享 #映射网络驱动器 #PCB设计 #LED灯珠 #LED灯珠生产厂家 #指示灯珠 #家用电器 #3C数码 #LT和ET模式 #modelengine #电商 #GEO #共享文件夹 #数据仓库 #doris #容器部署 #mysql5.7 #视频编解码 #Agent #Nano Banana Pro #文生图 #多模态 #设备驱动 #网卡 #3d #人机系统 #人机对话 #人本 #人本智能 #实用工具 #DameWare #远程工具 #IT运维 #后台按键 #后台鼠标 #鼠标连点 #自动按键 #ERP #SAP PP # 高并发部署 #微信公众号 #Linux入门 #Shell基础 #命令行操作 #Linux发行版 #React #Next #CVE-2025-55182 #RSC #我的世界 #AutoDL使用教程 #AI大模型训练 #linux常用命令 #PaddleOCR训练 #人大金仓 #swagger #模拟退火算法 #YOLO识别 #YOLO环境搭建Windows #YOLO环境搭建Ubuntu #jupyter #三维 #3D #三维重建 #winscp #机器视觉 #6D位姿 #Tkinter #GUI开发 # ProxyJump #密码学 #ebs #metaerp #oracle ebs # 远程连接 #PyCharm # 远程调试 # YOLOFuse # 批量管理 #SSM #在线订餐 #订餐系统 #在线订餐平台 #订餐管理系统 #Playbook #AI服务器 #工厂方法模式 #canva可画 #canvas # 批量部署 #es安装 #zookeeper #ZooKeeper #ZooKeeper面试题 #面试宝典 #深入解析 #投标 #标书制作 #Cpolar #国庆假期 #服务器告警 #IO #研发管理 #禅道 #禅道云端部署 #2025年 #AI应用编程 #测速 #iperf #iperf3 #copilot #重装操作系统 #coffeescript #uvicorn #uvloop #asgi #event #python量化 #高频量化 #hft #openai炒股 #PowerBI #企业 #YOLOFuse # Base64编码 # 多模态检测 #SSH反向隧道 # Jupyter远程访问 #claudecode实战 #翻译 #开源工具 #最大子数组和 #Kadane算法 #分治法 #LeetCode #maxSubArray #ms-swift # 一锤定音 # 大模型微调 #Smokeping #json #NeurIPS #ICML #ICLR #机器学习顶会 #学术投稿 #会议文化 #顶会顶刊 #AI部署 # 大模型 # ms-swift #SSH公钥认证 # 安全加固 # 远程访问 #ONLYOFFICE #MCP 服务器 #广播 #组播 #并发服务器 #web #webdav #SSH保活 #uvx #uv pip #npx #Ruff #pytest #ThingsBoard MCP #着色器 #ERP 基本操作 #Node.js # child_process #苍穹外卖 #云服务器操作系统 #服务器系统 #KMS 激活 #uip # GPU租赁 # 自建服务器 #Python办公自动化 #Python办公 #UDP套接字编程 #网络测试 #L6 #L10 #L9 #Anything-LLM #IDC服务器 #私有化部署 #处理器 #毛利润 #营业利润 #净利润 #x32dbg #SSE #测试用例 # 大模型推理 #VibeVoice # 高温监控 #昇腾300I DUO # 公钥认证 #国产操作系统 #V11 #kylinos #服务器架构 #AI推理芯片 #编程助手 # 服务器IP配置 #服务器线程 # SSL通信 # 动态结构体 #ipv6 #TURN # WebRTC #GPU服务器 #8U #firefox #vncdotool #链接VNC服务器 #如何隐藏光标 #appche #vps #文件管理 # 水冷服务器 # 风冷服务器 #Modbus #IFix #rsync # 数据同步 #cocoa #WinDbg #Windows调试 #内存转储分析 #tcpdump #DDD #绞杀者策略 #Spring Cloud #智能一卡通 #门禁一卡通 #梯控一卡通 #电梯一卡通 #消费一卡通 #一卡通 #考勤一卡通 #ICE #numpy #palantir #skywalking #告警 #分析 #链路 # 键鼠锁定 #Rendering #Multisampling #内核编码 #语音生成 #TTS #claude-code #软件开发 #pjsip #powerpoint #ueditor导入word #ueditor导入pdf #宕机 #启动失败 #操作系统 #国产化OS #mssql #memcache #RM #IMU #PID #new #delete #malloc #MQTT协议 #大模型应用 #PyInstaller打包运行 #服务端部署 # ControlMaster #插座检测系统 #WM_LBUTTONDOWN #Logon Help对话框 #ScanSysQueue #godot #clang #godot-cpp #DongYuTvWeb #电视应用 #Kotlin开发 #WebView #开源项目 #ResNet #六西格玛培训公司 #六西格玛绿带培训 #六西格玛 #精益六西格玛培训 #六西格玛培训 #ftp #sftp #Fluentd #Sonic #日志采集 #堡垒机 #安恒明御堡垒机 #windterm #回文链表 #快慢指针 #双指针 #opc #opc ua #opc模拟服务器 #408真题解析 #电源管理工具 #Comate #Robert变换 #银河麒麟aarch64 #Triton #screen命令 #ARM服务器 # GLM-4.6V # 多模态推理 #markdown #ComfyUI # 推理服务器 #运维工具 #C2000 #TI #实时控制MCU #AI服务器电源 #OPCUA #CA证书 #mongodb #视频修复 #RAID5恢复 #流媒体服务器恢复 #svn #高斯溅射 #scala #内存治理 #gerrit #DisM++ # 系统维护 # ARM服务器 #展厅 #展厅升级 #创新 #最短路算法 #Dijkstra #photoshop #微信开放平台 #udp #vue上传解决方案 #CNSH 中文编程 #龙魂系统 ##技术有军魂 #AI生成 # outputs目录 # 自动化 #库存超卖 #Redis #AB包 #改进 #IndexTTS2 # 阿里云安骑士 # 木马查杀 #seo #用户数据报协议 #网路编程 #hadoop #clickhouse #家政服务预约 #分子动力学 #化工仿真 #c++高并发 #晶振 #GATT服务器 #蓝牙低功耗 #学术写作辅助 #笔启AI论文功能 #AI写作效率提升 #VMware Workstation16 #虚拟线程 #JDK #I/O #ProjectLoom #决策树 #Gunicorn #WSGI #Flask #并发模型 #性能调优 #面试题 #大模型智能体 #二进制 #nfs #iscsi #汽车 #Jni #攻防演练 #Java web #红队 #ipad #复现论文 #blender #warp # 局域网访问 # 批量处理 #simulink #ipmitool #BMC #测试覆盖率 #ASR #SenseVoice #A2UI #CS2 #debian13 #系统修复 #七牛云存储 #CVE-2026-22686 #Node.js #enclave-vm沙箱 #AI代码执行领域 #企业信息化 #yolo #C #AutoGen #agent #AutoGenStudio #镜像 #DNS #腾讯轻量云AI创想家 #C盘清理技巧分享 #C盘清理 #进程终止 #DataGrip #kotlin #数据集 #数据库架构 #数据库开发 #ESXi #slam #navigation #HarmonyOS #GLIBC #在U盘安装操作系统 #kali linux #逻辑回归 #中文输入法 #规则引擎 #合同 #astmd4169 #ide #最小系统 #ida #远程调试 #Anaconda # PaddlePaddle #burpsuite #抓包 #orbstack #Homebrew #Ceiling #GATTC #vmtools #工业自动化 #欧姆龙 #Sysmac Studio #工控 #core #dump #usb驱动 #Rocky Linux #网络配置 #Qt6 #地学应用 #地质灾害 #文件备份 #Syncthing #布局 #列表 #网格 #轮播图 #瀑布流 #文本折叠 #折叠 #worker #reportRealtimeA #微信小程序报错 #uniapp微信小程序 #微信开发者工具编译报错 #旁路由 #postman #应用层 #学习方法 #Debezium系列 #基于Debezium #Operator #部署数据库 #采集任务 #SAAS #西门子 #罗宾康 #A5E03407403 #互斥锁 #原子性 #互斥量 #算逻运算 #WinApps #装饰器 #状态管理 #HmRouter #数据流 #tf-idf #easyui #短剧APP开发 #短剧平台定制开发 #短剧APP #短剧系统搭建 #夸克网盘 #夸克 #1T #ClaudeCode #Claude #GLM #GLM-4.5 #智谱 #GLM-4.6 #文献综述 #gitcode #设计规范 #volatile #顶级期刊 #IEEE TPAMI #IJCV #学术风格 #投稿策略 #轨道力学 #星际航行 #轨道确定 #航天 #防火墙 #Veristand #LabVIEW #HIL #Simulink #日志系统 #手搓 #刷新策略 #产品经理 #软件构建 #kibana #自然语言查询 # 日志分析 # Fun-ASR #pass through #vm # Shell # Bash #Macdown编辑器 #build-essential #线性导轨 #直线模组 #深度卷积生成对抗网络 #学习笔记 #Linux的多线程 #SQL #android-studio #android jetpack #Studio Pro 8 #StudioPro 8 #StudioPro8 #Studio One 7 #StudioOne #Studio One 6 #Studio One 8 #Spire.Office #高速电路 #SI #PI #信号完整性 #GNSS #SPP #PPP #osg #阴影校正 #ISP #阅读笔记 #钉钉 #微信开发者工具 #电子稳像 #MeshFlow #事务 #用户运营 #Cubase 15 #Cubase #Cubase15 #Cubase14 #Cubase13 #Cubase12 #nuendo14 #VPC #数字IC #Linux版PE #修改密码 #电脑上不了网怎么办 #wifi有网络但是电脑连不上网 #wifi网络设置 #wifi连接 #DNS设置 #路由器配置 #容器编排 #图形渲染 #密码攻击 #对比单体与微服务架构 #SpringCloud核心组件 #架构原理 #自动化工作流 #Docker部署 #工作流平台 #Ralph Wiggum #自动化开发 #VLOOKUP #办公自动化 #自动匹配 #智能录入 #进销存 #红黑树 #并集查找 #数据库选型 #单元测试 #gradle #流程引擎 #Flowable #企业级组件 #项目管理 #PAN #开源协议 #Dify 搭建数据分析应用 #Dify 搭建数据分析平台 #Dify操作数据库 #Dify 数据可视化 #Dify 数据可视化应用 #mediaquery #密码破解 #keepass #OpenStack #核心组件 #开源云平台 #OpenStack 企业应用 #unet #unet++ #cm unet #pip #光伏发电 #数字孪生 #光伏电站 #光伏电站运维 #碳中和 #工具集 #文件操作 #open系统调用 #HTTP跨域 #docker搭建mysql主从 #docker 搭建mysql #mysql主从搭建 #mysql主从 #mysql主从搭建详解 #go语言 #个人论坛 #小皮面板 #本地服务器 #Discuz论坛 #连续 查询 #CQ #TensorFlow # 镜像加速 # 私有镜像代理 #真题解析 #408考研 #JSP #机房预约管理系统设计与实现 #SaaS #明厨亮灶 #lamp #ue4 #序列化漏洞 #CISP-PTE #新媒体运营 #书匠策aI #Java原生目录生成 #vr #航空航天 #VR神舟飞船 #VR华夏神舟 #接口 #PROFINET #CycleConter #DCP #LLDP #智能制造 #ISA-95 #ISA-88 #数字化工厂 #华为od机试真题 #华为od机考真题 #华为od上机考试真题 #华为od上机考试双机位C卷 #华为od机考 #华为od机试 #Jenkins #USB数字耳机 #192k播放 #rxjava #高卢战记 #凯撒 #hastcat #x86架构 #语音 #罗宾康 #西门子 #OpenSSH #OpenSSH下载 #OpenSSH安装 #OpenSSH使用 #OpenSSH下载教程 #OpenSSH安装教程 #OpenSSH下载安装教程 #JAVA #JavaAI #飞算 #代码生成 #飞算JavaAI炫技赛 #musictag #音乐刮削 #音乐标签 #思源笔记 #2025博客之星评选投票 #博客之星评选投票 #2025博客之星 #评选投票 #投票 #2025评选投票 #freeswitch #GOT # 长连接 # 心跳机制 #vue3 #天地图 #403 Forbidden #天地图403错误 #服务器403问题 #天地图API #部署报错 #jar自动逆向工具 #jar逆向源码 #Chaterm #合合信息 #Chaterm智能终端 #Docker Compose #技术问答 #反爬虫 #Math #foundations #transformations #网站建设 #建站系统 #建站平台 #内容管理系统 #物料主数据管理系统 #花卉识别 #植物病虫害识别 #YOLO数据集 #花草识别 #MongoDB部署 #MongoDB部署文档 #MongoDB部署方案 #docker部署MongoDB #ai配音 #免费配音 #配音软件 #dreamweaver #癌症 #rnn #商业应用 #学术写作 #AI论文生成 #论文查重降重 #资源管理 #AutoCloseable #有序释放 #健壮性 #可读性 #cst #CST软件 #emqx #源代码管理 #迁移学习 #hop #因果推理 #统计 #飞书 #openclaw #abtest #LSTM #入侵 #日志排查 #万悟 #联通元景 #nvcc #screen 命令 #电气工程 #挖矿 #Linux病毒 #idc #服务器解析漏洞 #C# # REST API # GLM-4.6V-Flash-WEB #银河麒麟 #系统升级 #信创 #bond #服务器链路聚合 #网卡绑定 #uniapp #合法域名校验出错 #服务器域名配置不生效 #request域名配置 #已经配置好了但还是报错 #国产开源制品管理工具 #java-activemq # 鲲鹏 #企业级存储 #网络设备 #霍曼转移 #轨道机动 #ModelEngine #实时语义分割 #DeepLabV3 #与BiSeNet的效率权衡 #Conda # 私有索引 # 包管理 #vivado license #puppeteer #音频 #超算中心 #PBS #lsf #RAID # GLM-TTS # 数据安全 # 云服务器 #Autodl私有云 #深度服务器配置 #JumpServer #守护进程 #复用 #screen #GLM-4.6V-Flash-WEB # AI视觉 # 本地部署 # 智能运维 # 性能瓶颈分析 #python基础 #python小白 #面向对象 #学生社团管理系统 #社团加入与退出 #ARM64 # DDColor # ComfyUI #MOXA #分阶段策略 #模型协议 #Dell #PowerEdge620 #硬盘 #RAID5 #windbg分析蓝屏教程 #停车场管理系统 #docker安装seata #AI 推理 #NV # 模型微调 #科学家 #Nature #音诺ai翻译机 #AI翻译机 # Ampere Altra Max #jar #Modbus-TCP #代理服务器 #anaconda3 #无网 #热敏电阻 #PTC热敏电阻 #rustdesk #TTS私有化 # 音色克隆 #LockBit 5.0 #勒索软件 #LovkBit #在线考试系统 #考试系统 #考试管理系统 #openresty #IO多路复用 #ESP32编译服务器 #Ping #DNS域名解析 #LobeChat #GPU加速 # 权限修复 #华为交换机 #信创终端 #AD操作技巧 #原理图库文件自动缩放 #paddleocr #MC #Harbor #智能电视 # SSH # Qwen3Guard #SEO优化 #入职背调 #企业背调 #背调 #teamviewer #SPA #单页应用 #云服务器 #个人电脑 #蓝湖 #Axure原型发布 #SEW变频器 #web前端 #guava #caffe #sclgntfy #WLEventLogon #可视化大屏 #锐捷 #nat #银河麒麟部署 #银河麒麟部署文档 #银河麒麟linux #银河麒麟linux部署教程 #Ward #RustDesk #宝塔面板部署RustDesk #RustDesk远程控制手机 #手机远程控制 #课设 #开发实战 # 显卡驱动备份 #Nacos #计算机毕业设计 #计算机毕设项目 #Modbus # 串口服务器 # NPort5630 #TLS协议 #HTTPS #运维安全 # Python3.11 #可撤销IBE #服务器辅助 #私钥更新 #安全性证明 #双线性Diffie-Hellman #浏览器 #Ascend #MindIE #电影院管理系统 #电影院购票系统 #电影订票系统 #代理 #Anaconda配置云虚拟环境 # keep-alive #计算机组成与cmd dos命令 #启蒙英语 #防毒面罩 #防尘面罩 #生信入门 #生信云服务器 #openai #起源 #发展历程 #应用领域 #功能测试 #Arduino BLDC #机器人多模态智能导航系统 # 服务器IP访问 # 端口映射 #大语言模型 #长文本处理 #GLM-4 #Triton推理 #HarmonyOS APP #视觉理解 #Moondream2 #多模态AI #游戏程序 #Archcraft #语义检索 #向量嵌入 #多功能电脑维护工具 #驱动更新 #游戏加速 #系统优化 #电脑驱动工具 #电脑驱动一键更新 #电脑驱动一键备份 #企业建站 #搭建网站 #网站管理系统系统 #模型训练 #文生视频 #CogVideoX #AI部署 #最小二乘法 #超算服务器 #算力 #高性能计算 #仿真分析工作站 #TensorRT # 推理优化 #NFC #智能公交 #服务器计费 #FP-增长 #googlecloud #Linux开发 #系统编程 #文件IO #重定向 #运维开发 #微调 #训练 #多模态 #omnibox #职场和发展 #前端框架 #空间隔离 #镜像仓库 #安全自动化平台 #windows服务 #权限提升 #C/C++ #镜像实战 #Prometheus #wsl2 #wsl #OWASP #juice-shop #安全漏洞练习靶场 #docker 搭建mysql #mysql主从搭建 #mysql主从 #mysql主从搭建详解 #coze #cursor #线程池 # 镜像加速 # 私有镜像代理 #Linux的进程概念 #FuncPlotCalc #rk3588 #权限 #文件权限 #gdb/cgdb #调试器 #Linux开发工具 #蓝耘MaaS #dify #工作流 #交互 #嵌入式 #进程间通信 #多线程 #pthread #线程互斥 #环境变量 #进程切换 #进程调度 #Windows11 #WSL2 #ctf #逆向 #系列课程 #动态库 #静态库 # Mac部署 # Docker #美女 #学术 #专著 #降重 #降AI #V2装饰器 #V2 #local #毕业论文 #运维必备 #投屏软件 #Escrcpy #安卓投屏 #办公学习 #openeuler #bridge #题解 #图 #dijkstra #迪杰斯特拉 #whisper #Wan2.2-T2V-5B # Mac M系列芯片 # 文本生成视频 #Vue3 #前端工具链 #Vite #Pinia #AI集成 #程序替换 #系统集成项目管理工程师 #魔百盒刷机 #电视盒子 #Bright Data #rpa #awk #SSRF漏洞 #SSRF实战教程 #软件测试和开发 #测试场景 #用例设计 #BUG #pat考试 #rancher #orbstack #pyqt #库存管理 #Easy-Es 实战操作详解 #Easy-Es #Easy-Es 使用 #Easy-Es 使用详解 #Easy-Es操作详解 #Easy-Es详解 #Easy-Es 实战 #HBA卡 #RAID卡 #生成对抗网络 #YOLO11 #原型模式 #网络层 #IP协议 #AI论文写作工具 #论文效率提升 #AI写作实测 #教材创作效率提升 #教材格式规范处理 #AI教材写作工具 #教育行业技术应用 #教材创作项目实战 #教材格式问题解决 #JavaScript #less #flowable #jeecgboot #OA #打印 #光耦 #光电耦合器 #Codes #Jira #需求管理对比 #项目经理 #调试 #mcp server #AI实战 #cnn #导航网 #virtualbox #lubancat4 #sas #汇川 #Blazor #总线 #修改名称 # 模型训练 #smtp #smtp服务器 #PHP #Web墨卡托投影 #失真规律 #等角圆柱投影 #QGIS #LLM #jetty #读写锁 #RCU #GIL #空间转录组与空间ATAC #空间组学联合分析 #barcode映射 #文件传输 #电脑文件传输 #电脑传输文件 #电脑怎么传输文件到另一台电脑 #电脑传输文件到另一台电脑 # 环境迁移 #Qwen3-VL # 服务状态监控 # 视觉语言模型 #HTML5 #audio标签 #GLM-TTS #PaleMoon #Firefox # AI部署 #scala #NoSQL #win11 #ssm #用户运营 #进程调度算法 #进程同步与互斥算法 #死锁处理算法 #MCP服务器注解 #异步支持 #方法筛选 #声明式编程 #自动筛选机制 #risc-v #pcb工艺 #YOLOv11 #边缘AI # Kontron # SMARC-sAMX8 #照片背景更换 #图片换背景 #照片更换背景 #Cubase #nuendo #音乐分类 #音频分析 #ViT模型 #Gradio应用 #CMake #Make #远程软件 #Chat平台 #ARM架构 #Linux多线程 #WT-2026-0001 #QVD-2026-4572 #smartermail #主板 #总体设计 #电源树 #框图 #内存结构 #CSDN成长记录 #爬虫实战 #ai爬虫 #dubbo #MacOS #agentic rag #智能体 #openclaw #clawd #molt #密码攻击 #程序员 #本地化部署 #springboot #xfce #LoTDB #电路仿真 #proteus #AD #keil #硬件工程师面试 #mfc #工业级数码管 #notepad++ #tornado #对比单体与微服务架构 #SpringCloud核心组件 #架构原理 #pcm #健康医疗 #迭代加深 #Xshell #powershell #Ubuntu22.04 #Makefile #Cmake #贪心算法 #二分 #AI零代码开发 #敏捷开发 #自然语言编程 #软件开发范式变革 #蓝牙 #网页抓取API #数据采集 #oss #YOLOv12 #Java原生目录生成 #Claude code #自动化开发 #影刀 #RPA自动化工具 #AI结合影刀 #c5全栈 #docker镜像 #docker镜像源 #docker镜像加速 #docker镜像下载 #docker国内加速 #coze部署详解 #docker搭建coze #docker部署Coze #coze部署 #持续集成 #容器化 #进程 #进程控制 #Lenyiin #Linux网络 #协议定制 #JosnCpp库 #序列与反序列化 #进度条 #IP地址 #MAC地址 #TCP/IP协议 #网络传输流程 #Nano Banana Pro #Gemini #文生图 #仿真 #实战 #Skill #数据库选型 #Ubuntu共享文件夹 #共享目录 #Linux共享文件夹 #自动化工作流 #Docker部署 #工作流平台 #servlet #USB共享 #共享上网 #手机热点 #safari #N8N #沙盒 #部署 #MCM/ICM #数学建模美赛 #运筹学 #Docker Desktop #openstack #蓝耘容器云 #容器编排 #docker compose #docker windows #windows docker #dockerdesktop安装 #安装dockerdesktop #dockerdesktop使用 #docker desktop #Shell #连续 查询 #CQ #uboot #设备树 #基础IO #互联网医院 #idea #intellij idea #Tree #k8s集群资源管理 #云原生开发 #硬件架构 #podman #国内镜像源 #最小系统 #OpenClaw #css3 #FastCGI #PHP-FPM #telnet #远程登录 #Linux的多线程 #蓝耘agent #udev #金仓数据库 #国产数据库 #etl #etl工程师 #防火墙阻止连接网络 #网络防火墙 #阻止软件连接网络 #网络流量控制 #局域网连接 #允许程序访问网络 #hop #自动化枚举 #WinPEAS #文件I/O #系统文件 #系统调用 #cuDNN #TFTP #FTP #openssh #漏洞 #sshd #散列表 #缓冲区 #aarch64-linux #arm交叉编译工具 #安卓 #Linux的线程池 #AI大模型 #大模型学习 #管道Pipe #命名管道 #repo #openhamony #进程创建与终止 #用户体验 #消息队列 #SystemV #信号 #gazebo #宇树Go2 #ROS2 #tcp #RK35588 #ssh #运维 #Cgroup #jdk #JDK17 #jdk21 #网关 #工具 #项目 #懒汉模式 #LINUX系统 #trae #TraeAgent #无名杀 #windows日志 #5种IO模型 #非阻塞IO #mmu_notifier #invalidate_seq #amdgpu svm #ainode #信号量 #PV #elk #系统监控 #性能优化维监 #同花顺 #burp suite #抓包 #Yocto #buildroot #system V #IDE #unet #unet++ #cm unet #ELF #虚拟地址空间 #静态链接 #xrandr #显示器 #显示器扩展桌面 #分屏 #信号机制 #进程优先级 #Linux调度算法 #寄存器 #VON #Linux文件 #IO库 #Glibc #股票 #新闻 #BringData #材料工程 #触发器 #共享内存 #System V #TimechoDB #Kubernetes #linux驱动开发 #命令行解释器 #Linux系统编程 #NAT #数据库管理 #gru #PATH #绿联 #群晖 #飞牛 #局域网共享无权限 #组策略局域网访问修复 #绿色便携版网络工具 #多系统共享兼容性处理 #进程池 #unix #原子能力 #epoll #高级IO #LT和ET模式 #知识库 #Qwen3 #agi #GEO #密码破解原理 #John the Ripper #kube-vip #POC #若依框架 #定制化前端动态域名 #管道 #IPC #coze模板 #电影解说 #AI视频剪辑工具 #coze工作流 #TCP协议 #医学图像分割 #CNN #Transformer #人工智能论文 #生产者消费者模型 #并发编程 #基础指令 #Ext文件系统 #Linux面试 #LBA寻址 #CHS寻址 #平板电脑 #exec #进程替换 #汇编 #向日葵 #麒麟2503 #统信UOS #国产化 #远程控制 #脚本语言 #nodejs #英拓克 #直流调速器 #强化学习 #vibe code #时钟中断 #写时拷贝 #缺页中断 #硬件中断 #软中断 #Trae #AI代码编辑器 #vertx #vertx4 #vert.x #runOnContext #worker #ultralytics #各个子模块代码学习解读 #图像分割 #目标跟踪 #DETR #文件系统 #Ext2 #ai编程 #Typore #remote-ssh #离线安装 #VSIX #内网开发 #线程封装 #KingbaseES部署工具 #华为OD机试真题 #华为OD机考 #华为OD上机考试 #华为OD机试双机位C卷 #华为OD机考双机位C卷 #Java面试题 #面试宝典 #深入解析 #显卡驱动 #思源笔记 #firecrawl #捷配 #AINode #mobaxterm #termius #electerm #tabby #termcc #EtherCAT #MinerU #MinerU部署 #MinerU部署文档 #MinerU部署教程 #Reactor #ET模式 #非阻塞 #高并发服务器 #经管科研 #NativeRAG #docker run #数据卷挂载 #端口映射 #交互模式 #struts #xml #安全性测试 #MySQL 8.0容器化 #MySQL远程访问配置 #Docker数据卷挂载 #visual studio code #python学习路线 #python基础 #python进阶 #python标准库 #需求分析 #SSH 服务 #SSH Server #OpenSSH Server #GPT #Deepseek #osg #tensorflow #WordPress #期货 #Docker Hub #HTTP跨域 #跨域 #电视剧收视率分析与可视化平台 #umeditor粘贴word #ueditor粘贴word #ueditor复制word #ueditor上传word图片 #ueditor导入word #vue断点续传 #vue分片上传下载 #vue分块上传下载 #vue分割上传下载 #vue上传文件夹 #YOLOv13