欢迎访问开云体育APP官方下载(KAIYUN)旗下有开云体育手机app下载,开云体育app下载,开云体育官网入口,开云体育app下载官网,开云体育app官网入口手机版等业务,开云体育平台app安全靠谱信誉高!开云体育官网入口
编注:
欢迎访问开云体育APP官方下载(KAIYUN)旗下有开云体育手机app下载,开云体育app下载,开云体育官网入口,开云体育app下载官网,开云体育app官网入口手机版等业务,开云体育平台app安全靠谱信誉高!开云体育官网入口
编注:
本篇文章带大家聊聊go语言中的限流漏桶和令牌桶库,介绍令牌桶和漏桶的实现原理以及在实际项目中简单应用。
在大数据量高并发访问时,经常会出现服务或接口面对大量的请求而导致数据库崩溃的情况,甚至引发连锁反映导致整个系统崩溃。或者有人恶意攻击网站,大量的无用请求出现会导致缓存穿透的情况出现。使用限流中间件可以在短时间内对请求进行限制数量,起到降级的作用,从而保障了网站的安全性。
使用消息中间件进行统一限制(降速)
使用限流方案将多余请求返回(限流)
升级服务器
缓存(但仍然有缓存穿透等危险)
等等
可以看出在代码已经无法提升的情况下,只能去提升硬件水平。或者改动架构再加一层!也可以使用消息中间件统一处理。而结合看来,限流方案是一种既不需要大幅改动也不需要高额开销的策略。
令牌桶算法
漏桶算法
滑动窗口算法
等等
go get -u go.uber.org/ratelimit
// New returns a Limiter that will limit to the given RPS. func New(rate int, opts ...Option) Limiter { return newAtomicBased(rate, opts...) } // newAtomicBased returns a new atomic based limiter. func newAtomicBased(rate int, opts ...Option) *atomicLimiter { // TODO consider moving config building to the implementation // independent code. config := buildConfig(opts) perRequest := config.per / time.Duration(rate) l := &atomicLimiter{ perRequest: perRequest, maxSlack: -1 * time.Duration(config.slack) * perRequest, clock: config.clock, } initialState := state{ last: time.Time{}, sleepFor: 0, } atomic.StorePointer(&l.state, unsafe.Pointer(&initialState)) return l }
该函数使用了函数选项模式对多个结构体对象进行初始化
根据传入的值来初始化一个桶结构体 rate
为int
传参 。
初始化过程中包括了
perquest = config.per / time.Duration(rate)
maxSlack
宽松度(宽松度为负值)-1 * time.Duration(config.slack) * perRequest
松紧度是用来规范等待时间的// Clock is the minimum necessary interface to instantiate a rate limiter with // a clock or mock clock, compatible with clocks created using // github.com/andres-erbsen/clock. type Clock interface { Now() time.Time Sleep(time.Duration) }
同时还需要结构体Clock
来记录当前请求的时间now
和此刻的请求所需要花费等待的时间sleep
type state struct { last time.Time sleepFor time.Duration }
state
主要用来记录上次执行的时间以及当前执行请求需要花费等待的时间(作为中间状态记录)
func (t *atomicLimiter) Take() time.Time { var ( newState state taken bool interval time.Duration ) for !taken { now := t.clock.Now() previousStatePointer := atomic.LoadPointer(&t.state) oldState := (*state)(previousStatePointer) newState = state{ last: now, sleepFor: oldState.sleepFor, } if oldState.last.IsZero() { taken = atomic.CompareAndSwapPointer(&t.state, previousStatePointer, unsafe.Pointer(&newState)) continue } // 计算是否需要进行等待取水操作 newState.sleepFor += t.perRequest(每两滴水之间的间隔时间) - now.Sub(oldState.last)(当前时间与上次取水时间的间隔) // 如果等待取水时间特别小,就需要松紧度进行维护 if newState.sleepFor < t.maxSlack { newState.sleepFor = t.maxSlack } // 如果等待时间大于0,就进行更新 if newState.sleepFor > 0 { newState.last = newState.last.Add(newState.sleepFor) interval, newState.sleepFor = newState.sleepFor, 0 } taken = atomic.CompareAndSwapPointer(&t.state, previousStatePointer, unsafe.Pointer(&newState)) } t.clock.Sleep(interval) // 最后返回需要等待的时间 return newState.last }
实现一个Take方法
该Take方法会进行原子性操作(可以理解为加锁和解锁),在大量并发请求下仍可以保证正常使用。
记录下当前的时间 now := t.clock.Now()
oldState.last.IsZero()
判断是不是第一次取水,如果是就直接将state
结构体中的值进行返回。而这个结构体中初始化了上次执行时间,如果是第一次取水就作为当前时间直接传参。
如果 newState.sleepFor
非常小,就会出现问题,因此需要借助宽松度,一旦这个最小值比宽松度小,就用宽松度对取水时间进行维护。
如果newState.sleepFor > 0
就直接更新结构体中上次执行时间newState.last = newState.last.Add(newState.sleepFor)
并记录需要等待的时间interval, newState.sleepFor = newState.sleepFor, 0
。
如果允许取水和等待操作,那就说明没有发生并发竞争的情况,就模拟睡眠时间t.clock.Sleep(interval)
。然后将取水的目标时间进行返回,由服务端代码来判断是否打回响应或者等待该时间后继续响应。
func (c *clock) Sleep(d time.Duration) { time.Sleep(d) }
实际上在一个请求来的时候,限流器就会进行睡眠对应的时间,并在睡眠后将最新取水时间返回。
func ratelimit1() func(ctx *gin.Context) { r1 := rate1.New(100) return func(ctx *gin.Context) { now := time.Now() // Take 返回的是一个 time.Duration的时间 if r1.Take().Sub(now) > 0 { // 返回的时间比当前的时间还大,说明需要进行等待 // 如果需要等待, 就 time.Sleep(r1.Take().Sub(now())) 然后放行 // 如果不需要等待请求时间,就直接进行Abort 然后返回 response(ctx, http.StatusRequestTimeout, "rate1 limit...") fmt.Println("rate1 limit...") ctx.Abort() return } // 放行 ctx.Next() } }
这里你可以进行选择是否返回。因为Take一定会执行sleep函数,所以当执行take结束后表示当前请求已经接到了水。当前演示使用第一种情况。
如果你的业务要求响应不允许进行等待。那么可以在该请求接完水之后然后,如上例。
如果你的业务允许响应等待,那么该请求等待对应的接水时间后进行下一步。具体代码就是将if
中的内容直接忽略。(建议使用)
这里定义了一个响应函数和一个handler
函数方便测试
func response(c *gin.Context, code int, info any) { c.JSON(code, info) } func pingHandler(c *gin.Context) { response(c, 200, "ping ok~") }
执行go test -run=Run -v
先开启一个web服务
func TestRun(t *testing.T) { r := gin.Default() r.GET("/ping1", ratelimit1(), pingHandler) r.GET("/ping2", ratelimit2(), helloHandler) _ = r.Run(":4399") }
使用接口压力测试工具go-wrk
进行测试->tsliwowicz/go-wrk: go-wrk)
在golang引入install版本可以直接通过go install github.com/tsliwowicz/go-wrk@latest
下载
Usage: go-wrk <options> <url> Options: -H Header to add to each request (you can define multiple -H flags) (Default ) -M HTTP method (Default GET) -T Socket/request timeout in ms (Default 1000) -body request body string or @filename (Default ) -c Number of goroutines to use (concurrent connections) (Default 10) -ca CA file to verify peer against (SSL/TLS) (Default ) -cert CA certificate file to verify peer against (SSL/TLS) (Default ) -d Duration of test in seconds (Default 10) -f Playback file name (Default <empty>) -help Print help (Default false) -host Host Header (Default ) -http Use HTTP/2 (Default true) -key Private key file name (SSL/TLS (Default ) -no-c Disable Compression - Prevents sending the "Accept-Encoding: gzip" header (Default false) -no-ka Disable KeepAlive - prevents re-use of TCP connections between different HTTP requests (Default false) -no-vr Skip verifying SSL certificate of the server (Default false) -redir Allow Redirects (Default false) -v Print version details (Default false)
-t 8个线程 -c 400个连接 -n 模拟100次请求 -d 替换-n 表示连接时间
输入
go-wrk -t=8 -c=400 -n=100 http://127.0.0.1:4399/ping1
可以稍微等待一下水流积攒(压测速度过快)。
89
个请求全部返回。也就是说在一段请求高峰期,不会有请求进行响应。因此我认为既然内部已经睡眠,那么就也就应该对请求放行处理。
引入ratelimit
库
go get -u github.com/juju/ratelimit
// NewBucket returns a new token bucket that fills at the // rate of one token every fillInterval, up to the given // maximum capacity. Both arguments must be // positive. The bucket is initially full. func NewBucket(fillInterval time.Duration, capacity int64) *Bucket { return NewBucketWithClock(fillInterval, capacity, nil) } // NewBucketWithClock is identical to NewBucket but injects a testable clock // interface. func NewBucketWithClock(fillInterval time.Duration, capacity int64, clock Clock) *Bucket { return NewBucketWithQuantumAndClock(fillInterval, capacity, 1, clock) }
进行Bucket
桶的初始化。
func NewBucketWithQuantumAndClock(fillInterval time.Duration, capacity, quantum int64, clock Clock) *Bucket { if clock == nil { clock = realClock{} } // 填充速率 if fillInterval <= 0 { panic("token bucket fill interval is not > 0") } // 最大令牌容量 if capacity <= 0 { panic("token bucket capacity is not > 0") } // 单次令牌生成量 if quantum <= 0 { panic("token bucket quantum is not > 0") } return &Bucket{ clock: clock, startTime: clock.Now(), latestTick: 0, fillInterval: fillInterval, capacity: capacity, quantum: quantum, availableTokens: capacity, } }
令牌桶初始化过程,初始化结构体 fillInterval
(填充速率) cap
(最大令牌量) quannum
(每次令牌生成量)。
如果三个变量有一个小于或者等于0的话直接进行报错返回。在最开始就将当前令牌数初始化为最大容量。
// TakeAvailable takes up to count immediately available tokens from the // bucket. It returns the number of tokens removed, or zero if there are // no available tokens. It does not block. func (tb *Bucket) TakeAvailable(count int64) int64 { tb.mu.Lock() defer tb.mu.Unlock() return tb.takeAvailable(tb.clock.Now(), count) }
调用TakeAvailable
函数,传入参数为需要取出的令牌数量,返回参数是实际能够取出的令牌数量。
func (tb *Bucket) takeAvailable(now time.Time, count int64) int64 { // 如果需要取出的令牌数小于等于零,那么就返回0个令牌 if count <= 0 { return 0 } // 根据时间对当前桶中令牌数进行计算 tb.adjustavailableTokens(tb.currentTick(now)) // 计算之后的令牌总数小于等于0,说明当前令牌不足取出,那么就直接返回0个令牌 if tb.availableTokens <= 0 { return 0 } // 如果当前存储的令牌数量多于请求数量,那么就返回取出令牌数 if count > tb.availableTokens { count = tb.availableTokens } // 调整令牌数 tb.availableTokens -= count return count }
如果需要取出的令牌数小于等于零,那么就返回0个令牌
根据时间对当前桶中令牌数进行计算
计算之后的令牌总数小于等于0,说明当前令牌不足取出,那么就直接返回0个令牌
如果当前存储的令牌数量多于请求数量,那么就返回取出令牌数
调整令牌数
func (tb *Bucket) adjustavailableTokens(tick int64) { lastTick := tb.latestTick tb.latestTick = tick // 如果当前令牌数大于最大等于容量,直接返回最大容量 if tb.availableTokens >= tb.capacity { return } // 当前令牌数 += (当前时间 - 上次取出令牌数的时间) * quannum(每次生成令牌量) tb.availableTokens += (tick - lastTick) * tb.quantum // 如果当前令牌数大于最大等于容量, 将当前令牌数 = 最大容量 然后返回 当前令牌数 if tb.availableTokens > tb.capacity { tb.availableTokens = tb.capacity } return }
如果当前令牌数大于最大等于容量,直接返回最大容量
当前令牌数 += (当前时间 – 上次取出令牌数的时间) * quannum(每次生成令牌量)
如果当前令牌数大于最大等于容量, 将当前令牌数 = 最大容量 然后返回 当前令牌数
加锁 defer
解锁
判断count(想要取出的令牌数) 是否小于等于 0,如果是直接返回 0
调用函数adjustTokens
获取可用的令牌数量
如果当前可以取出的令牌数小于等于0 直接返回 0
如果当前可以取出的令牌数小于当前想要取出的令牌数(count) count = 当前可以取出的令牌数
当前的令牌数 -= 取出的令牌数 (count)
返回 count(可以取出的令牌数)
take
函数,能够返回等待时间和布尔值,允许欠账,没有令牌也可以取出。
func (tb *Bucket) Take(count int64) time.Duration
takeMaxDuration
函数,可以根据最大等待时间来进行判断。
func (tb *Bucket) TakeMaxDuration(count int64, maxWait time.Duration) (time.Duration, bool)
因为他们内部的实现都基于令牌调整,我这里不做过多介绍,如果感兴趣可以自行研究一下。
func ratelimit2() func(ctx *gin.Context) { // 生成速率 最大容量 r2 := rate2.NewBucket(time.Second, 200) return func(ctx *gin.Context) { //r2.Take() // 允许欠账,令牌不够也可以接收请求 if r2.TakeAvailable(1) == 1 { // 如果想要取出1个令牌并且能够取出,就放行 ctx.Next() return } response(ctx, http.StatusRequestTimeout, "rate2 limit...") ctx.Abort() return } }
令牌桶可以允许自己判断请求是否继续,内部不会进行睡眠操作。而漏桶需要进行睡眠,并没有提供方法让程序员进行判断是否放行。
【相关推荐:Go视频教程、编程教学】
React为什么不将Vite作为构建应用的首选?下面本篇文章就来带大家聊聊React不将Vite作为默认推荐的原因,希望对大家有所帮助!
在React
文档中,对于构建新的React
应用,首推的方式是CRA
(create-react-app)。
CRA
推出于2016年,彼时还没有成体系的React
脚手架工具供大家使用,再加上这是官方工具,一经推出就受到了欢迎。截止当前,CRA
仓库已经收获快10wstar
。
但是,随着时间的推移,出现了很多优秀的替代品,比如parcel
、vite
提供的React
模版。
而CRA
本身的进步速度却在放缓,其上一次提交要追溯到去年9月8日:
此外,CRA
对一些流行工具的支持也不是很好,比如在TailwindCSS
文档中就不推荐使用CRA
:
近日,油管10w粉丝的前端网红Theo就在React
文档仓库发起了一个PR,号召React
文档不要再默认推荐CRA
,而是应该将Vite
作为构建应用的首选。【相关推荐:Redis视频教程、编程视频】
看这围观群众的数量就知道大家对这种敏感问题有多关心了:
那么,React
团队是如何看待这个问题的呢?他们会将Vite
作为构建应用的首选项么?
本文来聊聊Dan(React
核心成员)对这一问题的看法。
欢迎加入人类高质量前端交流群,带飞
既然众矢之的是CRA
,那么首先我们需要明白CRA
在React
体系下的定位,再来看看Vite
能否在这个定位下取代前者。
CRA
诞生的时期(2016年),是SPA
(单页应用)最火热的时期。在当时,他很好的解决了两个痛点:
这点不用过多介绍,执行如下命令后就能生成一个CSR
(客户端渲染)的React
项目:
npx create-react-app 项目名复制代码
CRA
将当时的一些工程化最佳实践都封装在react-scripts
包下,并抹平这些工具不兼容的地方。
开发者既享受了开箱即用的最佳实践,又不用担心某些工具升级后对项目造成的影响(CRA
会处理)。
后来的很多优秀脚手架工具(比如Vite
、Parcel
),也都沿用了这种开箱即用的理念。
除了以上两点,随着CRA
的走红,React
团队还将他作为新特性的快速分发渠道,比如:
Fast Refresh
(针对React
的热更新,不会丢失组件状态)
Hooks
推出后的一系列lint
规则
依托CRA
庞大的装机量与使用量,这些集成到CRA
的特性可以快速部署到开发者的项目中,达到快速提高普及率的目的。
试想,如果没有CRA
的推动,Hooks
的lint
规则很难在开发者中有这么高普及率,Hooks
的理念也就不会这么快席卷整个前端框架领域。
从以上三点来看,Vite
完全可以成为比CRA
性能更优的替代品。
但是,React
团队的考量不仅如此。
虽然CRA
开箱即用,但他提供的能力并不全面,比如他并不提供:
状态管理方案
路由方案
数据请求方案
为什么不提供呢?因为在CRA
发展的时期,这些方案还未形成最佳实践。
随着时间发展,开发者逐渐摸索出解决这些问题的最佳实践。比如请求瀑布问题,考虑如下组件:
function App() { const [data, update] = useState(null); useEffect(() => { fetch('http://...').then(res => update(res.json())) }, []) return <Child data={data}/>}复制代码
只有当App
组件渲染后才能开始请求数据,这个请求时机是比较滞后的,如果Child
依赖data
来请求自己的数据,那么由于App
请求的滞后导致Child
的请求也滞后了,这就是请求瀑布问题。
这个问题常见的解决方法是 —— 将请求数据的逻辑收敛到路由方案中。
再比如,随着业务不断迭代,业务代码体积越来越大,常见的优化手段是懒加载组件。
但是,手动执行懒加载常常会产生意料之外的问题。比如,页面中有个图表组件<Chart/>
,如果开发者懒加载了这个组件,但是该组件在on mount
时请求数据,这又会陷入请求瀑布问题。
要彻底解决这个问题,需要配合3类技术方案:
数据请求方案(解决数据流向问题)
路由方案(解决数据请求时机问题)
打包方案(解决懒加载的实现问题)
类似的问题还有很多,比如CSR
首屏渲染速度慢的问题(需要通过SSR
解决)。
可见,CRA
仅仅提供了CSR
环境下一个开箱即用的模版,但是随着项目变得越来越复杂,一些业务细节问题CRA
是没有提供开箱即用的解决方案的。
从这个角度看,即使切换到Vite
还是会面临同样的问题。
随着各种常见问题的最佳实践被探索出来,逐渐诞生了一些以React
为基础,集成各种业务问题最佳实践的框架,比如Next.js
、Remix
。
其中,Remix
就是以React-Router
(路由解决方案)为基础,逐渐发展出来的囊括路由、数据请求、渲染为一体的全栈框架。
那么,能否将CRA
迭代为类似Next.js
、Remix
这样的全栈框架,一劳永逸解决CRA
对各种最佳实践的缺失呢?
React
团队认为,这样做需要极高的开发成本,而且随着时代发展,总会出现更多CRA
不支持的最佳实践(就像他当前面临的问题一样),那么CRA
终有一天会被再度淘汰。
所以,这个方案不可取。
既然这个方案不可取,那么用Vite
取代CRA
的方案也不可取。因为单纯使用Vite
并没有解决最佳实践的缺失,必须在此基础上实现那些最佳实践(比如路由、数据请求…),那又回到了开发一个全栈框架。
最终,React
团队更倾向如下解决方案:将CRA
作为一个脚手架工具,启动后会根据用户的不同场景需要(比如是SSR
还是CSR
)推荐不同的框架,再将CRA
作为不使用框架情况下的兜底方案。
并且,在实现上,可能将兜底方案中的webpack
切换为Vite
。
从React
团队的思考可以发现,React
始终将自己定位为一个状态驱动UI的库。
随着时代的发展,单独使用这个库已经不能满足日常开发需要,基于底层使用React + 实现各种最佳实践模式的框架会越来越流行。
最近,Next.js
达到了10wstar
成就,成为Github
中star
排名第14的仓库,间接印证了这种趋势。
回到开篇的问题:React
为什么不将Vite
作为默认推荐?
如果是用Vite
取代webpack
作为CRA
的打包工具,未来可能会。但是,这不是最首要的问题。
如何协助上层的框架更好的服务开发者,才是React
团队首要考虑的问题。
React
不死,他只会逐渐移居幕后。
【推荐学习:javascript视频教程】
php实现字符串去掉头尾的方法:1、使用ltrim函数删除字符串开头的空白字符或其他字符;2、使用rtrim函数删除字符串末端的空白字符或者其他字符即可。
本教程操作环境:Windows10系统、PHP8.1版、DELL G3电脑
php 怎么实现字符串去掉头尾?
PHP字符串去除首尾指定字符的trim ltrim rtrim函数
今天,我们看一个比较简单的处理字符串函数,我们都知道在表单提交的过程中,可用户输入的内容不一定就是和你想的一样合法的数据,就比如输入用户名或者邮箱的时候在input框空了几个空格,这样进入数据库中的数据自然也会多出几个空格显然不太符合逻辑,所以一般我们可以用trim来去除前后空格的效果。
trim函数
语法:trim ( string $str [, string $character_mask ] ) : string
作用:删除字符串首尾处的空白字符(或者其他字符)
返回值:返回处理后的字符串
同系列的函数有ltrim rtrim函数
ltrim — 删除字符串开头的空白字符(或其他字符)
rtrim — 删除字符串末端的空白字符(或者其他字符)
一个参数
函数里面只使用一个参数只能去除指定字符。不使用第二个参数,trim() 仅删除以下字符:
两个参数
通过指定 character_mask,可以指定想要删除的字符列表。简单地列出你想要删除的全部字符。或者通过使用 .. 格式,可以指定一个范围。
#trim去除前后指定字符列表 $str = "Hello Tacks"; var_dump($str); var_dump(trim($str,'Hes')); #删除 $binary 开头的 ASCII 控制字符 $binary = "\x09Example string\x0A"; // (从 0 到 31,包括 0 和 31) $clean = trim($binary, "\x00..\x1F"); var_dump($clean);
emmm,其他两个函数ltrim和rtrim函数用法一致,这里就不再累述。
小例子
你以为就要结束了吗,你以为这个函数很简单。那你看一下下面的代码。
$str = "Hello php"; var_dump($str); var_dump(rtrim($str,'hp'));
如果你觉得处理后的字符串是'Hello p',那么你就还是没真正意义上理解这个函数第二个参数的用法。
首先我用的是rtrim函数,去除字符串尾部的特定字符,第二这个特定字符是也就是第二个参数,是一个字符列表,那么就是字符串从右向左不断匹配字符列表里面的字符,所以会匹配p和h,因此最终的结果是'Hello '。所以还是不要大意而用错函数达不到你想要的结果。
推荐学习:《PHP视频教程》
php判断字符串是否有中文的方法:1、新建一个php文件;2、使用header()方法设置页面的编码格式为utf-8;3、将一个含有中文的字符串存放在$str变量中;4、使用preg_match函数利用正则匹配中文;5、通过if语句对上一步的结果进行判断,然后通过echo输出不同的提示结果即可。
本教程操作环境:Windows10系统、PHP8.1版、DELL G3电脑
php 怎么判断字符串是否有中文?
新建一个php文件,命名为test.php,用于讲解PHP中如何判断字符串是否含有中文。愁栗争
在test.php文件中,使用header()方法设置页面的编码格式为utf-8,避免页面输出中文时乱码。
在test.php文件中,将一个含有中文的字符串存放在$str变量中。
在test.php文件中,使用preg_match()函数利用正则匹配中文,结果保存在$res变量中。如果字符串中没有中文,结果为false。
在test.php文件中,通过if语句对上一步的结果进行判断,并通过echo输出不同的提示结果。
在浏览器打开test.php文件,查看结果。
推荐学习:《PHP视频教程》
Linux PATH环境变量是决定Shell将到哪些目录中寻找命令或程序的一个参数,它的内容是由一堆目录组成的,各目录之间用冒号 “:” 隔开。当执行某个Linux命令时,Linux会依照PATH环境变量中包含的目录依次搜寻该命令的可执行文件,一旦找到,即正常执行;反之,则提示无法找到该命令。
本教程操作环境:linux7.3系统、Dell G3电脑。
什么是环境变量
Linux是一个多用户操作系统,每个用户都有自己专有的运行环境。用户所使用的环境由一系列变量所定义,这些变量被称为环境变量。系统环境变量通常都是大写的。
每个用户都可以根据需要修改自己的环境变量,以达到自己的使用要求。常见的环境变量如下表:
序号 | 变量 | 说明 |
---|---|---|
1 | PATH |
决定了Shell 将到哪些目录中寻找命令 或程序 ,这个变量是在日常使用中经常 需要修改的变量 |
2 | TERM |
指定系统终端 |
3 | SHELL |
当前用户Shell类型 |
4 | HOME |
当前用户主目录 |
5 | LOGNAME |
当前用户的登录名 |
6 | USER |
当前用户名 |
7 | HISTSIZE |
历史命令 记录数 |
8 | HOSTNAME |
指主机 的名称 |
9 | LANGUAGE |
语言 相关的环境变量 ,多语言 可以修改此环境变量 |
10 | MAIL |
当前用户的邮件 存放目录 |
11 | PS1 |
基本 提示符 :(1) root 用户是# (2) 普通 用户是$ |
12 | PS2 |
附属 提示符,默认是 > |
13 | LS_COLORS |
ls 命令结果颜色 显示 |
Linux PATH环境变量
Linux 中的 PATH 环境变量 的内容是由一堆目录组成的,各目录之间用冒号 “:” 隔开。当执行某个 Linux 命令时,Linux 会依照 PATH 环境变量中包含的目录依次搜寻该命令的可执行文件,一旦找到,即正常执行;反之,则提示无法找到该命令。
说明
也就是说当用户在 shell 命令行界面中输入一个 外部命令 时, shell 必须搜索系统来找到对应的程序。 PATH 环境变量定义了用于进行命令和程序查找的目录。
如果命令或者程序的位置没有包括在 PATH 变量中,那么如果不使用绝对路径的话, shell 是没法找到的。如果想要在虚拟目录结构中的任何位置执行某个程序,办法是把这个程序所在的目录添加到 PATH 环境变量中,或者把这个程序放在 / 链接(ln) 到已经存在 PATH 中的目录下。
查看PATH环境变量
命令行中输入
echo $PATH
或者输入:
export
例1:查看环境变量 echo $PATH
例2:查看环境变量 export
相关推荐:《Linux视频教程》
区别:1、动态库的后缀为“.so”,静态库的后缀为“.a”。2、如果静态函数库改变了,那么程序必须重新编译;而动态函数库的改变并不影响程序。3、相对于静态库,动态库在编译的时候并没有被编译进目标代码中,用户的程序执行到相关函数时才调用该函数库里的相应函数,因此动态函数库所产生的可执行文件比较小。
本教程操作环境:linux7.3系统、Dell G3电脑。
一、库的基础概念:
在windows平台和linux平台下都大量存在着库。本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。由于windows和linux的本质不同,因此二者库的二进制是不兼容的。通俗的说就是把这些常用函数的目标文件打包在一起,提供相应函数的接口,便于程序员使用。在使用函数时,只需要包对应的头文件即可。按照库的使用方式又可分为动态库和静态库,在不同平台下对应后缀也有所不同。
WINDOWS下:.dll 后缀为动态库,.lib 后缀为静态库;
LINUX下:.so
后缀为动态库,.a
后缀为静态库。
二、静态库与静态链接
<1>静态库:
静态库可以简单的看成一组目标文件的集合,即很多目标文件经过压缩打包后形成的文件。比如在我们日常编程中,如果需要使用printf函数,就需要包stdio.h的库文件,使用strlen时,又需要包string.h的库文件,可是如果直接把对应函数源码编译后形成的.o文件直接提供给我们,将会对我们的管理和使用上造成极大不便,于是可以使用“ar”压缩程序将这些目标文件压缩在一起,形成libx.a静态库文件。
注:静态库命名格式:lib + "库名称”+ .a(后缀) 例:libadd.a就是一个叫add的静态库
<2>静态链接:
对于静态库,程序在编译链接时,将库的代码链接到可执行文件中,程序运行时不再需要静态库。在使用过程中只需要将库和我们的程序编译后的文件链接在一起就可形成一个可执行文件。
通过一个例子来了解下如何将我们自己写的头文件和代码同时进行编译链接,最终生成可执行文件:
/main.c/ #include <stdio.h> #include "add.h" int main() { int ret = add(3, 4); printf("3 + 4 = %d\n",ret); return 0; } /add.c/ #include "add.h" int add( int x, int y) { return x + y; } /add.h/ #pragma once #include <stdio.h> int add( int x, int y); /Makefile/ main : main.c libadd.a gcc main.c -L . -ladd -o main //-L为指定路径 .为当前目录下 -l+库名字,编译器可在指定目录下自己寻找名为add的库文件 libadd.a : gcc -c add.c -o add.o //ar -rc将多个编译后的文件打包为一个静态库文件 ar -rc libadd.a add.o .PHONY:clean clean: rm main libadd.a
make后输出截图:
<3>缺点:
1、内存和磁盘空间浪费:静态链接方式对于计算机内存和磁盘的空间浪费十分严重。
假如一个c语言的静态库大小为1MB,系统中有100个需要使用到该库文件,采用静态链接的话,就要浪费进100M的内存,若数量再大,那浪费的也就更多。例如下图:程序1和程序2都需要用到Lib.o,采用静态链接的话,那么物理内存中就会存放两份对应此文件的拷贝。
2、更新麻烦:
比如一个程序20个模块,每个模块只有1MB,那么每次更新任何一个模块,用户都得重新下载20M的程序。
三、动态库与动态链接
<1>动态库:
程序在运行时才去链接动态库的代码,多个程序共享库的代码。一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码。
注:动态库命名格式:lib + "库名称”+ .so(后缀) 例:libadd.so就是一个叫add的动态库
<2>动态链接:
由于静态链接具有浪费内存和模块更新困难等问题,提出了动态链接。基本实现思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将他们链接在一起形成一个完整的程序,而不是像静态链接那样把所有的程序模块都链接成一个单独的可执行文件。所以动态链接是将链接过程推迟到了运行时才进行。
同样,假如有程序1,程序2,和Lib.o三个文件,程序1和程序2在执行时都需要用到Lib.o文件,当运行程序1时,系统首先加载程序1,当发现需要Lib.o文件时,也同样加载到内存,再去加载程序2当发现也同样需要用到Lib.o文件时,则不需要重新加载Lib.o,只需要将程序2和Lib.o文件链接起来即可,内存中始终只存在一份Lib.o文件。
动态库和动态链接的例子依然使用上面的代码,输出结果也相同,唯一需要改变的就是Makefile文件。
/Makefile/ main : main.c libadd.so gcc main.c -L . -ladd -o main libadd.so : gcc -fPIC -shared add.c -o libadd.so //-shared表示输出结果是共享库类型的 -fPIC表示使用地址无关代码奇数来生产输出文件 .PHONY:clean clean: rm main libadd.so
当我们生成可执行文件后,可使用ldd命令查看该可执行文件所依靠的动态库。
前面提到windows和Linux下库文件的后缀不同,更根本的原因在于二者文件格式都不同。可以通过file一个动态库查看Linux下动态库的文件类型其实是ELF格式。ELF动态链接文件被称为动态共享对象(DSO,Dynamic Shared Objects),简称共享对象;在windows下,动态链接文件被称为动态链接库(Dynamic Linking Library),也就是.dll文件后缀的全称。
<3>优点:
①毋庸置疑的就是节省内存;
②减少物理页面的换入换出;
③在升级某个模块时,理论上只需要将对应旧的目标文件覆盖掉即可。新版本的目标文件会被自动装载到内存中并且链接起来;
④程序在运行时可以动态的选择加载各种程序模块,实现程序的扩展。
四、静态库和动态库的区别
1. 静态库
这类库的名字一般是 libxxx.a ;利用静态函数库编译成的文件比较大,因为整个 函数库的所有数据都会被整合进目标代码中,他的优点就显而易见了,即编译后的执行程序不需要外部的函数库支持,因为所有使用的函数都已经被编译进去了。当然这也会成为他的缺点,因为 如果静态函数库改变了,那么你的程序必须重新编译 。
2. 动态库
这类库的名字一般是 libxxx.so ;相对于静态函数库,动态函数库在编译的时候并没有被编译进目标代码中,你的程序执行到相关函数时才调用该函数库里的相应函数,因此动态函数库所产生的可执行文件比较小。由于函数库没有被整合进你的程序,而是程序运行时动态的申请并调用,所以程序的运行环境中必须提供相应的库。 动态函数库的改变并不影响你的程序,所以动态函数库的升级比较方便。
相关推荐:《Linux视频教程》
欢迎访问开云体育APP官方下载(KAIYUN)旗下有开云体育手机app下载,开云体育app下载,开云体育官网入口,开云体育app下载官网,开云体育app官网入口手机版等业务,开云体育平台app安全靠谱信誉高!开云体育官网入口
美国作家 H. Jackson Brown Jr. 说过一句很有名的话:
欢迎访问开云体育APP官方下载(KAIYUN)旗下有开云体育手机app下载,开云体育app下载,开云体育官网入口,开云体育app下载官网,开云体育app官网入口手机版等业务,开云体育平台app安全靠谱信誉高!开云体育官网入口
大结局了!《狂飙》——姐姐范穿搭教科书:孟钰、陈书婷、程程、高启兰,你更 pick 谁?
虽然后期剧情上有些许遗憾,但这部剧还是堪称制作精良,人物丰满的塑造和演员深厚的表演功底,让人一秒入戏,F 在吃到安利后,也没忍住熬夜追剧 …..
在以男性为主的脚本里,女性角色戏份不多,但每一个都性格迥异且鲜明,充满了人格魅力,可以说是集齐了当下大爱的各款 ” 姐姐 “~(这个编剧是懂群众的)
代表人物:陈书婷、程程
剧中陈书婷和程程都是气场十足的御姐型,要说两人之间的区别,则是陈书婷多了些风情万种的女人味,程程偏向又酷又飒的职场女强人。
其中,高叶扮演的陈书婷凭借着角色的人格魅力更是掀起全网 ” 大嫂热 “,从妆容、发型到穿搭都被网友们翻来覆去讨论了一遍又一遍。
而据说整部剧的妆容都是高叶自己完成的,有时还会结合角色去改良化妆师原本的妆容!” 大嫂 ” 有点东西在身上!
于是,很多网友在线求教程,高叶也非常 nice,亲自上手教授。
戏里的陈书婷常常让 F 想到《危险关系》里的陈数,也是靠着一头精致微卷的短发和复古耳钉,辅以烈焰红唇来凸显强势干练的女人味。
偶尔一套简约的羊绒大衣加珍珠耳饰,刻入骨子的刚强里流露出一分柔情。
给大家提供三种增加气场的单品
PART
大衣
大衣款式很多,秋冬必备单品,有质感版型好的大衣自带气场。
PART
夸张耳饰
选购的要点是显眼,御姐要的就不是小家碧玉感,剧里的陈书婷已经很好地给我们来了一波示范。(大一点的耳饰还能显脸小!)
PART
皮衣
皮衣,永不过时的时髦单品。
可以无违和地搭上各种单品,柔美型女生穿上后制造出的反差更让人心动。
第二类姐姐:轻熟文艺风
代表人物:孟钰
作为京海市公安局局长的女儿,也是安欣青梅竹马的 ” 妹妹 “,自幼生活环境的富足,令她天生攻击性少,单纯中带着些许的傲气,穿衣风格偏 ” 大家闺秀 “,前期有少女的浪漫,即使后来人到中年,所嫁非爱和后期丈夫被捕入狱,她的穿衣风格也依旧端庄大气。
此类风格推荐单品
PART
纯色连衣裙
PART
阔腿裤
参考之前孟钰约会时穿的那身,阔腿裤的裤型对梨形身材太友好了,姐姐必备单品 ~
第三类姐姐:冷酷高知感
代表人物:高启兰
高启兰虽然出身贫寒,但靠着 2 个哥哥的庇护,没怎么受过苦,长大后顺利当了三甲医院的医生。
这个角色其实很有意思,是剧里给人形象反差最大的,前期还是个普通的邻家女孩。
《狂飙》的爆火再次印证了国产剧能产出佳作,包括之前的《风吹半夏》,戏中不止有许多 ” 名场面 “,连服装造型都用了心思,同样是因为角色需要使用了大量的奢侈品,但不像 F 吐槽过的《爱的二八定律》和《玫瑰之战》,原因就在于造型团队对角色在不同时期和场合的把握,可以很直观地看到人物穿衣品味的变化,而不是盲目追求彰显财富的装扮。
谢谢你喜欢我们的文章,更欢迎你后台与我交流各种时尚心得。