2. 从服务器的主接口入手
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; // 端口号,默认9006LOGWrite = 0; // 日志写入方式,默认同步TRIGMode = 0; // 触发组合模式,默认listenfd LT + connfd LTLISTENTrigmode = 0; // listenfd触发模式,默认LTCONNTrigmode = 0; // connfd触发模式,默认LTOPT_LINGER = 0; // 优雅关闭链接,默认不使用sql_num = 8; // 数据库连接池数量,默认8thread_num = 8; // 线程池内的线程数量,默认8close_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 库的一部分。
使用步骤
- 定义选项字符串:根据程序需要,定义可接受的选项及其参数要求。
- 循环调用
getopt
:使用while
循环,不断调用getopt
,直到返回-1
。 - 处理选项:在循环内,使用
switch
语句,根据返回的选项字符执行相应的操作。 - 处理非选项参数:在所有选项解析完毕后,
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;elsestd::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; // 端口号,默认9006LOGWrite = 0; // 日志写入方式,默认同步TRIGMode = 0; // 触发组合模式,默认listenfd LT + connfd LTLISTENTrigmode = 0; // listenfd触发模式,默认LTCONNTrigmode = 0; // connfd触发模式,默认LTOPT_LINGER = 0; // 优雅关闭链接,默认不使用sql_num = 8; // 数据库连接池数量,默认8thread_num = 8; // 线程池内的线程数量,默认8close_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
显示帮助信息:
相关文章:

2. 从服务器的主接口入手
Webserver 的主函数 main.cpp,完成了哪些功能? #include "config.h"int main(int argc, char *argv[]) {string user "";string passwd "";string databasename "";Config config;config.parse_arg(argc, a…...

nginx上传文件超过限制大小、响应超时、反向代理请求超时等问题解决
1、文件大小超过限制 相关配置: client_max_body_size: Syntax:client_max_body_size size;Default:client_max_body_size 1m;Context:http, server, location 2、连接超时: proxy_read_timeout: Syntax:proxy_read_timeout time;Default…...

第16课 核心函数(方法)
掌握常用的内置函数及其用法。 数学类函数:abs、divmod、max、min、pow、round、sum。 类型转换函数:bool、int、float、str、ord、chr、bin、hex、tuple、list、dict、set、enumerate、range、object。 序列操作函数:all、any、filter、m…...

【工具变量】中国制造2025试点城市数据集(2000-2023年)
数据简介:《中国制造2025》是中国ZF于2015年5月8日印发的一项战略规划,旨在加快制造业的转型升级,提升制造业的质量和效益,实现从制造大国向制造强国的转变。该规划是中国实施制造强国战略的第一个十年行动纲领,明确提…...

vscode makfile编译
MinGW-w64下载安装 为了在 Windows 上安装 GCC,您需要安装 MinGW-w64。 MinGW-w64 是一个开源项目,它为 Windows 系统提供了一个完整的 GCC 工具链,支持编译生成 32 位和 64 位的 Windows 应用程序。 访问 MinGW-w64 的主页 mingw-w64.org…...
(四)PostgreSQL数据库操作示例
删除有外键约束的表 最近做数据库练习遇到一个问题,数据库里面有一个表,存在外键约束,我想要删除,所以必须先删除这些外键约束。 查询外键约束 查找外键约束:当你需要知道某个表的外键约束及其引用关系时࿰…...

