高并发实现高效内存管理

高并发下传统方式的弊端

void *malloc(size_t size);在内存的动态存储区中分配一块长度为size字节的连续区域返回该区域的首地址.

void *calloc(size_t nmemb, size_t size);malloc相似,参数size为申请地址的单位元素长度,nmemb为元素个数,即在内存中申请nmemb*size字节大小的连续地址空间,内存会初始化0

void *realloc(void *ptr, size_t size); 给一个已经分配了地址的指针重新分配空间,参数ptr为原有的空间地址,newsize是重新申请的地址长度。ptr 若为NULL,它就等同于 malloc

void free(void *ptr);释放指针所指的内存空间

在C++中会用到newdeletenew[]delete[]newdelete分别是申请和释放单个对象内存空间, new[]delete[]分别释放连续的内存空间。

问题1: 高并发时较小内存块使用导致系统调用频繁,降低了系统的执行效率。
在这里插入图片描述
问题2: 频繁使用时增加了系统内存的碎片,降低内存使用效率。
内部碎片:是指内存已经被分配出去(能明确指出属于哪个进程),却不能被利用;

产生根源:
1.内存分配必须起始于可被 4、8 或 16 整除(视处理器体系结构而定)的地址
2.MMU的分页机制的限制

问题3: 没有垃圾回收机制,容易造成内存泄漏,导致内存枯竭。
问题4: 内存分配与释放的逻辑在程序中相隔较远时,降低程序的稳定性。

弊端解决方法

在这里插入图片描述
在这里插入图片描述

高并发内存管理最佳实践

内存池 (M emory Pool) 是一种动态内存分配与管理技术。 通常情况下,程序员习惯直接使 用 new 、 delete 、 malloc 、 free 等 API 申请分配和释放内存,这样导致的后果是: 当程序长时间运行时,由于所申请内存块的大小不定,频繁使用时会造成大量的内存碎片从而降低程序和操作系统的性能 。
内存池则是在真正使用内存之前,先申请分配一大块内存 ( 内存池 ) 留作备用,当程序员申请内存时,从池中取出一块动态分配,当程序员释放内存时,将释放的内存再放入池内,再次申请池可以 再取出来使用 ,并尽量与周边的空闲内存块合并。若内存池不够时,则自动扩大内存池,从操作系统中申请更大的内存池。

在这里插入图片描述

  1. 内存池提前预先分配大块内存,统一释放,极大的减少了malloc 和 free 等函数的调用;
  2. 内存池每次请求分配大小适度的内存块,避免了碎片的产生;
  3. 在生命周期结束后统一释放内存,完全避免了内存泄露的产生;
  4. 在生命周期结束后统一释放内存,避免重复释放指针或释放空指针等情况。

高并发(High Concurrency) 是互联网分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计保证系统能够同时并行处理很多请求。

高并发特点:
1.响应时间短;
2.吞吐量大;
3.每秒响应请求数;
4.QPS 并发用户数高;

在高并发系统设计时需要考虑:
1.设计逻辑应该尽量简单,避免不同请求之间互相影响,尽量降低不同模块之间的耦合内存池
2.生存时间应该尽可能短,与请求或者连接具有相同的周期,减少碎片堆积和内存泄漏

高效内存池设计与实现

实现思路
对于每个请求或者连接都会建立相应的内存池,建立好内存池之后,我们可以直接从内存池中申请所需要的内存,不用去管内存的释放,当内存池使用完成之后一次性销毁内存池。
区分大小内存块的申请和释放,大于池尺寸的定义为大内存块,使用单独的大内存块链表保存,即时分配和释放;小于等于池尺寸的定义为小内存块,直接从预先分配的内存块中提取,不够就扩充池中的内存,在生命周期内对小块内存不做释放,直到最后统一销毁。

Nginx源码分析

Nginx 内存池结构图
在这里插入图片描述

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;      // 当前数据块最大可分配的内存大小(Bytes)ngx_pool_t           *current;  // 当前正在使用的数据块的指针ngx_pool_large_t     *large;    // pool 中指向大数据块的指针(大数据快是指 size > max 的数据块)
};

