nginx源码剖析之Nginx 内存池管理
off999 2025-01-05 19:30 24 浏览 0 评论
推荐视频:
手把手带你实现一个nginx模块,更加深入了解nginx(搭建好环境)
学习地址:C/C++Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂
概述
Nginx 使用内存池对内存进行管理,把内存分配归结为大内存分配和小内存分配。若申请的内存大小比同页的内存池最大值 max 还大,则是大内存分配,否则为小内存分配。
- 大块内存的分配请求不会直接在内存池上分配内存来满足请求,而是直接向系统申请一块内存(就像直接使用 malloc 分配内存一样),然后将这块内存挂到内存池头部的 large 字段下。
- 小块内存分配,则是从已有的内存池数据区中分配出一部分内存。
Nginx 内存管理相关文件:
- src/os/unix/ngx_alloc.h/.c
- 内存相关的操作,封装了最基本的内存分配函数。
- 如 free / malloc / memalign / posix_memalign,分别被封装为 ngx_free,ngx_alloc / ngx_calloc, ngx_memalign
- ngx_alloc:封装malloc分配内存
- ngx_calloc:封装malloc分配内存,并初始化空间内容为0
- ngx_memalign:返回基于一个指定 alignment 的大小为 size 的内存空间,且其地址为 alignment 的整数倍,alignment 为2的幂。
- src/core/ngx_palloc.h/.c
- 封装创建/销毁内存池,从内存池分配空间等函数。
Nginx 内存分配总流图如下:其中 size 是用户请求分配内存的大小,pool是现有内存池。
内存池基本结构
Nginx 内存池基本结构定义如下:
/* 内存池结构 */
/* 文件 core/ngx_palloc.h */
typedef struct {/* 内存池数据结构模块 */
u_char *last; /* 当前内存分配的结束位置,即下一段可分配内存的起始位置 */
u_char *end; /* 内存池的结束位置 */
ngx_pool_t *next; /* 指向下一个内存池 */
ngx_uint_t failed;/* 记录内存池内存分配失败的次数 */
} ngx_pool_data_t; /* 维护内存池的数据块 */
struct ngx_pool_s {/* 内存池的管理模块,即内存池头部结构 */
ngx_pool_data_t d; /* 内存池的数据块 */
size_t max; /* 内存池数据块的最大值 */
ngx_pool_t *current;/* 指向当前内存池 */
ngx_chain_t *chain;/* 指向一个 ngx_chain_t 结构 */
ngx_pool_large_t *large;/* 大块内存链表,即分配空间超过 max 的内存 */
ngx_pool_cleanup_t *cleanup;/* 析构函数,释放内存池 */
ngx_log_t *log;/* 内存分配相关的日志信息 */
};
/* 文件 core/ngx_core.h */
typedef struct ngx_pool_s ngx_pool_t;
typedef struct ngx_chain_s ngx_chain_t;
大块内存分配的数据结构如下:
typedef struct ngx_pool_large_s ngx_pool_large_t;
struct ngx_pool_large_s{
ngx_pool_large_t *next; //指向下一块大块内存
void *alloc; //指向分配的大块内存
};
其他数据结构如下:
typedef void (*ngx_pool_cleanup_pt)(void *data); //cleanup的callback类型
typedef struct ngx_pool_cleanup_s ngx_pool_cleanup_t;
struct ngx_pool_cleanup_s{
ngx_pool_cleanup_pt handler;
void *data; //指向要清除的数据
ngx_pool_cleanup_t *next; //下一个cleanup callback
};
typedef struct {
ngx_fd_t fd;
u_char *name;
ngx_log_t *log;
} ngx_pool_cleanup_file_t;
内存池基本机构之间的关系如下图所示:
ngx_pool_t 的逻辑结构
上面数据结构之间逻辑结构图如下:该图是采用 UML 画的,第一行黑色粗体表示对应数据结构,第二行是结构内的成员,冒号左边是变量,冒号右边是变量的类型;
内存池的操作
创建内存池
/* 创建内存池,该函数定义于 src/core/ngx_palloc.c 文件中 */
ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
ngx_pool_t *p; /* 执行内存池头部 */
/* 分配大小为 size 的内存 */
/* ngx_memalign 函数实现于 src/os/unix/ngx_alloc.c 文件中 */
p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);
if (p == NULL) {
return NULL;
}
/* 以下是初始化 ngx_pool_t 结构信息 */
p->d.last = (u_char *) p + sizeof(ngx_pool_t);
p->d.end = (u_char *) p + size;
p->d.next = NULL;
p->d.failed = 0;
size = size - sizeof(ngx_pool_t); /* 可供分配的空间大小 */
/* 不能超过最大的限定值 4096B */
p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;
p->current = p; /* 指向当前的内存池 */
p->chain = NULL;
p->large = NULL;
p->cleanup = NULL;
p->log = log;
return p;
}其中内存分配函数 ngx_memalign 定义如下:
void *
ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)
{
void *p;
int err;
err = posix_memalign(&p, alignment, size);
//该函数分配以alignment为对齐的size字节的内存大小,其中p指向分配的内存块。
if (err) {
ngx_log_error(NGX_LOG_EMERG, log, err,
"posix_memalign(%uz, %uz) failed", alignment, size);
p = NULL;
}
ngx_log_debug3(NGX_LOG_DEBUG_ALLOC, log, 0,
"posix_memalign: %p:%uz @%uz", p, size, alignment);
return p;
}
//函数分配以NGX_POOL_ALIGNMENT字节对齐的size字节的内存,在src/core/ngx_palloc.h文件中:
#define NGX_POOL_ALIGNMENT 16 【文章福利】需要C/C++ Linux服务器架构师学习资料加群812855908(资料包括C/C++,Linux,golang技术,内核,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg,大厂面试题 等)
销毁内存池
销毁内存池由 void ngx_destroy_pool(ngx_pool_t *pool) 函数完成。该函数将遍历内存池链表,释放所有内存,如果注册了clenup (也是一个链表结构),亦将遍历该 cleanup 链表结构依次调用 clenup 的 handler 清理。同时,还将遍历 large 链表,释放大块内存。
/* 销毁内存池 */
void
ngx_destroy_pool(ngx_pool_t *pool)
{
ngx_pool_t *p, *n;
ngx_pool_large_t *l;
ngx_pool_cleanup_t *c;
/* 若注册了cleanup,则遍历该链表结构,依次调用handler函数清理数据 */
for (c = pool->cleanup; c; c = c->next) {
if (c->handler) {
ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
"run cleanup: %p", c);
c->handler(c->data);
}
}
/* 遍历 large 链表,释放大块内存 */
for (l = pool->large; l; l = l->next) {
ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc);
if (l->alloc) {
ngx_free(l->alloc); /* 释放内存 */
}
}
/* 在debug模式下执行 if 和 endif 之间的代码;
* 主要是用于log记录,跟踪函数销毁时日志信息
*/
#if (NGX_DEBUG)
/*
* we could allocate the pool->log from this pool
* so we cannot use this log while free()ing the pool
*/
for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
"free: %p, unused: %uz", p, p->d.end - p->d.last);
if (n == NULL) {
break;
}
}
#endif
/* 遍历所有分配的内存池,释放内存池结构 */
for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
ngx_free(p);
if (n == NULL) {
break;
}
}
}重置内存池
重置内存池由 void ngx_reset_pool(ngx_pool_t *pool) 函数完成。该函数将释放所有 large 内存,并且将 d->last 指针重新指向 ngx_pool_t 结构之后数据区的开始位置,使内存池恢复到刚创建时的位置。由于内存池刚被创建初始化时是不包含大块内存的,所以必须释放大块内存。
/* 重置内存池
* 定义于 src/core/ngx_palloc.c 文件中
*/
void
ngx_reset_pool(ngx_pool_t *pool)
{
ngx_pool_t *p;
ngx_pool_large_t *l;
/* 遍历大块内存链表,释放大块内存 */
for (l = pool->large; l; l = l->next) {
if (l->alloc) {
ngx_free(l->alloc);
}
}
for (p = pool; p; p = p->d.next) {
p->d.last = (u_char *) p + sizeof(ngx_pool_t);
p->d.failed = 0;
}
pool->current = pool;
pool->chain = NULL;
pool->large = NULL;
}
内存分配
小块内存分配
小块内存分配,即请求分配空间 size 小于内存池最大内存值 max。小内存分配的接口函数如下所示:
void *ngx_palloc(ngx_pool_t *pool, size_t size);
void *ngx_pnalloc(ngx_pool_t *pool, size_t size);
void *ngx_pcalloc(ngx_pool_t *pool, size_t size);
void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment);
ngx_palloc 和 ngx_pnalloc 都是从内存池里分配 size 大小内存。他们的不同之处在于,palloc 取得的内存是对齐的,pnalloc 则不考虑内存对齐问题。ngx_pcalloc 是直接调用 palloc 分配内存,然后进行一次 0 初始化操作。ngx_pmemalign 将在分配 size 大小的内存并按 alignment 对齐,然后挂到 large 字段下,当做大块内存处理。
ngx_palloc的过程一般为,首先判断待分配的内存是否大于 pool->max,如果大于则使用 ngx_palloc_large 在 large 链表里分配一段内存并返回, 如果小于测尝试从链表的 pool->current 开始遍历链表,尝试找出一个可以分配的内存,当链表里的任何一个节点都无法分配内存的时候,就调用 ngx_palloc_block 生成链表里一个新的节点, 并在新的节点里分配内存并返回, 同时, 还会将pool->current 指针指向新的位置(从链表里面pool->d.failed小于等于4的节点里找出) 。
/* 分配内存 */
void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
u_char *m;
ngx_pool_t *p;
/* 若请求的内存大小size小于内存池最大内存值max,
* 则进行小内存分配,从current开始遍历pool链表
*/
if (size <= pool->max) {
p = pool->current;
do {
/* 执行对齐操作 */
m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT);
/* 检查现有内存池是否有足够的内存空间,
* 若有足够的内存空间,则移动last指针位置,
* 并返回所分配的内存地址的起始地址
*/
if ((size_t) (p->d.end - m) >= size) {
p->d.last = m + size; /* 在该节点指向的内存块中分配size大小的内存 */
return m;
}
/* 若不满足,则查找下一个内存池 */
p = p->d.next;
} while (p);
/* 若遍历所有现有内存池链表都没有可用的内存空间,
* 则分配一个新的内存池,并将该内存池连接到现有内存池链表中
* 同时,返回分配内存的起始地址
*/
return ngx_palloc_block(pool, size);
}
/* 若所请求的内存大小size大于max则调用大块内存分配函数 */
return ngx_palloc_large(pool, size);
}
static void *
ngx_palloc_block(ngx_pool_t *pool, size_t size)
{
u_char *m;
size_t psize;
ngx_pool_t *p, *new, *current;
/* 计算pool的大小,即需要分配新的block的大小 */
psize = (size_t) (pool->d.end - (u_char *) pool);
/* NGX_POOL_ALIGNMENT对齐操作 */
m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);
if (m == NULL) {
return NULL;
}
/* 计算需要分配的block的大小 */
new = (ngx_pool_t *) m;
new->d.end = m + psize;
new->d.next = NULL;
new->d.failed = 0;
/* 初始化新的内存池 */
/* 让m指向该块内存ngx_pool_data_t结构体之后数据区起始位置 */
m += sizeof(ngx_pool_data_t);
/* 在数据区分配size大小的内存并设置last指针 */
m = ngx_align_ptr(m, NGX_ALIGNMENT);
new->d.last = m + size;
current = pool->current;
for (p = current; p->d.next; p = p->d.next) {
if (p->d.failed++ > 4) {
/* 失败4次以上移动current指针 */
current = p->d.next;
}
}
/* 将分配的block连接到现有的内存池 */
p->d.next = new;
/* 如果是第一次为内存池分配block,这current将指向新分配的block */
pool->current = current ? current : new;
return m;
}
/* 直接调用palloc函数,再进行一次0初始化操作 */
void *
ngx_pcalloc(ngx_pool_t *pool, size_t size)
{
void *p;
p = ngx_palloc(pool, size);
if (p) {
ngx_memzero(p, size);
}
return p;
}
/* 按照alignment对齐分配size内存,然后将其挂到large字段,当做大块内存处理 */
void *
ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment)
{
void *p;
ngx_pool_large_t *large;
p = ngx_memalign(alignment, size, pool->log);
if (p == NULL) {
return NULL;
}
large = ngx_palloc(pool, sizeof(ngx_pool_large_t));
if (large == NULL) {
ngx_free(p);
return NULL;
}
large->alloc = p;
large->next = pool->large;
pool->large = large;
return p;
}
小内存分配之后如下图所示:
上图是由3个小内存池组成的内存池模型,由于第一个内存池上剩余的内存不够分配了,于是就创建了第二个新的内存池,第三个内存池是由于前面两个内存池的剩余部分都不够分配,所以创建了第三个内存池来满足用户的需求。由图可见:所有的小内存池是由一个单向链表维护在一起的。这里还有两个字段需要关注,failed和current字段。failed表示的是当前这个内存池的剩余可用内存不能满足用户分配请求的次数,即是说:一个分配请求到来后,在这个内存池上分配不到想要的内存,那么就failed就会增加1;这个分配请求将会递交给下一个内存池去处理,如果下一个内存池也不能满足,那么它的failed也会加1,然后将请求继续往下传递,直到满足请求为止(如果没有现成的内存池来满足,会再创建一个新的内存池)。current字段会随着failed的增加而发生改变,如果current指向的内存池的failed达到了4的话,current就指向下一个内存池了。
大块内存分配
/* 分配大块内存 */
static void *
ngx_palloc_large(ngx_pool_t *pool, size_t size)
{
void *p;
ngx_uint_t n;
ngx_pool_large_t *large;
/* 分配内存 */
p = ngx_alloc(size, pool->log);
if (p == NULL) {
return NULL;
}
n = 0;
/* 若在该pool之前已经分配了large字段,
* 则将所分配的大块内存挂载到内存池的large字段中
*/
for (large = pool->large; large; large = large->next) {
if (large->alloc == NULL) {
large->alloc = p;
return p;
}
if (n++ > 3) {
break;
}
}
/* 若在该pool之前并未分配large字段,
* 则执行分配ngx_pool_large_t 结构体,分配large字段内存,
* 再将大块内存挂载到pool的large字段中
*/
large = ngx_palloc(pool, sizeof(ngx_pool_large_t));
if (large == NULL) {
ngx_free(p);
return NULL;
}
large->alloc = p;
large->next = pool->large;
pool->large = large;
return p;
}
void *
ngx_alloc(size_t size, ngx_log_t *log)
{
void *p;
p = malloc(size);
if (p == NULL) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
"malloc() %uz bytes failed", size);
}
ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, log, 0, "malloc: %p:%uz", p, size);
return p;
}
/* 释放大块内存 */
ngx_int_t
ngx_pfree(ngx_pool_t *pool, void *p)
{
ngx_pool_large_t *l;
for (l = pool->large; l; l = l->next) {
if (p == l->alloc) {
ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
"free: %p", l->alloc);
ngx_free(l->alloc);
l->alloc = NULL;
return NGX_OK;
}
}
return NGX_DECLINED;
}
大块内存申请之后如下所示:
cleanup 资源
/* 注册cleanup;
* size 是 data 字段所指向的资源的大小;
*/
ngx_pool_cleanup_t * ngx_pool_cleanup_add(ngx_pool_t *p, size_t size);
/* 对内存池进行文件清理操作,即执行handler,此时handler==ngx_pool_cleanup_file */
void ngx_pool_run_cleanup_file(ngx_pool_t *p, ngx_fd_t fd);
/* 关闭data指定的文件句柄 */
void ngx_pool_cleanup_file(void *data);
/* 删除data指定的文件 */
void ngx_pool_delete_file(void *data);
/* 注册cleanup */
ngx_pool_cleanup_t *
ngx_pool_cleanup_add(ngx_pool_t *p, size_t size)
{
ngx_pool_cleanup_t *c;
c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t));
if (c == NULL) {
return NULL;
}
if (size) {
c->data = ngx_palloc(p, size);
if (c->data == NULL) {
return NULL;
}
} else {
c->data = NULL;
}
c->handler = NULL;
c->next = p->cleanup;
p->cleanup = c;
ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p->log, 0, "add cleanup: %p", c);
return c;
}
/* 清理内存池的文件 */
void
ngx_pool_run_cleanup_file(ngx_pool_t *p, ngx_fd_t fd)
{
ngx_pool_cleanup_t *c;
ngx_pool_cleanup_file_t *cf;
/* 遍历cleanup结构链表,并执行handler */
for (c = p->cleanup; c; c = c->next) {
if (c->handler == ngx_pool_cleanup_file) {
cf = c->data;
if (cf->fd == fd) {
c->handler(cf);
c->handler = NULL;
return;
}
}
}
}
/* 关闭data指定的文件句柄 */
void
ngx_pool_cleanup_file(void *data)
{
ngx_pool_cleanup_file_t *c = data; /* 指向data所指向的文件句柄 */
ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d",
c->fd);
/* 关闭指定文件 */
if (ngx_close_file(c->fd) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
ngx_close_file_n " \"%s\" failed", c->name);
}
}
/* 删除data所指向的文件 */
void
ngx_pool_delete_file(void *data)
{
ngx_pool_cleanup_file_t *c = data;
ngx_err_t err;
ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d %s",
c->fd, c->name);
/* 删除data所指向的文件 */
if (ngx_delete_file(c->name) == NGX_FILE_ERROR) {
err = ngx_errno;
if (err != NGX_ENOENT) {
ngx_log_error(NGX_LOG_CRIT, c->log, err,
ngx_delete_file_n " \"%s\" failed", c->name);
}
}
/* 关闭文件句柄 */
if (ngx_close_file(c->fd) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
ngx_close_file_n " \"%s\" failed", c->name);
}
}- 上一篇:宝塔面板安装配置Nginx的操作步骤
- 下一篇:Nginx之进程间通信-共享内存篇
相关推荐
- 移动硬盘不显示怎么修复(移动硬盘不显示怎么修复呢)
-
1、移动硬盘插入电脑,选择计算机,选择左侧“此电脑”,点击右击。2、选择“管理”点击,进入计算机管理;选择磁盘“磁盘管理”。3、如没有看到移动硬盘,可以选择右边的更多操作,重新扫描磁盘。4、如依然没有...
- 技术员联盟系统(技术员联盟官网)
-
区别一:纯净版就是指安装完了以后是一个windows的初始环境,没有安装其他任何第三方软件。装机版就是装好以后还会继续安装一些制作者添加的第三方软件,比如说下载工具啊,聊天工具啊什么的。安装版指的是用...
- win10系统如何还原系统(win10还原系统方法)
-
要想恢复到Win10原版系统,可以通过两种方式。一种是使用Win10系统恢复工具,该工具可以在Win10设置中找到,然后选择恢复选项,并按照步骤完成恢复操作即可。另一种方式是直接从Microsoft官...
- 老毛桃xp u盘启动(老毛桃xp u盘启动)
-
老毛桃安装纯净版XP的方法需要用到的工具:winxp纯净版系统iso镜像+启动U盘1、进入老毛桃,选第一个PE后找到我们事先准备好的纯净版系统iso镜像,右键点击加载虚拟磁盘(也可以利用虚拟光驱来加载...
- 无线wifi密码破解工具
-
WiFi万能密码WiFi万能密码可免费连接WiFi,极速发现无线WiFi信号,一键即可连接上网,同时还能检测WiFi的信号轻度及安全度,让你想怎么玩就怎么玩,不必在担心流量的问题。这款软件操作简单,一...
- 超级系统掠夺诸天(超级系统掠夺诸天万界)
-
杀了铁掌水上漂裘千仞就能获得他一身能为的是《史上最牛轮回》,作者:那一抹绯红。(这本书个人评价为“差”)
- autocad软件免费下载(cad软件下载2021)
-
首先在浏览器上搜索cad,打开一个下载cad的地址,然后点击一个下载地址入口,然后下载成功,点击打开按钮。然后点击快速安装按钮,最后CAD完全下载完成,点击打开文件即可。CAD(ComputerAi...
- ios下载网站(显卡bios下载网站)
-
ios网页下的app安装方法如下首先下载完软件以后,打开设置,然后点击通用,选择设备管理,接着点击下载的软件,最后选择信任,点击信任即可。1.搜索文件:苹果自带浏览器下载内容会自动存储在文件中,打开手...
- 如何修复ie11浏览器(xbox无法连接无线网络)
-
可以通过清理缓存和数据来修复IE浏览器因为IE浏览器可能会受到缓存和数据的干扰,导致无法正常访问网页或者打开网页很慢。清理浏览器的缓存和数据可以解决这个问题,具体方法是:打开IE浏览器,点击“工具”-...
- 鼠标灵敏度(鼠标灵敏度转换器)
-
具体算法游戏内灵敏度×DPI=eDPIDPI是鼠标的定位精度,可理解为鼠标本身的灵敏度。而eDPI是最终灵敏度举例:同一特定游戏中,游戏内灵敏度10×800dpi=游戏内灵敏度5×1600dpi...
- tplogincn官网手机登录页面(tplogin 官网登录)
-
旧版本的TP-Link路由器的登陆网址是:192.168.1.1;新版本的TP-Link路由器的登陆网址是:tplogin.cn;TP-Link迷你路由器的登陆网址是:192.168.1.253。接下...
- 电脑黑屏怎么修(电脑黑屏怎么修复没有鼠标箭头)
-
电脑主机故障引起的黑屏故障主要可以分为以下几类:1.主机电源引起的故障主机电源损坏或主机电源质量不佳引起的黑屏故障很常见。例如,当你添加了一些新设备之后,显示器便出现了黑屏故障,排除了配件质量及兼容性...
- 电脑打不开的原因(电脑打不开的原因怎么解决)
-
这是关机时出现死机了,如果不经常出现,直接按关机按钮关机就是了。如果经常发生,可以测试一下cpu的温度是否过高,如果高清一下灰,其它就是重新插拔一下内存清一下灰。系统方面,关机前将所有的程序关闭在关机...
- 免费注册邮箱163官网(邮箱注册163免费注册下载)
-
输入www.163.com找到163免费邮入口注册就可以了。官网是https://www.163.net和https://163vip.com。在这里我们就可以随意打开一个浏览器,输入官网网址注册登录...
欢迎 你 发表评论:
- 一周热门
-
-
抖音上好看的小姐姐,Python给你都下载了
-
全网最简单易懂!495页Python漫画教程,高清PDF版免费下载
-
Python 3.14 的 UUIDv6/v7/v8 上新,别再用 uuid4 () 啦!
-
飞牛NAS部署TVGate Docker项目,实现内网一键转发、代理、jx
-
python入门到脱坑 输入与输出—str()函数
-
宝塔面板如何添加免费waf防火墙?(宝塔面板开启https)
-
Python三目运算基础与进阶_python三目运算符判断三个变量
-
(新版)Python 分布式爬虫与 JS 逆向进阶实战吾爱分享
-
失业程序员复习python笔记——条件与循环
-
使用 python-fire 快速构建 CLI_如何搭建python项目架构
-
- 最近发表
- 标签列表
-
- python计时 (73)
- python安装路径 (56)
- python类型转换 (93)
- python进度条 (67)
- python吧 (67)
- python的for循环 (65)
- python格式化字符串 (61)
- python静态方法 (57)
- python列表切片 (59)
- python面向对象编程 (60)
- python 代码加密 (65)
- python串口编程 (77)
- python封装 (57)
- python写入txt (66)
- python读取文件夹下所有文件 (59)
- python操作mysql数据库 (66)
- python获取列表的长度 (64)
- python接口 (63)
- python调用函数 (57)
- python多态 (60)
- python匿名函数 (59)
- python打印九九乘法表 (65)
- python赋值 (62)
- python异常 (69)
- python元祖 (57)