Docker-微服务项目部署
环境准备 1.微服务项目 参考:通过网盘分享的文件:wolf2w_cloud.zip 链接: https://pan.baidu.com/s/1Lr4k6LPIJ59gVNA_DgKM_Q?pwdkjxt 提取码: kjxt 前端项目:trip-mgrsite-ui,trip-website-ui,trip-wenda-ui 服务项…...
测试Bug提交报告模板
撰写测试Bug提交说明时,清晰、详细和准确是至关重要的。这有助于开发团队快速理解问题、重现Bug并修复它。以下是一个测试Bug提交说明的模板,可以根据实际情况进行调整: 测试Bug提交说明 1. Bug基本信息 Bug编号:[系统自动生成…...

MybatisPlus - 核心功能
文章目录 1.MybatisPlus实现基本的CRUD快速开始常见注解常见配置 2.使用条件构建造器构建查询和更新语句条件构造器自定义SQLService接口 官网 MybatisPlus无侵入和方便快捷. MybatisPlus不仅仅可以简化单表操作,而且还对Mybatis的功能有很多的增强。可以让我们的开…...

小柴冲刺软考中级嵌入式系统设计师系列二、嵌入式系统硬件基础知识(6)嵌入式系统总线及通信接口
目录 越努力,越幸运! flechazo 小柴冲刺软考中级嵌入式系统设计师系列总目录 一、PCI、PCI-E 等接口基本原理与结构 1、PCI (1)高速性。 (2)即插即用性。 (3)可靠性。 (4)复杂性。 (5)自动配置。 (6)共享中断。 (7)扩展性好。 (8)多路复用。…...
利用字典对归一化后的数据0误差还原
假设我对精度要求很高,高到无法容忍有任何误差,那么我先将x按照大小排序,然后归一化,用字典将归一化前后的x存储下来,在深度学习时使用归一化后的x进行处理,但是最后画图等处理时,我用字典取出归…...

HarmonyOS:UIAbility组件概述
一、概述 UIAbility组件是一种包含UI的应用组件,主要用于和用户交互。 UIAbility的设计理念: 原生支持应用组件级的跨端迁移和多端协同。支持多设备和多窗口形态。 UIAbility划分原则与建议: UIAbility组件是系统调度的基本单元,…...

12寸半导体厂说的华夫区是什么意思
1\什么是华夫板 在半导体行业中,“华夫区”通常指的是“华夫板”(Waffle Slab),这是一种特殊设计的楼板,其表面具有许多均匀分布的孔洞,这些孔洞形成了回风通道,用于电子芯片厂房等对空气洁净度有极高要求的环境。华夫板的设计和施工对于保证洁净室的功能发挥至关重要。…...

数据结构之链式结构二叉树的实现(进阶版)
本篇文章主要讲解链式二叉树的层序遍历以及判断是否为一棵完全二叉树 二者将会用到之前学过的队列知识,是将队列和二叉树的整合 一、如何将之前已经写好的文件加入当前的编译界面 如图所示,打开我们需要加入文件所在的文件夹,找到我们要加…...
【高等数学】3-2多元函数积分学
1. 二重积分 可以想象你有一块不规则的平面薄板,它在一个平面区域上。二重积分就是用来求这个薄板的质量(假设薄板的面密度函数是)。 把区域划分成许多非常小的小方块(类似于把一块地划分成很多小格子),在每个小方块上,密度近似看成是一个常数,然后把每个小方块的质量加…...

【传知代码】智慧医疗:纹理特征VS卷积特征
🍑个人主页:Jupiter. 🚀 所属专栏:传知代码 欢迎大家点赞收藏评论😊 目录 论文概述纹理特征和深度卷积特征算法流程数据预处理方法纹理特征提取深度卷积特征提取分类网络搭建代码复现BLS_Model.py文件——分类器搭建py…...

Python-创建并调用自定义文件中的模块/函数
背景:在Python编程中,我们常常需要创建自己的专属文件,以便帮助我们更高效,快捷地完成任务。那么在Python中我们怎么创建并调用自己文件中的模块/函数呢? 在Python中调用自定义文件,通常是指调用自己编写的Python模块…...

Kali Linux
起源与背景 Kali Linux是一个基于Debian的开源Linux发行版,专门为信息安全工作者和渗透测试员设计。它是由Offensive Security Ltd.开发和维护的,作为BackTrack的继承者而诞生。BackTrack是一个流行的安全测试发行版,但为了提供更好的支持和…...

DiffusionDet: Diffusion Model for Object Detection—用于对象检测的扩散模型论文解析
DiffusionDet: Diffusion Model for Object Detection—用于对象检测的扩散模型论文解析 这是一篇发表在CVPR 2023的一篇论文,因为自己本身的研究方向是目标跟踪,之前看了一点使用扩散模型进行多跟踪的论文,里面提到了DiffusionDet因此学习一…...

深度学习基础知识-编解码结构理论超详细讲解
编解码结构(Encoder-Decoder)是一种应用广泛且高效的神经网络架构,最早用于序列到序列(Seq2Seq)任务,如机器翻译、图像生成、文本生成等。随着深度学习的发展,编解码结构不断演变出多种模型变体…...

网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...
Java 8 Stream API 入门到实践详解
一、告别 for 循环! 传统痛点: Java 8 之前,集合操作离不开冗长的 for 循环和匿名类。例如,过滤列表中的偶数: List<Integer> list Arrays.asList(1, 2, 3, 4, 5); List<Integer> evens new ArrayList…...
可靠性+灵活性:电力载波技术在楼宇自控中的核心价值
可靠性灵活性:电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中,电力载波技术(PLC)凭借其独特的优势,正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据,无需额外布…...
django filter 统计数量 按属性去重
在Django中,如果你想要根据某个属性对查询集进行去重并统计数量,你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求: 方法1:使用annotate()和Count 假设你有一个模型Item,并且你想…...

《通信之道——从微积分到 5G》读书总结
第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...
Neo4j 集群管理:原理、技术与最佳实践深度解析
Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...

【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)
🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...
三体问题详解
从物理学角度,三体问题之所以不稳定,是因为三个天体在万有引力作用下相互作用,形成一个非线性耦合系统。我们可以从牛顿经典力学出发,列出具体的运动方程,并说明为何这个系统本质上是混沌的,无法得到一般解…...