ngx_pool_t 结构示意图
在这里插入图片描述

Nginx 内存池基本操作
1.内存池创建、销毁和重置
在这里插入图片描述
2.内存池申请、释放和回收操作
在这里插入图片描述

具体代码实现

1.内存分配 ngx_alloc和ngx_calloc

void *
ngx_alloc(size_t size)
{void  *p;//分配一块内存p = malloc(size);if (p == NULL) {fprintf(stderr,"malloc(%zu) failed", size);}if(debug) fprintf(stderr, "malloc: %p:%zu", p, size);return p;
}//调用ngx_alloc方法,如果分配成,则调用ngx_memzero方法,将内存块设置为0
// #define ngx_memzero(buf, n)  (void) memset(buf, 0, n)
void *
ngx_calloc(size_t size)
{void  *p;p = ngx_alloc(size);if (p) {ngx_memzero(p, size);}return p;
}

2.创建内存池ngx_create_pool

/*** 创建一个内存池*/
ngx_pool_t *
ngx_create_pool(size_t size)
{ngx_pool_t  *p;/*** 相当于分配一块内存 ngx_alloc(size, log)*/p = ngx_memalign(NGX_POOL_ALIGNMENT, size);if (p == NULL) {return NULL;}/*** Nginx会分配一块大内存,其中内存头部存放ngx_pool_t本身内存池的数据结构* ngx_pool_data_t	p->d 存放内存池的数据部分(适合小于p->max的内存块存储)* p->large 存放大内存块列表* p->cleanup 存放可以被回调函数清理的内存块(该内存块不一定会在内存池上面分配)*/p->d.last = (u_char *) p + sizeof(ngx_pool_t);	//内存开始地址,指向ngx_pool_t结构体之后数据取起始位置p->d.end = (u_char *) p + size;	//内存结束地址p->d.next = NULL;	//下一个ngx_pool_t 内存池地址p->d.failed = 0;	//失败次数size = size - sizeof(ngx_pool_t);p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;//有缓存池的父节点,才会用到下面的这些 ,子节点只挂载在p->d.next,并且只负责p->d的数据内容p->current = p;//p->chain = NULL;p->large = NULL;//p->cleanup = NULL;//p->log = log;return p;
}
#define NGX_ALIGNMENT   sizeof(unsigned long)    /* platform word */
#define ngx_memalign(alignment, size)  ngx_alloc(size)ngx_memalign(size_t alignment, size_t size)
{void  *p;p = memalign(alignment, size);if (p == NULL) {fprintf(stderr,"memalign(%zu, %zu) failed", alignment, size);}if(debug) fprintf(stderr,"memalign: %p:%zu @%zu", p, size, alignment);return p;
}/** 它的作用和ngx_palloc_large可以是基本一样的,有两个不同点:* 1)调用内存对齐的函数ngx_memalign创建内存,而非像ngx_palloc_large内调用不内存对齐的ngx_alloc。* 2)不再去检查前面三个大内存是否为空,而是直接将新创建的大内存p按照头插法放进大内存链表管理。
*/
void *
ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment)
{void              *p;ngx_pool_large_t  *large;// 1)创建一个内存对齐的大内存p = ngx_memalign(alignment, size, pool->log);if (p == NULL) {return NULL;}// 2)创建管理大内存的结构体,并在下面赋值进行管理该内存large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);if (large == NULL) {ngx_free(p);return NULL;}//头插法插进大内存链表large->alloc = p;large->next = pool->large;pool->large = large;return p;
}

3.销毁内存池ngx_destroy_pool

