2. 从服务器的主接口入手

news2024/12/22 23:51:15

Webserver 的主函数 main.cpp,完成了哪些功能?

#include "config.h"

int main(int argc, char *argv[])
{

    string user = "";
    string passwd = "";
    string databasename = "";

    Config config;
    config.parse_arg(argc, argv);

    WebServer server;

    server.init(config.PORT, user, passwd, databasename, config.LOGWrite, 
                config.OPT_LINGER, config.TRIGMode,  config.sql_num,  config.thread_num, 
                config.close_log, config.actor_model);
    

    server.log_write();

    server.sql_pool();

    server.thread_pool();

    server.trig_mode();

    server.eventListen();

    server.eventLoop();

    return 0;
}

上面是服务器的主函数,初始化并启动服务器。它的主要功能包括解析命令行参数初始化服务器设置日志数据库连接池线程池、触发模式等。

数据库信息设置

这里是数据库连接的用户名、密码和数据库名称,根据实际需要修改为正确的数据库配置信息。

string user = "";
string passwd = "";
string databasename = "";

命令行参数解析

创建 Config 类对象 config 并调用 parse_arg 函数解析命令行参数,例如端口号、日志模式、触发模式、线程池线程数等设置。

Config config;	// 创建 Config 类对象 config
config.parse_arg(argc, argv);	// 调用 parse_arg 函数解析命令行参数

服务器初始化

初始化 WebServer 对象,传入多个配置信息,包括:

  • 端口号
  • 数据库信息:1 数据库信息设置 中设置的数据库用户名、密码和数据库名称
  • 日志配置
  • 触发模式
  • 数据库连接池大小
  • 线程池线程数
  • 日志关闭选项
  • 并发模型(actor_model)。

这一步会完成服务器的基本配置。

WebServer server;

server.init(config.PORT, user, passwd, databasename, config.LOGWrite, 
            config.OPT_LINGER, config.TRIGMode,  config.sql_num,  
            config.thread_num, config.close_log, config.actor_model);

日志系统

配置并初始化日志系统,用于记录服务器运行时的事件、错误、请求信息等,便于调试和维护。

server.log_write();

数据库连接池

初始化数据库连接池,管理多个数据库连接,提高数据库访问效率。连接池可以避免频繁的数据库连接开销。

server.sql_pool();

线程池

初始化线程池,创建一定数量的工作线程,用于并发处理客户端请求,提升服务器的并发能力

server.thread_pool();

触发模式

设置触发模式,配置事件监听的触发方式( LT 模式和 ET 模式),用于控制 I/O 多路复用的触发行为。

server.trig_mode();

监听端口

监听端口,准备接受客户端的连接请求。

server.eventListen();

事件循环

进入事件循环,开始处理客户端连接请求及其他事件。eventLoop 通常会运行在一个循环中,处理网络事件、请求解析、响应生成等操作。

server.eventLoop();

Config 类

Config 类,用于配置服务器项目中的一些参数,并通过命令行参数来动态调整这些配置。

class Config
{
public:
    Config();
    ~Config(){};

    void parse_arg(int argc, char*argv[]);	// 使用 getopt 函数来解析命令行参数。

    // 成员函数
    int PORT;           // 端口号 (默认为9006)

    int LOGWrite;       // 日志写入方式 (默认同步写入且不关闭)

    int TRIGMode;       // 触发组合模式

    int LISTENTrigmode; // listenfd触发模式

    int CONNTrigmode;   // connfd触发模式

    int OPT_LINGER;     // 优雅关闭链接

    int sql_num;        // 数据库连接池数量

    int thread_num;     // 线程池内的线程数量

    int close_log;      // 是否关闭日志

    int actor_model;    // 并发模型选择 (默认使用 Proactor 模式)
};

构造函数

