nginx 内存管理(二)
共享内存
- 共享内存结构与接口定义
- nginx共享内存在操作系统上的兼容性设计
- 互斥锁
- 锁的结构体
- 锁的一系列操作(core/ngx_shmtx.c)
- 创建锁
- 原子操作
- nginx的上锁操作
- 尝试加锁
- 获取锁
- 释放锁
- 强迫解锁
- 唤醒等待进程
- slab共享内存块管理
- nginx的slab大小规格
- 内存池结构体
- 共享内存池结构体slots
- 分配共享内存池

共享内存结构与接口定义
正常来说,通过malloc函数申请的内存都是进程私有的内存但是Linux会提供共享内存的系统调用,如mmap和munmap等
Nginx基于Linux提供的系统调用,封装了共享内存的数据结构以及共享内存的创建与释放函数,其共享内存结构和接口定义如下:
os/unix/ngx_shmem.h
typedef struct { u_char *addr; //指向申请的共享内存块首地址size_t size; //共享内存块大小ngx_str_t name; //共享内存块名字ngx_log_t *log; //共享内存块日志ngx_uint_t exists;//标志是否已经存在
} ngx_shm_t;//共享结构//以下共享接口
ngx_int_t ngx_shm_alloc(ngx_shm_t *shm);//创建共享内存块
void ngx_shm_free(ngx_shm_t *shm);//释放共享内存块
ngx_int_tngx_shm_alloc(ngx_shm_t *shm)
{
shm->addr = (u_char *) mmap(NULL, shm->size, PROT_READ|PROT_WRITE, MAP_ANON|MAP_SHARED, -1, 0); //创建
if (shm->addr == MAP_FAILED) { //错误处理ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno, "mmap(MAP_ANON|MAP_SHARED, %uz) failed", shm->size); return NGX_ERROR;
}
return NGX_OK;//成功,返回
}
void ngx_shm_free(ngx_shm_t *shm)
{
if (munmap((void *) shm->addr, shm->size) == -1)//是否成功
{ //失败处理,记录ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno, "munmap(%p, %uz) failed", shm->addr, shm->size);
}
}
nginx共享内存在操作系统上的兼容性设计
#if (NGX_HAVE_MAP_ANON)//匿名共享内存
………
#elif (NGX_HAVE_MAP_DEVZERO)//文件共享内存
………
#elif (NGX_HAVE_SYSVSHM)//IPC System V共享内存
………
#endif
互斥锁
- 并发进程访问共享内存时需要加锁。nginx提供了互斥锁的机制,保证了正确的共享内存的访问。nginx的进程主要是通过ngx_shmtx_t进行加锁、解锁等操作。
- nginx实现的时候,如果操作系统提供原子操作机制,就使用操作系统的原子操作实现互斥锁,否则nginx采用文件锁实现互斥。
互斥锁模型

锁的结构体
//core/ngx_shmtx.h
typedef struct { ngx_atomic_t lock;//0为锁开(空闲),其它(进程号)已上锁#if (NGX_HAVE_POSIX_SEM) //如果有SEM信号量ngx_atomic_t wait;//等待共享内存进程总数#endif
} ngx_shmtx_sh_t;
上锁、解锁的结构体模型:
typedef struct {#if (NGX_HAVE_ATOMIC_OPS) //若有原子操作…………#if (NGX_HAVE_POSIX_SEM) //如果有信号量 …………#endif#else …………#endif ngx_uint_t spin;
} ngx_shmtx_t;
typedef struct {#if (NGX_HAVE_ATOMIC_OPS) //若有原子操作ngx_atomic_t *lock;//进程内指向共享内存锁的地址#if (NGX_HAVE_POSIX_SEM) //如果有信号量 ngx_atomic_t *wait; //指向共享内存等待进程总数ngx_uint_t semaphore; //是否使用信号量,1使用sem_t sem;//sem_t信号量,可用于线程之中,也可用于进程#endif#else //操作系统无原子操作和信号量支持,用文件ngx_fd_t fd; u_char *name;#endif ngx_uint_t spin;
} ngx_shmtx_t;
锁的一系列操作(core/ngx_shmtx.c)
- ngx_int_t ngx_shmtx_create(ngx_shmtx_t *mtx, ngx_shmtx_sh_t *addr, u_char *name);//创建锁
- void ngx_shmtx_destroy(ngx_shmtx_t *mtx);//销毁锁
- void ngx_shmtx_lock(ngx_shmtx_t *mtx);//获取锁
- ngx_uint_t ngx_shmtx_trylock(ngx_shmtx_t *mtx);//尝试加锁
- void ngx_shmtx_unlock(ngx_shmtx_t *mtx);//释放锁
- ngx_uint_t ngx_shmtx_force_unlock(ngx_shmtx_t *mtx, ngx_pid_t pid);//强制解锁
- static voidngx_shmtx_wakeup(ngx_shmtx_t *mtx)//唤醒等进程
临界区管理的基本思路
①找到临界区
②在临界区前面增加一段用于进行检查的代码,当不满足进入临界区的条件,就不进入,直到满足条件才进入,称为进入区(entry section)。
③在临界区后面加上一段称为离开区(exit section)的代码,作为善后处理。基本形式如下:

创建锁
ngx_int_t ngx_shmtx_create( ngx_shmtx_t *mtx,ngx_shmtx_sh_t *addr, u_char *name);//创建锁//ngx_shmtx_t *mtx,是进程操作锁结构地址
//ngx_shmtx_sh_t *addr,是共享内存中保存的锁结构地址
//u_char *name,名字(用于区别不同锁)地址{
mtx->lock = &addr->lock; //将共享内存锁信息储存到进程操作锁结构体中
if (mtx->spin == (ngx_uint_t) -1) { return NGX_OK; }
mtx->spin = 2048;//自旋次数指定#if (NGX_HAVE_POSIX_SEM) //如果是信号量,初始化sem为1,并将semaphore设为1mtx->wait = &addr->wait; if (sem_init(&mtx->sem, 1, 0) == -1) { ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_errno, "sem_init() failed"); } else { mtx->semaphore = 1; }
#endif
return NGX_OK;
}
- 进程操作锁结构需要获得(保存)共享内存锁的信息,对于自旋锁,
1)保存共享内存锁的lock;
2设置自旋锁的自旋次数;以便于后续进行加锁、解锁等操作;
mtx->lock = &addr->lock; if (mtx->spin == (ngx_uint_t) -1) { //已经加锁了 ?return NGX_OK; }
mtx->spin = 2048;//nginx设置的进程自旋次数
- 对于信号量,
1)保存共享内存锁的保存wait;
2设置信号量的semaphore或sem的值;以便于后续进行加锁、解锁等操作;
mtx->wait = &addr->wait; //保存指向保存共享内存进程总数指针
if (sem_init(&mtx->sem, 1, 0) == -1) //线程信号量初始化失败
{ ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_errno, "sem_init() failed");
}
else // 线程信号量初始化成功,初始化semaphore为1
{ mtx->semaphore = 1; //使用信号量
}
- 其它可能需要记录的调试信息以及可能的错误处理等
原子操作
计算机系统并发的基础
- 两个原子操作
ngx_atomic_cmp_set(a,old,new):如果*a==old,将*a赋值为new,返回1。否则返回0。ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid),(原子操作)若*mtx->lock为0,即将*mtx->lock赋值为ngx_pid。
ngx_atomic_fetch_add(old,v):将*old加上v,并返回*old。ngx_atomic_fetch_add(mtx->wait, 1),将*mtx->wait加上1,并返回加之前的*mtx->wait值。
nginx的上锁操作
当共享内存lock为0(表示空闲)时可以上锁。对于上锁的操作,Nginx将其标准化为将lock(当其为0时)设为进程的PID。即
*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid)。
尝试加锁
ngx_uint_tngx_shmtx_trylock(ngx_shmtx_t *mtx)
{ return (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid));
}
获取锁
- 进程自旋锁的获取
-
当共享内存lock为0(表示空闲)时可以上锁。对于上锁的操作,nginx将其标准化为将lock(当其为0时)设为进程的PID。即
*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid)。 -
因为*mtx->lock 为0时,可能有很多进程都来上锁,但只能有一个进程会成功上锁。因此对上锁进程来讲,以上上锁操作可能不成功。
-
此时,当有多个CPU时,上锁进程可以等待一段T时间后,再次尝试上锁操作。Ngnix对T的构造有其独特的方法。
-
上锁失败,放弃使用CPU
-
void ngx_shmtx_lock(ngx_shmtx_t *mtx)//自旋锁
{ngx_uint_t i, n; //初始化变量
ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0, "shmtx lock");
for(;;){//不断循环进行自旋方式加锁;#if (NGX_HAVE_POSIX_SEM)信号量方式加锁,wait记录等待共享进程总数,等待进程挂入sem等待队列;#endifngx_sched_yield();//优化方式放弃CPU}
}//自旋式加锁
for ( ;; ) { if (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid)) { return; } //成功上锁返回 if (ngx_ncpu > 1) { //当有多个CPU时,等待T时间后,再次尝试上锁 for (n = 1; n < mtx->spin; n <<= 1){//构造等待时间T,再多次尝试上锁{ for (i = 0; i < n; i++) { //每次都有等待时间T ,每次内循环等待次数不一样ngx_cpu_pause(); //(借用CPU机制)优化自旋等待} if (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid)) { return; } //再次尝试上锁若成功,则返回} }
ngx_sched_yield();//(优化)上锁失败,放弃使用CPU 。调度选中后,再次自旋上锁(为啥?)。
}
- 信号量处理锁的获取
如果是信号量:- 等待共享内存进程总数(预先)(原子性操作)加一;
- 当lock为0(表示空闲)时可以上锁。按照nginx标准化上锁操作,也就是将lock(当其为0时)设为进程的PID。即
*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid)。
如果成功,将等待共享内存进程数减一(因已成功上锁,预计加需扣除),返回。
(void) ngx_atomic_fetch_add(mtx->wait, 1); //原子操作预加一if (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid)){ //上锁成功了 (void) ngx_atomic_fetch_add(mtx->wait, -1); //原子操作减一 return; //返回}
- 如果上锁失败,将(该加锁进程)挂入sem的等待队列中。由于挂入sem的等待队列操作可能失败,为了确保1)中的加一操作与实际等待进程总数一致性,需要不断尝试挂入等待队列操作,直至成功挂入为止。否则数据将不一致。挂入等待队列的某进程,由释放锁某进程唤醒。
while (sem_wait(&mtx->sem) == -1) {//如果失败,再次进行挂入sem等待队列操作 ngx_err_t err; err = ngx_errno; //获取错误原因 if (err != NGX_EINTR) { //若是系统原因,进行错误日志处理后,终止尝试 ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, err, "sem_wait() failed while waiting on shmtx");break; } } ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0, "shmtx awoke"); continue; //进行下一个循环
释放锁
void ngx_shmtx_unlock(ngx_shmtx_t *mtx);//释放锁
{ if (mtx->spin != (ngx_uint_t) -1) //调试信息处理{ ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0,"shmtx unlock"); } if (ngx_atomic_cmp_set(mtx->lock, ngx_pid, 0)) {//将lock设为0就是释放ngx_shmtx_wakeup(mtx); //唤醒等待进程}
}
强迫解锁
ngx_uint_tngx_shmtx_force_unlock(ngx_shmtx_t *mtx, ngx_pid_t pid)//强迫解锁
{ ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0, “shmtx forced unlock”); //记录调试信息if (ngx_atomic_cmp_set(mtx->lock, pid, 0)) { //共享内存lock为0(空闲) ngx_shmtx_wakeup(mtx); //唤醒等待共享进程进程 return 1; } return 0;//强制失败,返回0;
}
唤醒等待进程
- 如果有信号量支持://因为只有有信号量支持时,才有sem等待队列
- 如果没有标记使用信号量,(没有构造等待队列)返回。
#if (NGX_HAVE_POSIX_SEM)//由信号量支持
if (!mtx->semaphore) { return; }
………//剩下的其它操作实现
#endif
- 不断使用ngx_atomic_cmp_set(mtx->wait, wait, wait - 1)对mtx->wait减一操作,直至成功将mtx->wait(原子操作)减一。
for ( ;; ) { //不断尝试进行以下方式原子操作减一wait = *mtx->wait; if ((ngx_atomic_int_t) wait <= 0) { return; } //没有等待共享内存进程,返回。if (ngx_atomic_cmp_set(mtx->wait, wait, wait - 1)) { break; //成功原子操作减一,终止尝试原子减一。 } }
- 从sem等待队列中唤醒一个进程;
if (sem_post(&mtx->sem) == -1)
{ //失败唤醒一个进程,错误日志处理ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_errno, "sem_post() failed while wake shmtx"); }
slab共享内存块管理
nginx设计与实现了一种基于slab理念的共享内存块机制,并提供了创建共享内存块、从共享内存块中申请与释放内存的API。其结构体包括(core/ngx_slab.h以及core/ngx_slab.c):
- ngx_slab_page_s:内存块管理结构体
ngx_slab_stat_t:内存页使用信息管理结构体
ngx_slab_pool_t:共享内存块结构体
typedef struct ngx_slab_page_s ngx_slab_page_t;
nginx的slab大小规格

内存池结构体
typedef struct ngx_slab_page_s ngx_slab_page_t;
struct ngx_slab_page_s
{ uintptr_t slab; ngx_slab_page_t *next; //后向uintptr_t prev;//前向
};typedef struct { ngx_uint_t total; //总数 ngx_uint_t used; //使用总数 ngx_uint_t reqs; //请求总数 ngx_uint_t fails;//失败总数
} ngx_slab_stat_t;// 共享内存池结构体
typedef struct { ngx_shmtx_sh_t lock; //内存锁 size_t min_size; //可以分配最小内存大小,即为8size_t min_shift; //最小slab内存的幂数,即min_size=2^ min_shiftngx_slab_page_t *pages; //指向第一页的管理结构ngx_slab_page_t *last; //指向最后页的管理结构ngx_slab_page_t free; //指向空闲首页的一个结点ngx_slab_stat_t *stats; //指向记录各种规格slab统计信息链表ngx_uint_t pfree; //空闲总页数u_char *start; //空闲页始址u_char *end;//空闲末址ngx_shmtx_t mutex; //进程操作锁结构u_char *log_ctx; u_char zero; unsigned log_nomem:1; void *data; void *addr;//共享内存池结构地址
} ngx_slab_pool_t;

共享内存池结构体slots
1.初始化共享内存池管理结构体各数据成员的值,理清控制管理关系。
2.分出控制管理结构后,剩余的即为可以共享分配的内存池。
管理不同规格的ngx_slab_page_t的首地址,nginx用宏ngx_slab_slots(pool)描述了这一大小位置关系:

(按情形)初始化共享池为0xA5(除共享池管理结构外)

初始化管理不同大小slab的ngx_slab_page_t

(按情形)初始化ngx_slab_stat_t

计算总页数pages

初始化pool的pages

初始化pool的free

初始化管理空页的首个ngx_slab_page_t

初始化pool的start

初始化pool的start,因对齐,修正总空闲数

初始化pool的其它成员

分配共享内存池
- 理论上,每个大小为KB的系统物理页,可以包含k/m个大小为mB规格的slab块。
- 为了标明一个系统物理页中含有的大小为mB规格slab块的占有情况,Nginx为每个系统物理页使用bitmap描述其含有的每个slab块是否空闲。
- 这样,每个大小为KB的系统物理页,需要k/m位描述其每个slab的空闲占有情况,如位1表示占有,如位0表示空闲。
- 对于小块内存(
大小8Byte~32Byte),需要较多位(512b~128b)。nginx在内存首页开辟固定区域,码放这些bitmap。 - 对于精确内存(大小为64Byte),需要64b。nginx使管理内存页的ngx_slab_page_t结构体的slab字段作为bitmap。
- 对于大块内存(
大小128Byte~2048Byte),需要(32b~2b)使用slab的前32位作为bitmap
void* ngx_slab_alloc(ngx_slab_pool_t *pool, size_t size)
{void *p; ngx_shmtx_lock(&pool->mutex);//互斥分配p = ngx_slab_alloc_locked(pool, size); ngx_shmtx_unlock(&pool->mutex); //互斥分配return p;
}
相关文章:
nginx 内存管理(二)
共享内存 共享内存结构与接口定义nginx共享内存在操作系统上的兼容性设计互斥锁锁的结构体锁的一系列操作(core/ngx_shmtx.c)创建锁 原子操作nginx的上锁操作尝试加锁获取锁释放锁强迫解锁唤醒等待进程 slab共享内存块管理nginx的slab大小规格内存池结构…...
【DevChat】智能编程助手 - 使用评测
写在前面:博主是一只经过实战开发历练后投身培训事业的“小山猪”,昵称取自动画片《狮子王》中的“彭彭”,总是以乐观、积极的心态对待周边的事物。本人的技术路线从Java全栈工程师一路奔向大数据开发、数据挖掘领域,如今终有小成…...
Geek challenge 2023 EzHttp
打开链接需要使用post请求提交username和password 查看源码得到提示,爬虫想到robots协议 访问robots.txt 访问得到的路径:/o2takuXXs_username_and_password.txt 拿到用户名和密码: username:admin password:dm1N123456r00t# 进行post传参…...
matlabR2021a正版免费使用
目录 matlab介绍: 安装: matlab介绍: MATLAB(Matrix Laboratory的缩写)是一种高级技术计算和编程环境,由MathWorks公司开发。它在科学、工程、数据分析和数学建模领域中广泛应用,为用户提供了…...
天气数据可视化平台-计算机毕业设计vue
天气变幻无常,影响着我们生活的方方面面,应用天气预报信息可以及时了解天气的趋势,给人们的工作、生活等带来便利,也可以为我们为未来的事情做安排和打算,所以一个精准的、易读 通过利用 程序对气象网站大量的气象信息…...
揭秘Java switch语句中的case穿透现象
揭秘Java switch语句中的case穿透现象 1. switch 语句简介2. case穿透现象的原因关于 goto 3. switch和if的区别4. 总结 导语:在 Java 开发中,我们经常使用switch语句来进行条件判断和分支选择。然而,有一个令人困惑的现象就是,当…...
Java-API简析_java.io.FilterOutputStream类(基于 Latest JDK)(浅析源码)
【版权声明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) https://blog.csdn.net/m0_69908381/article/details/134106510 出自【进步*于辰的博客】 因为我发现目前,我对Java-API的学习意识比较薄弱…...
C语言 每日一题 PTA 10.29 day7
1.特殊a串数列求和 给定两个均不超过9的正整数a和n,要求编写程序求a aa aaa⋯ aa⋯a(n个a)之和。 输入格式: 输入在一行中给出不超过9的正整数a和n。 输出格式: 在一行中按照“s 对应的和”的格式输出。 思路 n…...
持续集成部署-k8s-服务发现-Ingress 路径匹配与虚拟主机匹配
持续集成部署-k8s-服务发现-Ingress 路径匹配与虚拟主机匹配 1. 安装 Ingress-Nginx2. 创建要代理的 Service3. 创建一个新的 Ingress-Nginx1. 安装 Ingress-Nginx 要使用 Ingress-Nginx 首先第一步是需要先安装它,安装的步骤可以参考:持续集成部署-k8s-服务发现-Ingress 2…...
selenium工作原理和反爬分析
一、 Selenium Selenium是最广泛使用的开源Web UI(用户界面)自动化测试套件之一,支持并行测试执行。Selenium通过使用特定于每种语言的驱动程序支持各种编程语言。Selenium支持的语言包括C#,Java,Perl,PHP,Python和Ru…...
windows电脑安装系统后固态硬盘和机械硬盘的盘符号顺序显示错乱,解决方法
一、场景 由于电脑磁盘是SSD固态硬盘自己拓展的1T机械硬盘组成,固态硬盘分为C、D两个盘区,机械硬盘分为E、F两个盘区。为了提升运行速度,系统安装在C盘,安装完成后按照习惯盘区顺应该为C、D、E、F,但实际情况却是D、E…...
自定义控件的子控件布局(onLayout()方法)
onLayout()方法用于指定布局中子控件的位置,该方法通常在自定义的ViewGroup容器中重写。 重写onLayout()方法中的常用方法: getChildCount() 获取子控件数量 getChildAt( int index ) 获取指定index的子控件,返回View view.getVisibilit…...
vscode提取扩展出错xhr
在 Visual Studio Code (VSCode) 中提取扩展出现 XHR 错误通常意味着在下载扩展或进行扩展管理操作时出现了网络请求问题。XHR (XMLHttpRequest) 是一种用于在浏览器中进行 HTTP 请求的技术,通常用于获取数据或资源。在 VSCode 中,它也可用于管理扩展的下…...
Docker 笔记(上篇)
Docker 概述 Docker 概念 Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中,然后发布到任何流行的 Linux或Windows操作系统的机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之…...
python自动化测试(六):唯品会商品搜索-练习
目录 一、配置代码 二、操作 2.1 输入框“运动鞋” 2.2 点击搜索按钮 2.3 选择品牌 2.4 选择主款 2.5 适用性别 2.6 选择尺码 2.7 选择商品:(通过css的属性去匹配) 2.8 点击配送地址选项框 一、配置代码 # codingutf-8 from selen…...
深度强化学习用于博弈类游戏-基础测试与说明【1】
深度强化学习用于博弈类游戏-基础【1】 1. 强化学习方法2. 强化学习在LOL中的应⽤2.1 环境搭建2.2 游戏特征元素提取1)小地图人物位置:2)人物血量等信息3)在整个图像上寻找小兵、防御塔的位置4)自编码器提取3. 策略梯度算法简介参考资料1. 强化学习方法 伴随着人工智能的潮起…...
通过requests库使用HTTP编写的爬虫程序
使用Python的requests库可以方便地编写HTTP爬虫程序。以下是一个使用requests库的示例: import requests# 发送HTTP GET请求 response requests.get("http://example.com")# 检查响应状态码 if response.status_code 200:# 获取响应内容html response.…...
550MW发电机变压器组继电保护的整定计算及仿真
摘要 电力系统继电保护设计是根据系统接线图及要求选择保护方式,进行整定计算,电力系统继电保护的设计与配置是否合理直接影响到电力系统的安全运行。如果设计与配置不当,保护将不能正确工作,会扩大事故停电范围,造成…...
Linux 命令|服务器相关
1. 在公共 linux 上创建 python 虚拟环境 【精选】在公共Linux服务器上创建自己的python虚拟环境_服务器创建自己的环境-CSDN博客 2. 查看现存的状态,看有没有程序在跑 nvidia-smi命令详解-CSDN博客 3. 上传本地文件到服务器 在本地 Mac 计算机的终端中&#x…...
node 第十三天 express初见
express概念 Fast, unopinionated, minimalist web framework for Node.js 快速、独立、极简的 Node.js Web 框架。 express相当于前端的jquery, 在不更改不侵入原生node的基础上封装了大量易用且实用的服务端api, express框架的封装原理就是前面第十天我们自己封装的简易服务器…...
网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...
7.4.分块查找
一.分块查找的算法思想: 1.实例: 以上述图片的顺序表为例, 该顺序表的数据元素从整体来看是乱序的,但如果把这些数据元素分成一块一块的小区间, 第一个区间[0,1]索引上的数据元素都是小于等于10的, 第二…...
基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容
基于 UniApp + WebSocket实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...
postgresql|数据库|只读用户的创建和删除(备忘)
CREATE USER read_only WITH PASSWORD 密码 -- 连接到xxx数据库 \c xxx -- 授予对xxx数据库的只读权限 GRANT CONNECT ON DATABASE xxx TO read_only; GRANT USAGE ON SCHEMA public TO read_only; GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only; GRANT EXECUTE O…...
Module Federation 和 Native Federation 的比较
前言 Module Federation 是 Webpack 5 引入的微前端架构方案,允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...
GitHub 趋势日报 (2025年06月08日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...
有限自动机到正规文法转换器v1.0
1 项目简介 这是一个功能强大的有限自动机(Finite Automaton, FA)到正规文法(Regular Grammar)转换器,它配备了一个直观且完整的图形用户界面,使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...
虚拟电厂发展三大趋势:市场化、技术主导、车网互联
市场化:从政策驱动到多元盈利 政策全面赋能 2025年4月,国家发改委、能源局发布《关于加快推进虚拟电厂发展的指导意见》,首次明确虚拟电厂为“独立市场主体”,提出硬性目标:2027年全国调节能力≥2000万千瓦࿰…...
SpringAI实战:ChatModel智能对话全解
一、引言:Spring AI 与 Chat Model 的核心价值 🚀 在 Java 生态中集成大模型能力,Spring AI 提供了高效的解决方案 🤖。其中 Chat Model 作为核心交互组件,通过标准化接口简化了与大语言模型(LLM࿰…...
【安全篇】金刚不坏之身:整合 Spring Security + JWT 实现无状态认证与授权
摘要 本文是《Spring Boot 实战派》系列的第四篇。我们将直面所有 Web 应用都无法回避的核心问题:安全。文章将详细阐述认证(Authentication) 与授权(Authorization的核心概念,对比传统 Session-Cookie 与现代 JWT(JS…...