void
ngx_destroy_pool(ngx_pool_t *pool)
{ngx_pool_t          *p, *n;ngx_pool_large_t    *l;//ngx_pool_cleanup_t  *c;//首先清理pool->cleanup链表/*for (c = pool->cleanup; c; c = c->next) {//handler 为一个清理的回调函数if (c->handler) {ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,"run cleanup: %p", c);c->handler(c->data);}}*/#if (NGX_DEBUG)/** we could allocate the pool->log from this pool* so we cannot use this log while free()ing the pool*///清理pool->large链表(pool->large为单独的大数据内存块)for (l = pool->large; l; l = l->next) {fprintf(stderr,"free: %p", l->alloc);}for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {fprintf(stderr,"free: %p, unused: %zu", p, p->d.end - p->d.last);if (n == NULL) {break;}}#endiffor (l = pool->large; l; l = l->next) {if (l->alloc) {ngx_free(l->alloc);}}//对内存池的data数据区域进行释放for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {ngx_free(p);if (n == NULL) {break;}}
}

4.重设内存池ngx_reset_pool

void
ngx_reset_pool(ngx_pool_t *pool)
{ngx_pool_t        *p;ngx_pool_large_t  *l;//清理pool->large链表(pool->large为单独的大数据内存块)for (l = pool->large; l; l = l->next) {if (l->alloc) {ngx_free(l->alloc);}}//循环重新设置内存池data区域的 p->d.last;data区域数据并不擦除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;
}

5.使用内存池分配一块内存ngx_palloc和ngx_pnalloc

void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
#if !(NGX_DEBUG_PALLOC)//判断每次分配的内存大小,如果超出pool->max的限制,则需要走大数据内存分配策略if (size <= pool->max) {return ngx_palloc_small(pool, size, 1);}
#endifreturn ngx_palloc_large(pool, size);
}//它的作用和ngx_palloc可以是一样的,只不过多了一步清零操作。
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;
}void *
ngx_pnalloc(ngx_pool_t *pool, size_t size)
{
#if !(NGX_DEBUG_PALLOC)//判断每次分配的内存大小,如果超出pool->max的限制,则需要走大数据内存分配策略if (size <= pool->max) {return ngx_palloc_small(pool, size, 0);}
#endifreturn ngx_palloc_large(pool, size);
}

分配一块内存,如果分配的内存 size 小于内存池的 pool->max 的限制,则属于小内存块分配,走小内存块分配逻辑;否则走大内存分配逻辑。

小内存分配逻辑:循环读取pool->d上的内存块,是否有足够的空间容纳需要分配的 size,如果可以容纳,则直接分配内存;否则内存池需要申请新的内存块,调用ngx_palloc_block
大内存分配逻辑:当分配的内存size大于内存池的pool->max的限制,则会直接调用ngx_palloc_large方法申请一块独立的内存块,并且将内存块挂载到pool->large的链表上进行统一管理。

static inline void *
ngx_palloc_small(ngx_pool_t *pool, size_t size, ngx_uint_t align)
{u_char      *m;ngx_pool_t  *p;p = pool->current;do {m = p->d.last;if (align) {m = ngx_align_ptr(m, NGX_ALIGNMENT);}if ((size_t) (p->d.end - m) >= size) {p->d.last = m + size;return m;}p = p->d.next;} while (p);return ngx_palloc_block(pool, size);
}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);if (p == NULL) {return NULL;}n = 0;/* 去pool->large链表上查询是否有NULL的,只在链表上往下查询3次,主要判断大数据块是否有被释放的,如果没有则只能跳出*/for (large = pool->large; large; large = large->next) {if (large->alloc == NULL) {large->alloc = p;return p;}if (n++ > 3) {break;}}/* 分配一个ngx_pool_large_t 数据结构 */large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);if (large == NULL) {ngx_free(p);	//如果分配失败,删除内存块return NULL;}large->alloc = p;large->next = pool->large;pool->large = large;return p;
}

6.ngx_palloc_block,内存池扩容