/*构造函数*/
Config::Config(){
    
    PORT = 9006;        // 端口号,默认9006

    LOGWrite = 0;       // 日志写入方式,默认同步

    TRIGMode = 0;       // 触发组合模式,默认listenfd LT + connfd LT

    LISTENTrigmode = 0; // listenfd触发模式,默认LT

    CONNTrigmode = 0;   // connfd触发模式,默认LT

    OPT_LINGER = 0;     // 优雅关闭链接,默认不使用

    sql_num = 8;        // 数据库连接池数量,默认8

    thread_num = 8;     // 线程池内的线程数量,默认8

    close_log = 0;      // 关闭日志,默认不关闭

    actor_model = 0;    // 并发模型,默认是proactor
}
void Config::parse_arg(int argc, char*argv[]){
    int opt;
    const char *str = "p:l:m:o:s:t:c:a:";
    while ((opt = getopt(argc, argv, str)) != -1)
    {
        switch (opt)
        {
        case 'p':
            PORT = atoi(optarg);
            break;
        case 'l':
            LOGWrite = atoi(optarg);
            break;
        case 'm':
            TRIGMode = atoi(optarg);
            break;
        case 'o':
            OPT_LINGER = atoi(optarg);
            break;
        case 's':
            sql_num = atoi(optarg);
            break;
        case 't':
            thread_num = atoi(optarg);
            break;
        case 'c':
            close_log = atoi(optarg);
            break;
        case 'a':
            actor_model = atoi(optarg);
            break;
        default:
            break;
        }
    }
}

使用 parse_arg 方法解析命令行参数

选项字符串 str"p:l:m:o:s:t:c:a:" 定义了可接受的选项,每个字母代表一个选项,后面的冒号 : 表示该选项需要一个参数。

解析循环:使用 while 循环调用 getopt,逐个解析命令行参数。

选项处理:根据解析到的选项字符,使用 atoi(optarg) 将字符串转换为整数,并赋值给对应的配置变量。

如果不传入任何命令行参数,程序将会使用在 Config 类构造函数中设置的默认值。因为在 parse_arg 函数中,如果没有提供任何参数,getopt 函数会立即返回 -1,因此不会对配置对象的成员变量进行任何修改。

/* 命令行参数解析 */
void Config::parse_arg(int argc, char*argv[]){
    int opt;
    const char *str = "p:l:m:o:s:t:c:a:";
    while ((opt = getopt(argc, argv, str)) != -1)
    {
        switch (opt)
        {
        case 'p':
        {
            PORT = atoi(optarg);
            break;
        }
        case 'l':
        {
            LOGWrite = atoi(optarg);
            break;
        }
        case 'm':
        {
            TRIGMode = atoi(optarg);
            break;
        }
        case 'o':
        {
            OPT_LINGER = atoi(optarg);
            break;
        }
        case 's':
        {
            sql_num = atoi(optarg);
            break;
        }
        case 't':
        {
            thread_num = atoi(optarg);
            break;
        }
        case 'c':
        {
            close_log = atoi(optarg);
            break;
        }
        case 'a':
        {
            actor_model = atoi(optarg);
            break;
        }
        default:
            break;
        }
    }
}

getopt 函数

getopt 函数是用于解析命令行参数的函数,通常在 C/C++ 程序中使用。它可以方便地处理带有选项的命令行参数,使程序支持类似于 Unix 风格的命令行接口。

#include <unistd.h>

int getopt(int argc, char * const argv[], const char *optstring);

参数说明

  • argc:命令行参数的数量。
  • argv:命令行参数的数组。
  • optstring:选项字符,定义了可接受的选项字符及其是否需要参数。

返回值

  • 成功时,返回解析到的选项字符。
  • 如果所有选项解析完毕,返回 -1
  • 如果遇到未定义的选项字符,返回 ?

optstring格式

  • 每个字符代表一个可接受的选项。例如,"abc" 表示接受 -a-b-c 选项。
  • 如果一个选项需要参数,在其后加上冒号 :。例如,"a:b" 表示 -a 需要参数,-b 不需要参数。
  • 如果一个选项的参数是可选的,在其后加上两个冒号 ::。这种用法是 GNU 扩展,不是标准 C 库的一部分。

使用步骤

  1. 定义选项字符串:根据程序需要,定义可接受的选项及其参数要求。
  2. 循环调用getopt:使用 while 循环,不断调用 getopt,直到返回 -1
  3. 处理选项:在循环内,使用 switch 语句,根据返回的选项字符执行相应的操作。
  4. 处理非选项参数:在所有选项解析完毕后,optind 指向第一个非选项参数,可以继续处理剩余的命令行参数。

示例代码

以下面的代码为例:

#include <iostream>
#include <unistd.h>