/*** 申请一个新的缓存池 ngx_pool_t* 新的缓存池会挂载在主缓存池的 数据区域 (pool->d->next)*/
static void *
ngx_palloc_block(ngx_pool_t *pool, size_t size)
{u_char      *m;size_t       psize;ngx_pool_t  *p, *new;psize = (size_t) (pool->d.end - (u_char *) pool);/* 申请新的块 */m = ngx_memalign(NGX_POOL_ALIGNMENT, psize);if (m == NULL) {return NULL;}new = (ngx_pool_t *) m;new->d.end = m + psize;new->d.next = NULL;new->d.failed = 0;/* 分配size大小的内存块,返回m指针地址 */m += sizeof(ngx_pool_data_t);m = ngx_align_ptr(m, NGX_ALIGNMENT);new->d.last = m + size;/*** 缓存池的pool数据结构会挂载子节点的ngx_pool_t数据结构* 子节点的ngx_pool_t数据结构中只用到pool->d的结构,只保存数据* 每添加一个子节点,p->d.failed就会+1,当添加超过4个子节点的时候,* pool->current会指向到最新的子节点地址** 这个逻辑主要是为了防止pool上的子节点过多,导致每次ngx_palloc循环pool->d.next链表* 将pool->current设置成最新的子节点之后,每次最大循环4次,不会去遍历整个缓存池链表*/for (p = pool->current; p->d.next; p = p->d.next) {if (p->d.failed++ > 4) {pool->current = p->d.next;}}p->d.next = new;return m;
}

分配一块大内存,挂载到pool->large链表上ngx_palloc_large

/*** 当分配的内存块大小超出pool->max限制的时候,需要分配在pool->large上*/
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);if (p == NULL) {return NULL;}n = 0;for (large = pool->large; large; large = large->next) {if (large->alloc == NULL) {large->alloc = p;return p;}if (n++ > 3) {break;}}large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);if (large == NULL) {ngx_free(p);return NULL;}large->alloc = p;large->next = pool->large;pool->large = large;return p;
}

7.大内存块的释放 ngx_pfree
内存池释放需要走ngx_destroy_pool,独立大内存块的单独释放,可以走ngx_pfree方法。

ngx_int_t
ngx_pfree(ngx_pool_t *pool, void *p)
{ngx_pool_large_t  *l;/* 在pool->large链上循环搜索,并且只释放内容区域,不释放ngx_pool_large_t数据结构*/for (l = pool->large; l; l = l->next) {if (p == l->alloc) {fprintf(stderr,"free: %p", l->alloc);ngx_free(l->alloc);l->alloc = NULL;	// 大内存块的起始地址return NGX_OK;}}return NGX_DECLINED;
}

总结

1)ngx_alloc函数
作用:单独调用malloc开辟内存。
2)ngx_calloc函数
调用ngx_alloc增加清零操作。可认为与ngx_alloc一样。
3)ngx_memalign函数
开辟内存并内存对齐。4)ngx_palloc函数和ngx_pnalloc函数
这两个函数调用small和large开辟内存。
1)ngx_palloc_samll首先在已有内存池中查找可用内存,没有则调用block开辟新内存,并给新内存池管理再插入到内存池链表,所以调用这两个函数的内存在内存池链表上。但实际并不连续,只是内存池结构体连续。所以调用samll必定在内存池链表。
2)而ngx_palloc_large是调用ngx_alloc直接开辟内存后交给ngx_pool_large_t结构体管理,再插入大内存块链表。所以该函数开辟的内存不在内存池。而是必定在大内存块链表中。
所以:开辟的内存在内存池链表或者在大内存块链表。5)ngx_pcalloc函数
调用ngx_palloc,增加清零操作。可认为与ngx_palloc一样。额外函数(较少使用)6)ngx_pmemalign函数
调用ngx_memalign开辟内存对齐的空间,然后交给ngx_pool_large_t结构体管理,再插入大内存块链表。
所以可认为与ngx_palloc_large一样,只不过ngx_palloc_large少了内存对齐但多了检查前三个结构的步骤。所以可以总结:
1)前三个都是开辟无任何关系管理的内存。
2)后三个需要进行区分:
1:ngx_palloc和ngx_pnalloc开辟的内存在内存池链表或者在大内存块链表。
2:ngx_palloc_samll(即block)开辟的内存必定在内存池结构体链表上管理。
3:ngx_palloc_large和ngx_pmemalign开辟的内存必定在大内存块结构体的链表上管理。

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

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

相关文章

软考高级 | 系统架构设计师笔记(一)

一. 系统规划 1.1 项目的提出与选择 该步骤生成” 产品/项目建议书”. 1.2 可行性研究与效益分析 包括经济可行性/技术可行性/法律可行性/执行可行性/方案选择 5 个部分. 该步骤生 成”可行性研究报告”. 1.3 方案的制订和改进 包括确定软件架构/确定关键性要素?/确定计算…

Python 自定义日志输出

Python 有着内置的日志输出模块&#xff1a;logging 使用也很方便&#xff0c;但我们今天不说这个&#xff0c;我们用文件读写模块&#xff0c;实现自己的日志输出模块&#xff1b;这样在项目中&#xff0c;可以存在更高的自由度及更高的扩展性&#xff1b; 先来看看日志输出…

TDengine高可用架构之TDengine+Keepalived

之前在《TDengine高可用探讨》提到过&#xff0c;TDengine通过多副本和多节点能够保证数据库集群的高可用。单对于应用端来说&#xff0c;如果使用原生连接方式&#xff08;taosc&#xff09;还好&#xff0c;当一个节点下线&#xff0c;应用不会受到影响&#xff1b;但如果使用…

Python爬虫--Scrapy框架安装

Scrapy框架安装 &#xff0c; Scrapy 是 Python 领域专业的爬虫开发框架&#xff0c;已经完成爬虫程序的大部分通用工具 它使用了 Twisted 异步网络库来处理网络通讯。整体架构大致如下 第一步&#xff1a;挂小灰机或者将要安装的文件下载到本地 Scrapy 框架安装踩坑中 为什…

Blender曲线操作

1.几种常见建模方式 -多边形建模&#xff1a;Blender&#xff0c;C4D&#xff0c;3DsMax&#xff0c;MaYa -曲线&#xff1a; -曲面&#xff1a;Rhino&#xff08;Nurbs&#xff09; -雕刻&#xff1a;Blender&#xff0c;ZBrush -蜡笔&#xff1a;Blender 1&#xff09;新…

【办公类-22-14】周计划系列(5-6)“周计划-06 19周的周计划教案合并打印PDF(最终打印版))

背景需求&#xff1a; 花了十周&#xff0c;终于把周计划教案的文字都写满、加粗、节日替换了。为了便于打印&#xff0c;我把19周的周计划教案全部合并在一起PDF。制作打印用PDF 思路 1、周计划是单独打印一张&#xff0c;因此要在第2页插入空白页&#xff0c; 2、教案有3页…

鸿蒙launcher浅析

鸿蒙launcher浅析 鸿蒙launcher源码下载鸿蒙launcher模块launcher和普通的应用ui展示的区别 鸿蒙launcher源码下载 下载地址如下&#xff1a; https://gitee.com/openharmony/applications_launcher 鸿蒙launcher模块 下载页面已经有相关文件结构的介绍了 使用鸿蒙编辑器D…

CMDB系统的目标

CMDB即配置管理数据库&#xff08;Configuration Management Database, CMDB&#xff09;系统被广泛应用于实现IT资产管理和IT服务管理。CMDB系统的目标是建立一个全面的、精确的信息数据库&#xff0c;用于追踪、管理和记录IT基础设施的配置信息及其相关关系&#xff0c;从而提…

OpenHarmony开发实例:【电话簿联系人Contacts】

样例简介 Contacts应用是基于OpenHarmony SDK开发的安装在润和HiSpark Taurus AI Camera(Hi3516d)开发板标准系统上的应用&#xff1b;应用主要功能是展示联系人列表&#xff0c;并点击某一列弹出联系人详细信息&#xff1b; 运行效果 样例原理 样例主要有一个list组件和dia…

Docker本地部署overleaf后,挖掘用户加密逻辑

overleaf的用户信息&#xff0c;保存在mongo数据库的users集合中。 用户密码则存在hashedPassword字段中 从开源的代码services\web\app\src\Features\Authentication\AuthenticationManager.js第303行可以找到密码加密逻辑。 本地可以通过下面的代码生成overleaf用户密码信息…

如何在每天特定的时间打开指定的网页?教你设置每天自动打开指定网页