int main(int argc, char *argv[]) {
    int opt;

    /* 
        a: 需要参数
        b: 可选参数
        c: 不需要参数
     */
    std::string optString = "a:b::c";

    // 禁止 getopt 自动打印错误信息
    opterr = 0;

    while ((opt = getopt(argc, argv, optString.c_str())) != -1) {
        switch (opt) {
            case 'a':
                std::cout << "选项 -a,参数值:" << optarg << std::endl;
                break;
            case 'b':
                if (optarg)
                    std::cout << "选项 -b,参数值:" << optarg << std::endl;
                else
                    std::cout << "选项 -b,没有提供参数" << std::endl;
                break;
            case 'c':
                std::cout << "选项 -c,不需要参数" << std::endl;
                break;
            case '?':
                std::cerr << "未知选项:" << char(optopt) << std::endl;
                break;
            default:
                break;
        }
    }

    // 处理非选项参数
    for (int index = optind; index < argc; index++)
        std::cout << "非选项参数:" << argv[index] << std::endl;

    return 0;
}

运行:./getopt -a value1 -b value2 -c arg1 arg2

结果如下:

运行:./getopt -a value1 -bvalue2 -c arg1 arg2

结果如下:

在使用 getopt 函数并且定义了可选参数(使用 ::)的选项时,如果选项参数与选项之间有空格(例如 -b value2),那么 getopt 不会将 value2 视为 -b 的参数,而是将其作为非选项参数处理。

函数优化

原来的代码,只是实现了一个很简单的命令行参数解析逻辑,在一些情况下,如:

  • 提供了未知选项。改进方法:?分支中 处理 getopt 返回的 ?,提示用户输入了未知选项。
  • 提供了选项但是没有提供对应的参数。改进方法:在optstring 的开头加入冒号

optstring(选项字符串)中,开头的冒号 : 非常重要。它改变了 getopt 函数的错误处理方式。
当选项需要参数但未提供参数时:
如果 optstring 以冒号开头(如 :p:l:m:o:s:t:c:a:h),getopt 将返回 :,并将 optopt 设置为缺少参数的选项字符。
如果 optstring 不以冒号开头(如 p:l:m:o:s:t:c:a:h),getopt 将返回 ?,并将 optopt 设置为出错的选项字符。

  • -p 提供的端口号非法(端口号小于 0 或者大于 65535)。改进方法:PORT <= 0 || PORT > 65535,限制 -p 提供的参数范围
  • 参数格式非法(传入的字符串不是数字字符串):atoi 对非数字字符串会返回 0,无法检测输入是否为有效数字。如果用户输入非数字字符(如 -p abc),程序可能会继续运行,但使用了错误的参数值。 改进方法:使用 strtol

这些情况下,可能导致无法正确解析参数。

此外,还可以加入一个帮助选项-h,用于打印帮助信息:

/* 显示帮助信息 */
void Config::display_usage() {
    printf(
        "用法: server [选项]...\n"
        "选项列表:\n"
        "  -p <端口号>           设置服务器监听端口号 (默认: 9006)\n"
        "  -l <日志写入方式>     设置日志写入方式 (0: 同步, 1: 异步, 默认: 0)\n"
        "  -m <触发模式>         设置触发模式 (0~3, 默认: 0)\n"
        "                         0: listenfd LT + connfd LT\n"
        "                         1: listenfd LT + connfd ET\n"
        "                         2: listenfd ET + connfd LT\n"
        "                         3: listenfd ET + connfd ET\n"
        "  -o <优雅关闭连接>     是否使用优雅关闭连接 (0: 不使用, 1: 使用, 默认: 0)\n"
        "  -s <数据库连接数>     设置数据库连接池连接数 (默认: 8)\n"
        "  -t <线程数>           设置线程池内线程数量 (默认: 8)\n"
        "  -c <关闭日志>         是否关闭日志 (0: 不关闭, 1: 关闭, 默认: 0)\n"
        "  -a <并发模型>         选择并发模型 (0: Proactor, 1: Reactor, 默认: 0)\n"
        "  -h                    显示此帮助信息\n"
        "示例:\n"
        "  server -p 8080 -t 16 -c 1\n"
    );
}

改进后的完整代码如下:

#include "config.h"

/* 构造函数 */
Config::Config(){
    
    PORT = 9006;        // 端口号,默认9006

    LOGWrite = 0;       // 日志写入方式,默认同步

    TRIGMode = 0;       // 触发组合模式,默认listenfd LT + connfd LT

    LISTENTrigmode = 0; // listenfd触发模式,默认LT

    CONNTrigmode = 0;   // connfd触发模式,默认LT

    OPT_LINGER = 0;     // 优雅关闭链接,默认不使用

    sql_num = 8;        // 数据库连接池数量,默认8

    thread_num = 8;     // 线程池内的线程数量,默认8

    close_log = 0;      // 关闭日志,默认不关闭

    actor_model = 0;    // 并发模型,默认是proactor
}

/* 显示帮助信息 */
void Config::display_usage() {
    printf(
        "用法: server [选项]...\n"
        "选项列表:\n"
        "  -p <端口号>           设置服务器监听端口号 (默认: 9006)\n"
        "  -l <日志写入方式>     设置日志写入方式 (0: 同步, 1: 异步, 默认: 0)\n"
        "  -m <触发模式>         设置触发模式 (0~3, 默认: 0)\n"
        "                         0: listenfd LT + connfd LT\n"
        "                         1: listenfd LT + connfd ET\n"
        "                         2: listenfd ET + connfd LT\n"
        "                         3: listenfd ET + connfd ET\n"
        "  -o <优雅关闭连接>     是否使用优雅关闭连接 (0: 不使用, 1: 使用, 默认: 0)\n"
        "  -s <数据库连接数>     设置数据库连接池连接数 (默认: 8)\n"
        "  -t <线程数>           设置线程池内线程数量 (默认: 8)\n"
        "  -c <关闭日志>         是否关闭日志 (0: 不关闭, 1: 关闭, 默认: 0)\n"
        "  -a <并发模型>         选择并发模型 (0: Proactor, 1: Reactor, 默认: 0)\n"
        "  -h                    显示此帮助信息\n"
        "示例:\n"
        "  server -p 8080 -t 16 -c 1\n"
    );
}

void Config::parse_arg(int argc, char* argv[]) {
    int opt;

    // 设置 optstring:选项字符
    const char *str = ":p:l:m:o:s:t:c:a:h";
    while ((opt = getopt(argc, argv, str)) != -1) {
        switch (opt) {
            
            case 'p': // 设置端口号,范围应在 1 到 65535 之间。
                {
                    char *endptr;
                    PORT = strtol(optarg, &endptr, 10);
                    if (*endptr != '\0' || PORT <= 0 || PORT > 65535) {
                        fprintf(stderr, "无效的端口号:%s\n", optarg);
                        exit(EXIT_FAILURE);
                    }
                }
                break;
            
            case 'l':
                {
                    char *endptr;
                    LOGWrite = strtol(optarg, &endptr, 10);
                    if (*endptr != '\0' ) {
                        fprintf(stderr, "无效的日志写入方式:%s\n", optarg);
                        exit(EXIT_FAILURE);
                    }
                }
                break;
            
            case 'm':
                {
                    char *endptr;
                    TRIGMode = strtol(optarg, &endptr, 10);
                    if (*endptr != '\0' ) {
                        fprintf(stderr, "无效的触发模式:%s\n", optarg);
                        exit(EXIT_FAILURE);
                    }
                }
                break;

            case 'o':
                {
                    char *endptr;
                    OPT_LINGER = strtol(optarg, &endptr, 10);
                    if (*endptr != '\0' ) {
                        fprintf(stderr, "无效的优雅关闭选项:%s\n", optarg);
                        exit(EXIT_FAILURE);
                    }
                }
                break;

            case 's':
                {
                    char *endptr;
                    sql_num = strtol(optarg, &endptr, 10);
                    if (*endptr != '\0' || sql_num <= 0) {
                        fprintf(stderr, "无效的数据库连接池数量:%s,应为正整数\n", optarg);
                        exit(EXIT_FAILURE);
                    }
                }
                break;

            case 't':
                {
                    char *endptr;
                    thread_num = strtol(optarg, &endptr, 10);
                    if (*endptr != '\0' || thread_num <= 0) {
                        fprintf(stderr, "无效的线程数量:%s,应为正整数\n", optarg);
                        exit(EXIT_FAILURE);
                    }
                }
                break;

            case 'c':
                {
                    char *endptr;
                    close_log = strtol(optarg, &endptr, 10);
                    if (*endptr != '\0') {
                        fprintf(stderr, "无效的日志关闭选项:%s\n", optarg);
                        exit(EXIT_FAILURE);
                    }
                }
                break;

            case 'a':
                {
                    char *endptr;
                    actor_model = strtol(optarg, &endptr, 10);
                    if (*endptr != '\0' ) {
                        fprintf(stderr, "无效的并发模型选项:%s\n", optarg);
                        exit(EXIT_FAILURE);
                    }
                }
                break;

            case 'h': // 显示帮助信息
                display_usage();
                exit(EXIT_SUCCESS);

            case ':': // 缺少参数
                fprintf(stderr, "选项 -%c 缺少一个参数。\n", optopt);
                exit(EXIT_FAILURE);

            case '?':
                fprintf(stderr, "输入了未定义的选项:-%c\n", optopt);
                exit(EXIT_FAILURE);

            default:
                fprintf(stderr, "解析选项时遇到未知错误\n");
                exit(EXIT_FAILURE);
        }
    }

    // 检查是否有非选项参数
    if (optind < argc) {
        fprintf(stderr, "存在多余的非选项参数:\n");
        for (int i = optind; i < argc; i++) {
            fprintf(stderr, "  %s\n", argv[i]);
        }
        exit(EXIT_FAILURE);
    }
}