在现代社会&#xff0c;互联网已成为我们日常生活和工作中不可或缺的一部分。随着科技的 发展&#xff0c;我们可以利用各种工具和技术来提高我们的工作效率和生活品质。其中&#xff0c;定 时自动打开指定的网址便是一个实用的功能&#xff0c;它可以帮助我们节省时间&#xf…

百万人都在求的网络安全学习路线,渗透漏洞防御总结(附图)

前言 不折腾的网络安全&#xff0c;和咸鱼有什么区别 目录 二、 前言三 、同源策略 3.1 什么是同源策略 3.2 为什么需要同源策略四 、XSS 4.1 概览 4.2 介绍 4.3 防御五 、CSRF 5.1 概览 5.2 介绍 5.3 防御六、 SQL 注入七 、流量劫持 7.1 DNS 劫持 7.2 HTTP 劫持…

【Canvas与艺术】 绘制五星红旗

【注意】 该图中五星定位和大小都是按 https://www.douyin.com/note/7149362345016380710 精确绘制的。 【成图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8&q…

【前端】6. JavaScript(WebAPI)

WebAPI 背景知识 什么是 WebAPI 前面学习的 JS 分成三个大的部分 ECMAScript: 基础语法部分DOM API: 操作页面结构BOM API: 操作浏览器 WebAPI 就包含了 DOM BOM. 这个是 W3C 组织规定的. (和制定 ECMAScript 标准的大佬们不是一伙人). 前面学的 JS 基础语法主要学的是 EC…

【多维动态规划】Leetcode 64. 最小路径和【中等】

最小路径和 给定一个包含非负整数的 m x n 网格 grid &#xff0c;请找出一条从左上角到右下角的路径&#xff0c;使得路径上的数字总和为最小。 说明&#xff1a;每次只能向下或者向右移动一步。 示例 1&#xff1a; 输入&#xff1a;grid [[1,3,1],[1,5,1],[4,2,1]] 输出…

手动在Ubuntu22.04上部署LAMP环境

简介 LAMP环境是常用的Web开发环境之一&#xff0c;其中LAMP分别代表Linux、Apache、MySQL和PHP。本文介绍如何在Ubuntu操作系统的ECS实例内部署LAMP环境。 准备工作 该实例必须满足以下条件&#xff1a; 实例已分配公网IP地址或绑定弹性公网IP&#xff08;EIP&#xff09;。…

关于Dockerfile镜像实例

文章目录 Dockerfile镜像实例一、构建SSH镜像1、建立工作目录2、生成镜像3、启动容器并修改root密码 二、构建systemd镜像1、建立工作目录2、生成镜像3、运行镜像容器4、测试容器systemd 三、构建Nginx镜像1、建立工作目录2、编写Dockerfile脚本3、编写run.sh启动脚本4、生成镜…

源代码加密

企业到底该如何正确选择源代码加密产品&#xff1f; 源代码加密的方法和重点到底是怎样的&#xff1f; 源代码开发环境复杂&#xff0c;涉及的开发软件、文件类型庞杂多变&#xff0c;究竟有什么源代码加密软件能够适应众多开发软件而不影响原有的工作效率&#xff1f; 相信…

个人学习总结__打开摄像头、播放网络视频的以及ffmpeg推流

前言 最近入手了一款非常便宜的usb摄像头&#xff08;买回来感觉画质很低&#xff0c;没有描述的4k&#xff0c;不过也够用于学习了&#xff09;,想着利用它来开启流媒体相关技术的学习。第一步便是打开摄像头&#xff0c;从而才能够对它进行一系列后续操作&#xff0c;诸如实…

有趣的 CSS 图标整合技术!sprites精灵图,css贴图定位

你好&#xff0c;我是云桃桃。 一个希望帮助更多朋友快速入门 WEB 前端的程序媛。 云桃桃-大专生&#xff0c;一枚程序媛&#xff0c;感谢关注。回复 “前端基础题”&#xff0c;可免费获得前端基础 100 题汇总&#xff0c;回复 “前端工具”&#xff0c;可获取 Web 开发工具合…