优化效果

优化前,输入一个非法端口号启动服务器,如:./server -p -1

终端没有报错,但是服务器拒绝连接。

优化后,针对错误情况都提供了报错和退出处理:

优化后,输入 ./server -h 显示帮助信息:

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

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

相关文章

向量数据库 PieCloudVector 进阶系列丨打造音乐推荐系统

在上一篇内容中&#xff0c;我们介绍了 PieCloudVector 如何助力构建基于图片数据的商品推荐系统&#xff0c;详细描述从数据集的准备到数据向量化处理&#xff0c;再到向量数据的存储和相似性搜索的完整流程。本文将进一步探讨如何将 PieCloudVector 应用于音频数据&#xff0…

python之数据结构与算法(数据结构篇)-- 栈

一、栈的概念 这里我们不去了解教科书上面的“教条概念”&#xff0c;其实“栈”的概念和古代的时候的“客栈”是有异曲同工之妙的。 在这里我们把客栈看成“栈”&#xff0c;旅客看作“栈元素” 1.当旅客进来住店时&#xff0c;叫做“入栈”&#xff1b; 2.当旅客退房时&#…

Java调用chatgpt

目前openai的chatgpt在国内使用有一定难度&#xff0c;不过国内的大模型在大部分情况下已经不弱于chatgpt&#xff0c;而且还更便宜&#xff0c;又能解决国内最敏感的内容安全问题。本文后续以spring ai调用国内chatgpt厂商实现为例&#xff0c;讲解怎么构建一个java调用chatgp…

web前端多媒体标签设置(图片,视频,音频)以及图片热区(usemap)的设置

多媒体标签运用 在HTML中有以下常见多媒体标签&#xff1a; <img> &#xff08;图像标签&#xff09; - 作用&#xff1a;用于在网页中嵌入图像。 - 示例&#xff1a; <img src"image.jpg" alt"这是一张图片"> 。其中 src 属性指定图像的…

安卓开发之数据库的创建与删除

目录 前言&#xff1a;基础夯实&#xff1a;数据库的创建数据库的删除注意事项 效果展示&#xff1a;遇到问题&#xff1a;如何在虚拟机里面找到这个文件首先&#xff0c;找到虚拟机文件的位置其次&#xff0c;找到数据库文件的位置 核心代码&#xff1a; 前言&#xff1a; 安…

基于SSM+微信小程序的订餐管理系统(点餐2)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1、项目介绍 基于SSM微信小程序的订餐管理系统实现了管理员和用户。管理端实现了 首页、个人中心、用户管理、菜品分类管理、菜品信息管理、订单信息管理、配送信息管理、菜品评价管理、订单投诉管理、…

《AI在企业战略中的关键地位:以微软和阿里为例》

内容概要 在当今商业环境中&#xff0c;人工智能&#xff08;AI&#xff09;的影响力如滔滔洪水&#xff0c;愈演愈烈。文章将揭示AI在企业战略中的崛起&#xff0c;尤其以微软和阿里巴巴为代表的企业&#xff0c;这两家科技巨头通过不同方式&#xff0c;将智能技术融入其核心…

华为荣耀曲面屏手机下面空白部分设置颜色的方法

荣耀部分机型下面有一块空白区域&#xff0c;如下图红框部分 设置这部分的颜色需要在themes.xml里面设置navigationBarColor属性 <item name"android:navigationBarColor">android:color/white</item>

【ESP32】ESP-IDF开发 | I2C从机接收i2c_slave_receive函数的BUG导致程序崩溃解决(idf-v5.3.1版本)

1. 问题 在调试I2C外设的demo时&#xff0c;按照官方文档的描述调用相关API&#xff0c;烧录程序后发现程序会不断崩溃&#xff0c;系统log如下。 初步分析log&#xff0c;原因是访问到了不存在的地址。一开始我以为是自己的代码问题&#xff0c;反反复复改了几次都会出现同样的…

企业数字化转型实施中的挑战与解决方案:架构引领的战略路径

在企业推动数字化转型的过程中&#xff0c;通常会面临复杂的挑战。随着技术的不断演进和业务环境的变化&#xff0c;企业架构&#xff08;Enterprise Architecture, EA&#xff09;成为帮助企业应对这些挑战的关键工具。通过提供一个全面的战略蓝图&#xff0c;EA使企业能够在保…

桑基图在医学数据分析中的更复杂应用示例

桑基图&#xff08;Sankey Diagram&#xff09;能够有效地展示复杂的流动关系&#xff0c;特别适合用于医学数据分析中的多种转归和治疗路径的可视化。接下来&#xff0c;我们将构建一个稍微复杂的示例&#xff0c;展示不同疾病患者在治疗过程中的流动&#xff0c;以及他们的治…

[SICTF Round4] PWN

这PWN题似乎是给我出的&#xff0c;4个一血1个2血。密码又过于简单。逆向太难了又不大会。 Stack fengshui main可以溢出覆盖rbpret所以它每一步都需要移栈。 可用的ROP里没有pop rdi,在4004c0里有错位的01 5d c3 &#xff1a;add DWORD PTR [rbp-0x3d], ebx 并且有对应的p…

消息中间件类型介绍

ActiveMQ&#xff1a; ActiveMQ可是个老将了&#xff0c;它功能全面、稳定可靠&#xff0c;还支持多种协议和编程语言。如果你需要一个兼容性好、易于集成的消息中间件&#xff0c;ActiveMQ可是个不错的选择。 RabbitMQ&#xff1a; RabbitMQ以其简单易用和高性能著称。它支持丰…

【设计模式系列】组合模式(十二)

目录 一、什么是组合模式 二、组合模式的角色 三、组合模式的典型应用 四、组合模式在Mybatis SqlNode中的应用 4.1 XML映射文件案例 4.2 Java代码使用案例 一、什么是组合模式 组合模式&#xff08;Composite Pattern&#xff09;是一种结构型设计模式&#xff0c;其核…

Ghidra无头模式(自动化批处理执行重复性任务)

Ghidra无头模式&#xff08;自动化批处理执行重复性任务&#xff09; 与Ghidra GUI探索单个项目中的单个文件不同&#xff0c;Ghidra headless analyzer&#xff08;Ghidra无头分析器&#xff09;更加适合批处理和用脚本控制Ghidra。 &#xff08;一&#xff09;启动analyzeHea…

【大众点评】店铺评论 加密参数生成逆向分析

点击好评 https://www.dianping.com/ajax/json/shopDynamic/allReview 分析参数_token 直接搜_token 共17个&#xff0c;优先看和请求相关的 给第一个_token打上断点&#xff0c;然后切换评论&#xff0c;就直接断住了 n h(i, e.sendData) _token: n 现在给它打上断点&am…

Fsm3

采用读热码编写方式&#xff1a; module top_module(input clk,input in,input areset,output out); ////reg [3:0]A 4d0001;// reg [3:0]B 4d0010;//reg [3:0]C 4d0100;// reg [3:0]D 4d1000; //1、首先用读热码定义四个状态变量parameter A 4d0001 ,B 4d0010, C 4d01…

在腾讯云服务器上部署MaxKB项目(基于LLM大语言模型的知识库问答系统)

前言 一&#xff0c; MaxKB介绍 MaxKB是基于LLM大语言模型的知识库问答系统&#xff0c;旨在成为企业的最强大脑。它支持开箱即用&#xff0c;无缝嵌入到第三方业务系统&#xff0c;并提供多模型支持&#xff0c;包括主流大模型和本地私有大模型&#xff0c;为用户提供智能问…

【大众点评】加密参数生成逆向分析

点击好评 https://www.dianping.com/ajax/json/shopDynamic/allReview 分析参数_token 直接搜_token 共17个&#xff0c;优先看和请求相关的 给第一个_token打上断点&#xff0c;然后切换评论&#xff0c;就直接断住了 n h(i, e.sendData) _token: n 现在给它打上断点&am…

【Python+Pycharm】2024-Python安装配置教程

【PythonPycharm】2024-Python安装配置教程 一、下载装 Python 1、进入Python官网首页&#xff0c;下载最新的Python版本 Download Python | Python.org 选择对应版本下载 安装 测试安装情况 python如果安装失败 在系统环境变量添加安装路径 where pythonwin7安装路径添加…