搶進電動車市場,電容量提高的鎂電池也要分杯羹

 

雖然鋰離子電池在市場上的聲勢居高不下,但安全性太招人質疑,現在又有一款主打更便宜、更安全的新電池要跟電動車市場分一杯羹了。一款由休士頓大學研究團隊開發的新型鎂電池,標榜電容量比商用鋰離子電池高出2 倍。

鎂電池最大的缺點在於充、放電過程中,充電性能會迅速衰退,過去由於鎂電池一直難找到能存儲大量鎂離子的陰極材料,不光打破氯化鎂鍵只為儲存鎂離子是很困難的技術,以這種方式產生的鎂離子在電池內移動速度也緩慢,種種原因都會導致電池效率低下,大好前程因此遭阻擋。目前,鎂電池主要供軍事通信、氣象測候儀、海難救生設備、高空雷達儀等設備使用。

現在,休士頓大學電機電子工程學系副教授Yan Yao 團隊在《自然通訊》雜誌發表最新研究,他們設計了奈米結構的電池陰極,讓新型鎂電池大大增加4 倍存儲容量。早期鎂電池儲存容量為100mAh / g,相比之下新電池可達400mAh / g,而一般的商業鋰離子電池陰極容量則約200mAh / g。

新型鎂電池將氯化鎂注入二硫化鈦的陰極材料以儲存能量,由於保留氯化鎂鍵沒有破壞,與傳統鎂電池相比,離子擴散速度比傳統材料快許多。Yan Yao 說,保留氯化鎂鍵可使陰極儲存的電荷翻倍,不過由於氯化鎂分子較大,所以關鍵就在擴大陰極材料的開口,以便容納更多氯化鎂。

於是,研究人員動手將二硫化鈦材料的開口撐大了300 倍──雖然還是很小,從0.57 奈米增加到1.8 奈米,但這樣便足夠讓氯化鎂分子通過。大容量的氯化鎂鍵有優異速度和循環性能,也為多價離子電池的未來開闢更多可能性。

高壓鋰離子電池雖然還是鞏固自己的市占率,但其成本高、內部結構易發生破壞導致起火等潛在問題,都使其隨時可能被更便宜、更安全的鎂電池、鋅電池等篡位。

新款鎂電池的電壓在1 伏特左右(鋰離子電池的電壓3到4 伏特),它在一定程度上受電壓限制,但研究人員仍期盼鎂電池的新設計能應用在更有價值的領域。Yan Yao 說:「我們的最高宗旨就是以更低價格製造更高能量電池,特別是在電動汽車領域,要和其他電池一較高下。」

(合作媒體:。首圖為鹼性電池,圖片出處:public domain CC0)  

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

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

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

北京推廣新能源車,擬大力新建公用充電樁

北京日報消息,北京城市管理委員會表示,已正式發文要求加強電動汽車充電基礎設施建設和管理,其中,新建停車場必須預留配建充電設施條件,全市機關單位內部停車場應含充電停車位,新建社區停車位須100%具有安裝充電樁條件。

資料顯示,截至今年7月底,北京市新能源汽車數量達14.14萬輛,全市已累計建成約9.75萬個充電樁,基本形成六環內平均服務半徑5公里的公用充電網,其中,個人自用樁約6.87萬個,配建比約75%,商場商圈、交通樞紐等公共停車區域建成1,895處、約1.75萬個。

北京市城管委相關負責人表示,大力新建公用充電樁是破解新能源車推廣困難的重要工作之一,日前市政府已發佈《關於進一步加強電動汽車充電基礎設施建設和管理的實施意見》,首次對新建、既有建築的停車位配建充電設施作出了量化要求。

新規定將充電設施配建指標納入規劃設計規範,明確新建建築配建停車場及公共停車場中充電設施的建設比例或預留建設安裝條件。其中,辦公類建築不低於配建停車位的25%,商業類及公共停車場庫(含P+R停車場)不低於20%,居住類按照配建停車位的100%規劃建設,其他類公建如醫院、學校、文體設施等不低於15%規劃建設。

另外,新建社區100%規劃建設,係指每個停車位都要具備安裝條件,讓車主買車後就能直接裝充電樁,不用再申請改電增容。

同時,對於具備電源條件的既有辦公區、大型商場等公用停車場,新規要求必須配建不低於車位數量10%的公用充電樁。對於老舊社區住戶而言,購買電動車最難的關卡就是無法安裝充電樁,而此次頒佈的新規將充電設施建設納入北京市老舊社區綜合改造範圍內,引導物業公司、業主委員會積極支持和配合充電設施建設。

此次新規也提出,北京全市各級機關、企事業單位內部停車場都要配建公用充電設施。按規定,北京市各級公共機構(包括各級政府機關、事業單位、社會組織)和國有企業新建內部停車場,充電樁數量應不低於車位數的25%,或預留建設安裝條件。

(本文內容由授權使用。圖片出處:public domain CC0)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

Hook原理–逆向開發

今天我們將繼續講解逆向開發工程另一個重要內容–Hook原理講解。Hook,可以中文譯為“掛鈎”或者“鈎子”,逆向開發中改變程序運行的一種技術。按照如下過程進行講解

  1. Hook概述
  2. Hook技術方式
  3. fishhook原理及實例
  4. 符號表查看函數名稱
  5. 總結

一、Hook概述

在逆向開發中是指改變程序運行流程的技術,通過Hook可以讓自己的代碼運行在別人的程序中。需要了解其Hook原理,這樣就能夠對惡意代碼攻擊進行有效的防護。

 

二、Hook技術方式

2.1 Method Swizzle方式

Method Swizzle 上次已經講到,是利用OC的Runtime的特性,去動態改變SEL(方法編號)與IMP(方法實現)的對應關係,達到OC方法調用流程更改的目的。也是主要用於OC方法。

2.2 Cydia Substrate方式

Cydia Substrate 原名叫做Mobile SubStrate,主要作用為針對C函數,OC函數以及函數的地址進行Hook操作。並且有個很大的優勢,Cydia Substrate 並不是僅僅是針對iOS設計,Andriod一樣也可以使用。

2.2.1

Cydia Substrate定義了一系列的函數和宏,底層調用了objc的runtime和fishHook來替代目標函數或者系統方法。

其中有兩個函數

  • MSHookMessageEx主要用於OC方法
void MSHookMessageEx(Class class, SEL selector, IMP replacement, IMP result)
  • MSHookFunction主要用於C++和C函數
void MSHookFunction(voidfunction,void* replacement,void** p_original)

2.2.2 MobileLoader

MobileLoader主要用於加載第三方dylib運行的應用程序中。啟動時MobileLoader會根據指定的第三方動態庫加載進去,第三方動態庫也是我們寫的破解程序。

2.2.3 safe mode

破解程序的本質在於dylib,寄生於別人程序進程中。但是系統進程一旦出現錯誤,可能會導致整個進程崩潰,也可能會導致iOS程序崩潰。在Cydia Substrate 中引入了安全模式,如果一旦錯誤,三方的dylib會被禁用,便於查錯和修復。

2.3 fishHook

fishHook是Facebook提供一種動態修改鏈接Mach-O文件的工具。此利用Mach-O文件加載原理,通過修改非懶加載和懶加載兩個表的指針達到C函數的Hook的目的。

今天我們主要講解第三種方式fishHook達到更改程序的目的。

 

三、fishhook原理及實例

3.1 概述

fishhook的源碼地址為

fishhook的主要方法有兩個還有一個結構體

查看代碼結構為,將紅色圈起來部分移入到代碼中,即可使用fishhook來hook代碼。

 

 3.2 實例

3.2.1 Demo1實例1

// rebinding 結構體的定義 // struct rebinding { // const char *name; // 需要 HOOK 的函數名稱,字符串 // void *replacement; // 替換的新函數(函數指針,也就是函數名稱) // void **replaced; // 保存原始函數指針變量/地址的指針(它是一個二級指針!) // }; // C 語言傳參是值/址傳遞的,把它的值/址穿過去,就可以在函數內部修改函數指針變量的值

- (void)viewDidLoad {
    [super viewDidLoad];
     NSLog(@"123"); //rebinding結構體
    struct rebinding nslog;
    nslog.name = "NSLog";// 函數名稱
    nslog.replacement = myNslog; // 新的函數指針
    nslog.replaced = (void *)&sys_nslog;// 保存原始函數地址的變量的指針
    //rebinding結構體數組
    struct rebinding rebs[1] = {nslog};
    /**
     * 存放rebinding結構體的數組
     * 數組的長度
     */
    rebind_symbols(rebs, 1);
}
//---------------------------------更改NSLog-----------
//函數指針,用來保存原始的函數地址 (C 語言語法,函數指針類型變量)
static void(*sys_nslog)(NSString * format,...);
//定義一個新的函數
void myNslog(NSString * format,...){
    format = [format stringByAppendingString:@"勾上了!\n"];
    //調用原始的
    sys_nslog(format);
}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSLog(@"點擊了屏幕!!");
}

上面的代碼運行結果如下:

3.2.2 Demo2實例2

void func(const char * str){
    NSLog(@"%s",str);
}

- (void)viewDidLoad {
    [super viewDidLoad];
    //rebinding結構體
    struct rebinding nslog;
    nslog.name = "func";
    nslog.replacement = new_func;
    nslog.replaced = (void *)&old_func;
    //rebinding結構體數組
    struct rebinding rebs[1] = {nslog};
    /**
     * 存放rebinding結構體的數組
     * 數組的長度
     */
    rebind_symbols(rebs, 1);
}
//---------------------------------更改NSLog-----------
//函數指針
static void(*old_func)(const char * str);
//定義一個新的函數
void new_func(const char * str){
      NSLog(@"%s + 1",str);
}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    func("哈哈");
}

運行結果如下:

從上面可以看出自定義的交換方法為什麼交換不了呢?首先可以肯定的是代碼是OK的,下面我們講解原理,為什麼自定義的方法不行呢?

 

 3.3 原理探究

Mach-O文件是如何加載的?

Dyld工具動態加載,加載MachO文件完成后,開始加載依賴的動態庫,也就是通過上篇博客的image List 可看到相關的類庫。

PIC(Promrammable Interrupt Controller)位置代碼獨立,由外設發出中斷請求需要中斷控制器來處理。

Mach-O文件內部調用系統函數時:

  • Mach-O _data段建立了一個指針(也就是符號,實現指向內部的函數調用,指向了外部的函數地址),指向了外部函數(dyld),可讀可寫,當Mach-O被加載進去,就會指向所指的函數。
  • Dyld會動態的綁定,將Mach-O中的data 段中指針指向了外部的函數,也是Dyld為什麼叫做動態綁定的原因。

這也回答了上面的問題,為什麼內部/自定義的函數不能修改,只能修改Mach-O文件的外部函數,如果是另外一個動態庫或者需要動態符號綁定的就可以(符號表中能找到才可以實現)

 

下面我們是真實查看內容,通過實例

利用第一個Demo來測試,運行起來,然後查看可執行文件,通過MachoView工具

 

從圖2看出offset偏移地址為3028,也就是NSLog函數文件的偏移地址,懶加載此表時在Mach-O文件偏移地址+函數偏移的地址。

下面以Demo1查看,在Demo1打斷點,查看Mach-O函數偏移地址,通過指令image list 第一個就是Mach-O內容和地址(本人上篇博客地址即可)

Mach-O在內存的偏移地址也就是Mach-O的真實地址,發現為 0x000000010a9c5000

通過上面紅色加重算法,計算Mach-O文件Data段的函數指針

發現執行完只有就會被綁定。NSLog函數文件就會被綁定。

下面再看一下,對於屏幕點擊的,hook如下

前提是我們去除ViewDidLoad方法裏面的NSLog(@“123”)這句代碼,運行代碼,最後將斷點斷在touchesBegan裏面,此時開始看地址和內容

截圖的前兩次打印是程序運行時,但是未曾點擊touchesBegan,后兩次是點擊屏幕時斷點進入到了裏面,再看內容,打印的對象是NSLog還是myNslog,通過上面發現是myNslog,說明Hook成功。

通過上面可看出,fishhook能夠Hook c函數,是因為Mach-O文件特點,PIC位置代碼獨立造就了靜態語言C也有動態的部分,之後通過Dyld進行動態綁定的時機,在這其中我們就可以做手腳,替換自定義的方法。

fishhook是根據方法字符串的名字“NSLog”,它是怎麼找到的呢?下面將講解利用符號表查看函數名稱字符串。

 

四、符號表查看函數名稱

 再次查看Mach-O文件,查看懶加載表中的NSLog函數

懶加載表是和動態符號表是一一對應關係,通過上面發現NSLog函數時第一個,而對應的Dynamic Symbol table也是第一個,打開Dynamic Symbol table

查看Dynamic Symbol Table 第一個也是NSLog,查看Data值為7A,對應的十進製為122,然後到Symbols Table裏面查看122,如下:

 

查看Symbols Table的data值為0000009B,然後在String Table Index去看函數偏移值為0000009B的內容,如下:

 

 為什麼選擇00004F94查看NSLog呢,我們從上面得知Symbols Table的data值為0000009B,然後加上String Table的函數第一個地址為00004F04,然後將0000009B + 00004F04 = 0X4F9F,最後看00004F94裡面包含了0X4F9F,藍色內容看出是NSLog內容,也就是找到啦。完美!!!

以上過程可以在fishhook中github上有說明圖:

 

上面的說明圖也就是通過符號表查看函數名稱以及反過來也可以逆查的過程。配上說明圖,方便大家熟悉流程。

 

五、總結

上面講述了Hook的幾種技術方式以及fishhook的原理探究,以及如何讓別人的app實現自己的代碼。下面我們對此總結一下,寫了一個本篇博客的整個過程便於大家整理,希望對大家有所幫助加深理解。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

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

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

.NET開發者的機遇與Web Blazor基礎(有彩蛋),.NET Core 又一殺器! Web Blazor框架橫空出世!

 一.嘮嘮WebAssembly的發展歷程

  目前有很多支持WebAssembly的項目,但發展最快的是Blazor,這是一個構建單頁面的.NET技術,目前已經從Preview版本升級到了beta版本,微軟計劃在2020年5月發布Blazor的第一個版本。

  Blazor是什麼?它是一項將C#和.NET都放入瀏覽器的Microsoft技術。它使用WebAssembly來工作,WebAssembly是一種高性能的管道,可以將代碼預編譯為緊湊的二進制格式。最重要的是,每個主流瀏覽器(包括移動版本)都支持WebAssembly。

  十年前,JavaScript統治世界還不是很明顯。Flash和Silverlight也正在運行。這二個都需要使用瀏覽器插件來完成工作,並且都以不同的用戶界面方法替換了HTML。這種方法使他們在功能方面遙遙領先於JavaScript,但隨着移動互聯網的出現,他們就慢慢過時。

  但隨後從最初的Javascript再到微軟的JScript和CEnvi的ScriptEase三足鼎立,再到最後的統一標準,當時微軟憑藉Windows系統捆綁Internet Explorer的先天優勢擊潰Netscape后,兩大巨頭就此進入了長達數年的靜默期,JavaScript就是在這樣的情況下被構想出來的,當時的瀏覽器之王,Netscape Navigator創始人Marc Andreessen認為Netscape需要一種“glue language”來支持HTML,讓Web設計師和兼職程序員可以很容易地使用它來組裝諸如圖像和插件之類的組件,且代碼是可以直接寫在網頁標記中。除此之外微軟的步步緊逼也迫使Andreessen不得不聘請Brendan Eich,及早將Scheme編程語言嵌入到Netscape Navigator中。1995年,JavaScript以Mocha為名開發,並於9月在Netscape Navigator 2.0的測試版中首次發布,當時被稱為LiveScript,12月,在Netscape Navigator 2.0 beta 3中部署時被重命名為JavaScript 。雖然Netscape Navigator在Chrome、Internet Explorer和Firefox等多款瀏覽器的圍追堵截中最終落敗,但是JavaScript卻推動了網頁的發展,並一直被沿用至今。

  這是一個諷刺。在JavaScript征服世界的同時,播下了一顆很小的種子,這可能會在將來的某個時候暗示JavaScript的終結。那顆種子是名為asm.js的實驗技術。

  這是Mozilla的開發人員在2013年完成的一個古怪的實驗。他們正在尋找在瀏覽器中運行高性能代碼的方法。但是與插件不同,asm.js並未嘗試在瀏覽器旁邊運行。相反,它的目的是直接通過Javascript的虛擬化。

  從本質上講,asm.js是簡潔,優化的JavaScript語法。它比普通的JavaScript運行得更快,因為它避免了該語言的慢動態部分。但是認識到它的網絡瀏覽器也可以應用其他優化,從而大大提高性能。換句話說,asm.js遵循黃金法則- 不要破壞網絡 -同時提供通往未來改進的途徑。Firefox團隊使用asm.js以及名為的轉碼工具來獲取用C ++構建的實時3D遊戲,並將其放入Web瀏覽器中,並且僅在JavaScript和原始野心上運行。

  有人問為什麼asm.js好在哪裡,簡單而言,它的性能比JavaScript高几百倍,當然是在沒有谷歌的V8引擎之下,因為JavaScript是弱類型語言,它需要猜測你的數據類型來進行編譯,這樣的情況下,在我看來它肯定需要遍歷完一個方法,然後再進行運算,與其這樣我為什麼不打個標識呢?當然在不破壞JavaScript的情況下,arm.js選擇了一個騷氣的想法,如果你想你的數據類型是int,那麼聲明一個值就變成了變量名|0,就這樣它的目的就達到了。

  儘管asm.js實驗產生了一些令人眼花撩亂的演示,但工作的開發人員基本上忽略了它。對他們來說,這隻是超越現代的一個有趣方面。但這隨着WebAssembly的創建而改變。

  WebAssembly既是asm.js的後繼產品,又是一項截然不同的技術。這是一種緊湊的二進制代碼格式。像asm.js一樣,WebAssembly代碼也被輸入到JavaScript執行環境中。它具有相同的沙箱和相同的運行時環境。與asm.js一樣,WebAssembly的編譯方式也可以提高效率。但是現在,這些效率比有以前更加明顯,並且瀏覽器可以完全跳過JavaScript解析階段。對於普通的邏輯,WebAssembly遠比常規JavaScript快,幾乎與本機編譯的代碼一樣快。

   WebAssembly於2015年首次出現。如今,桌面和移動設備上的四大瀏覽器(Chrome,Edge,Safari和Firefox)已完全支持它。儘管可以通過將WebAssembly代碼轉換為asm.js來實現向後兼容,但Internet Explorer不支持它。就讓IE涼透吧!但需要注意的是WebAssembly無法迴避JavaScript,因為它已鎖定在JavaScript運行時環境中。實際上,WebAssembly需要與至少一些普通的JavaScript代碼一起運行,因為它不能直接訪問頁面。這意味着如果不通過JavaScript層,就無法操縱DOM或接收事件。

   聽我說起來,這是一個限制,但聰明的微軟開發者已經找到了走私的方法,在瀏覽器中下載一個微型.NET運行時,作為已編譯的WASM文件。此運行時處理JavaScript互操作,並提供基本服務,它能給我們提供GC或者其它用法。Blazor不是唯一一個由WebAssembly支持的實驗。考慮一下,它旨在將Python放入瀏覽器中,並帶有用於數據分析的高級數學工具包。據我所知這應該使用emscripten的編譯器。

   人們常說,何時Javascript能夠替代服務器端語言,又有人說什麼時候可以代替桌面級應用程序,所以WebAssembly並不是用來代替JavaScript的。而是為了解決現代問題,如果它做到了,那就真的做到了!所以作為一個程序員,你應該對WebAssembly引起足夠的重視,未來快速加載Web應用程序的需求肯定會增加。

   就現在我們的.NET Core提供了兩種Blazor模板,包括Blazor Server 以及 Blazor WebAssembly。

  • Blazor Server使用熟悉的.NET環境在Web服務器上運行代碼。訣竅是瀏覽器和服務器之間的通信方式。當用戶與頁面進行交互時,JavaScript代碼將回調到發生實際頁面生命周期的服務器。(要建立此連接,該頁面使用名為的Microsoft API )運行服務器端代碼后,Blazor Server呈現該頁面並將更改發送回Web頁面,該Web頁面將相應地進行更新。
  • Blazor WebAssembly使用由WebAssembly提供支持的微型.NET運行時在瀏覽器中運行代碼。您的客戶端代碼可以訪問許多熟悉的.NET庫,並且您使用C#語言編寫它,您仍然可以像在JavaScript頁面中一樣在Web服務器上調用API。

  Blazor Server是一種具有一些有趣用例的技術,但是由於不斷的通信,您顯然會犧牲一些性能-甚至不用問脫機功能。Blazor WebAssembly是受到最多宣傳的一種,也是我們在本文中探討的一種。

  關於Blazor,程序員最常見的誤解是將其C#代碼編譯為WebAssembly,然後發送到瀏覽器,然後執行。這種方法並非不可能-Blazor的創建者暗示他們將來可能會嘗試這種技術。但是如今Blazor的工作方式並不是如此。

  換句話說,如今的Blazor是當您訪問使用Blazor的網頁時,該頁面將從下載按比例縮小的.NET運行時開始。然後它將下載您的應用程序以及您的應用程序使用的任何其他.NET庫,所有這些都在其本機IL中。最後,Blazor運行時執行IL。

二.配置您的開發環境

   由於Blazor是一個預發布的早期Beta產品。基礎結構的關鍵部分正在發生變化,您將無法獲得與其他類型的Microsoft項目相同級別的工具支持。我嘗試在Visual Studio 2019中進行編碼,需要注意的是您需要勾選.NET FrameWork 4.8 以及 .NET Core 3.0 + ,這樣您才具有Web Assembly的項目。完成設置后,您可以輕鬆創建Blazor項目。只需啟動Visual Studio,創建一個新項目,然後選擇“ Blazor App”項目即可。Visual Studio會詢問您是否需要Blazor Server應用程序或Blazor WebAssembly應用程序.

 三.Blazor的數據綁定與組件傳值

  由於關於Blazor的一篇我編寫的文章,未能提及更深入的內容,那麼現在我將要介紹一下高級的Blazor用法,到最後還會有一個糖果,園友力作的Blazor UI!多麼激動人心的時刻,那麼趕快開始吧.

3.1 Child Component

  在Blazor的Child Component中可以使用[Parameter] 關鍵字,來進行傳值的定義,我們可以這麼來做,現在只是提一下這個概念,下面會仔細說下組件之間如何進行跨組件綁定值。

<div>
    <p>標題:@title</p>
</div>
@code{
    [Parameter]
    public string title { get; set; }
}

隨後在調用時,Visual Studio IDE 就可以直接向您的視覺進行提示輸入相關屬性。

<Demorazor title="Hello 博客園的兄弟們!"></Demorazor>

運行效果如下:

3.2 single Bind and Two-way binding

single bind就不用說了,新建項目自帶的模板Counter示例那就是如此。

@page "/counter"

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }
}

此處 @currentCount 值根據點擊按鈕的數量遞增Click me。<p>標記元素中的值會自動刷新,無需任何組件刷新。

two-way binding 我們可以自定義我們的事件 一共分為二中綁定方式 包括@bind 和 @Bind-Value,值得一提的是還可以通過使用event參數指定@bind-value屬性, 使用其他事件來綁定屬性或字段。例如第四個文本框就是綁定changeString採用oninput事件的屬性,以到達在文本框的值更改時激發,經過我的測試如果你的綁定事件是Javascript中不存在的,那麼也無妨,不會報出系統級別的異常,我想如果是從IL轉換到WebAssembly中,就會直接過濾掉,但是Visual Studio 2019 沒有給我們提示,也讓我們編譯通過,即使是當前的最高16.0.4 預覽版也是如此,這個是令我詫異的。

<p>
    <span>在這裏可以使用bind-value 或者 bind 當然這裏確保您不使用其它事件!</span>
    <input @bind-value="changeString" />
    <p>這是我輸入的內容: @changeString</p>
</p>
<p>
    <span>oninput</span>
    <input @bind-value="changeString" @bind-value:event="oninput" />
</p>

@code {
    string changeString = "";
}  

運行效果如下:

 3.3 Component bindings

   想要跨組件進行綁定屬性值,可以使用,@bind-{property}可在其中跨組件綁定屬性值,我們試着嘗試,首先我們創建一個子控件,這個blazor就叫Baby,有一個身份證Id的屬性和出生地址。

   EventCallback的用法非常廣泛,它可以跨組件共享方法和屬性,如不寫下面的兩個屬性,則就會報錯。

@page "/baby"
<h2>Child Compoent</h2>
<p>出生的Baby IdentityCard:@Baby_IdentityCrad_Id</p>
<h3>在{@Baby_new_Address} 生的</h3>
@code {
    [Parameter]
    public string Baby_IdentityCrad_Id{ get; set; }

    /// <summary>
    /// 這個屬性也是牛的雅皮~~~ hhh
    /// </summary>
    [Parameter]
    public string Baby_new_Address{ get; set; }
    
    [Parameter]
    public EventCallback<string> Baby_IdentityCrad_IdChanged { get; set; }

    [Parameter]
    public EventCallback<string> Baby_new_AddressChanged { get; set; }
}

   有什麼樣的兒子就會有什麼樣的爸爸? 現在我們創建出父親,那就直接叫做一個Father.razor吧~

@page "/father"
<h3>Father</h3>

<Baby @bind-Baby_IdentityCrad_Id="@id_Card"
      @bind-Baby_new_Address="@address">
</Baby>
<button class="btn btn-primary" @onclick="@ChangeTheYear">new baby()</button>
@code {
    public string id_Card { get; set; }
    public string address { get; set; }
    private void ChangeTheYear()
    {
        id_Card = Guid.NewGuid().ToString();
        address = "老張";
    }
}

運行效果如下:

 

 如果要在子組件中定義事件,則可以MouseEventArgs來接受設備上的事件,然後再進行附加事件。

[Parameter]
public EventCallback<MouseEventArgs> OnClick { get; set; }

四.級聯傳值

   在某些情況下, 使用組件參數將數據從祖先組件流式傳輸到附屬組件是不方便的, 尤其是在有多個組件層時。 級聯值和參數通過提供一種方便的方法, 使上級組件為其所有子代組件提供值。 級聯值和參數還提供了一種方法來協調組件。我們試着去構建一個例子,首先創建一個最頂層的組件。

@page "/myDome"
<p><span>姓名:</span><input @bind="@pName" /></p>
<p><span>年齡:</span><input @bind-value="@pAge" @bind-value:event="oninput"/></p>
<CascadingValue Value="@pName" Name="ProfileName">
    <CascadingValue Value="@pAge" Name="ProfileAge">
        <ParentComponent />
    </CascadingValue>
</CascadingValue>
@code {
    private string pName { get; set; } = "張三";
    private int pAge { get; set; } = 35;
}

ParentComponent.razor:

<div style="background-color:darkgray;width:200px;">
    <p>Parent Component</p>
    <div style="padding:10px;">
        <p> 年齡 :@Age</p>
        <ChildComponent />
    </div>
</div>
@code{
    [CascadingParameter(Name = "ProfileAge")]
    int Age { get; set; }
}

ChildComponent.razor:

<div style="background-color:beige;width:200px;">
    <p>Child Component</p>
    <p>名稱 : @Name.ToString()</p>
</div>

@code{
    [CascadingParameter(Name = "ProfileName")]
    string Name { get; set; }
}

 運行效果如下:

 

 可以發現,一級直接將二級和三級的組件進行了數據穿透,不過需要注意的是CascadingValue的Name一定要和CascadingParameter的Name相同,否則將會執行錯誤。

五.路由

   從古至今,任何大型的開發框架,都是具有路由的,否則可能將會無法工作,其實Blazor的啟動頁也就使用了路由,這是毋庸置疑的。當你的組件帶有 @page 指令時,將為生成的類指定  指定路由模板的。 在運行時,路由器將使用 RouteAttribute 查找組件類,並呈現哪個組件包含與請求的 URL 匹配的路由模板。

@page "/luyou"
@page "/luyou/{text}"

<h1>Blazor is @Text!</h1>

@code {
    [Parameter]
    public string Text { get; set; }

    protected override void OnInitialized()
    {
        Text = Text ?? "fantastic";
    }
}

運行效果如下:

在上面的示例中應用了兩個 @page 指令。 第一個允許導航到沒有參數的組件。 第二個 @page 指令採用 {text} 路由參數,並將該值分配給 Text 屬性。

關於Blazor的基礎入門咱們這篇就說到這裏,相信你一定覺得Blazor了不起!它是一個現代的開源框架。它也由一家擁有悠久歷史的公司擁有,該公司放棄了昨天的閃亮新技術。因此,大多數開發人員都應該謹慎對待Blazor。只要JavaScript能夠執行Blazor可以做的所有事情,而沒有下載大小,性能和新工具堆棧帶來的額外挑戰,大多數開發人員將一如既往。

這並不意味着Blazor不能在所有這些領域都佔有一席之地。它甚至可能成為.NET Web應用程序開發中的主導力量。但是如果我今天必須下注,這就是我要依靠的東西。WebAssembly是未來。但就目前而言,Blazor只是一種有趣的可能性。

六.彩蛋

就現在!我的好朋友宇辰正在開發一款名為Blazui的UI組件。它為什麼叫Blazui?

Blazor + Element UI = Blazui,Element UI 的blazor版本,無JS,無TS,用 .Net 寫前端的 UI 框架,非 Silverlight,非 WebForm,開箱即用!!

Blazui 演示地址:。QQ群:74522853,碼雲地址:

參考Blazor使用的前提條件:

  1. 安裝 .Net Core 3.0
  2. 安裝 VS2019
  3. 安裝所有 VS2019 Blazor Extension

現在Blazor正在逐漸變好,讓我們即刻出發!.NET Core 不只是開源!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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

GoStation電池交換站突破400座,電池交換成為市場主流

 

結合智慧能源與智慧交通的新創科技品牌Gogoro(睿能創意股份有限公司)7 日公布全台建置與營運中的GoStation 電池交換站已達400 站,再度創造新的里程碑。從2015 年7 月至今,Gogoro 在基隆到屏東的台灣西半部地區,平均每1.8 天即新增一座電池交換站,最近一個月,每日提供將近4 萬名車主接近17,000 顆的電池交換服務,電池交換服務已經成為台灣消費者購買電動機車時的首要選擇。

自從Gogoro 於2015 年在台北市設立首座電池交換站以來,在短短兩年多的時間,建置了400 座電池交換站,廣布於基隆到屏東的各個縣市,推升Gogoro 電動機車市佔率至85.1%,並穩居台灣機車市場第四名的寶座。在今年7 月開通雲嘉地區電池交換站後,暢騎台灣西半部,不再是夢想。同時六都的電池交換站建置更來到一公里一站。

Gogoro 行銷總監陳彥揚說:「我們會依據人口密集度、車輛密極度以及道路的重要性來建置及調度電池交換站。根據車主換電的大數據分析,換電最密集的電池交換站位於Gogoro 永和中正店,而換電的尖峰時刻不外乎是上、下班的時間。有趣的是,雖然全台已經有將近400 座電池交換站,但每名消費者平均只會造訪其中的3-4 站來更換電池。證明Gogoro 能源網路的大數據分析,能計算出消費者換電池的使用行為模式,滿足車主們的需求。」

走在環保、綠能尖端的Gogoro,目前共建置了兩座太陽能換電站,分別是八里公兒四電池交換站和Gogoro 師大和平店站,這兩站設有物聯網智慧平台,透過分析供電情況的螢幕,說明了包括減少碳排量、減少樹木砍伐面積、綠能總儲電量、城市電網和太陽能發電量等訊息,讓每名換電的民眾,清楚的知道,自己對環境的貢獻度。

陳彥揚說:「Gogoro 致力發展潔淨的智慧能源,希望具備能源調度能力的智慧電網,能成為城市的電力調節樞紐,以促成電力平衡。對於Gogoro 車主而言,Gogoro 不再僅是都會的通勤工具,而是更進一步深入使用者的生活,同時讓生活環境更環保、更健康。」

Gogoro 目前擁有近4 萬名車主,總共累積超過570 萬次的電池交換,總里程數超過1 億100 公里,已經替地球減少將近840 萬公斤的二氧化碳排放,隨著未來再生能源比例逐漸提升,Gogoro 的車主們將更對地球與環境產生更多正面的影響力。而Gogoro 更會透過大數據進行科學的規劃,以調控電池供應,未來,即便新增的萬名車主同步上路,也能確保能源及電池的調配無虞。

(合作媒體:。圖片出處:科技新報)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

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

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

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

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

7. SOFAJRaft源碼分析—如何實現一個輕量級的對象池?

前言

我在看SOFAJRaft的源碼的時候看到了使用了對象池的技術,看了一下感覺要吃透的話還是要新開一篇文章來講,內容也比較充實,大家也可以學到之後運用到實際的項目中去。

這裏我使用RecyclableByteBufferList來作為講解的例子:

RecyclableByteBufferList

public final class RecyclableByteBufferList extends ArrayList<ByteBuffer> implements Recyclable {

    private transient final Recyclers.Handle handle;

    private static final Recyclers<RecyclableByteBufferList> recyclers = new Recyclers<RecyclableByteBufferList>(512) {

        @Override
        protected RecyclableByteBufferList newObject(final Handle handle) {
            return new RecyclableByteBufferList(
                    handle);
        }
    };

      //獲取一個RecyclableByteBufferList實例
    public static RecyclableByteBufferList newInstance(final int minCapacity) {
        final RecyclableByteBufferList ret = recyclers.get();
        //容量不夠的話,進行擴容
        ret.ensureCapacity(minCapacity);
        return ret;
    }
      //回收RecyclableByteBufferList對象
    @Override
    public boolean recycle() {
        clear();
        this.capacity = 0;
        return recyclers.recycle(this, handle);
    }
}

我在上面將RecyclableByteBufferList獲取對象的方法和回收對象的方法給列舉出來了,獲取實例的時候會通過recyclers的get方法去獲取,回收對象的時候會去調用list的clear方法清空list裏面的內容之後再去調用recyclers的recycle方法進行回收。
如果recyclers裏面沒有對象可以獲取,那麼會調用newObject方法創建一個對象,然後將handle對象傳入構造器中進行實例化。

對象池Recyclers

數據結構

  1. 每一個 Recyclers 對象包含一個 ThreadLocal<Stack<T>> threadLocal實例;
    每一個線程包含一個 Stack 對象,該 Stack 對象包含一個 DefaultHandle[],而 DefaultHandle 中有一個屬性 T value,用於存儲真實對象。也就是說,每一個被回收的對象都會被包裝成一個 DefaultHandle 對象
  2. 每一個 Recyclers 對象包含一個ThreadLocal<Map<Stack<?>, WeakOrderQueue>> delayedRecycled實例;
    每一個線程對象包含一個 Map<Stack<?>, WeakOrderQueue>,存儲着為其他線程創建的 WeakOrderQueue 對象,WeakOrderQueue 對象中存儲一個以 Head 為首的 Link 數組,每個 Link 對象中存儲一個 DefaultHandle[] 數組,用於存放回收對象。

假設線程A創建的對象

  1. 線程A回收RecyclableByteBufferList時,直接將RecyclableByteBufferList的DefaultHandle 對象壓入 Stack 的 DefaultHandle[] 中;
  2. 線程B回收RecyclableByteBufferList時,會首先從其 Map<Stack<?>, WeakOrderQueue> 對象中獲取 key=線程A的Stack 對象的 WeakOrderQueue,然後直接將RecyclableByteBufferList的DefaultHandle 對象(內部包含RecyclableByteBufferList對象)壓入該 WeakOrderQueue 中的 Link 鏈表中的尾部 Link 的 DefaultHandle[]中,同時,這個 WeakOrderQueue 會與線程 A 的 Stack 中的 head 屬性進行關聯,用於後續對象的 pop 操作;
  3. 當線程 A 從對象池獲取對象時,如果線程 A 的 Stack 中有對象,則直接彈出;如果沒有對象,則先從其 head 屬性所指向的 WeakorderQueue 開始遍歷 queue 鏈表,將 RecyclableByteBufferList 對象從其他線程的 WeakOrderQueue 中轉移到線程 A 的 Stack 中(一次 pop 操作只轉移一個包含了元素的 Link),再彈出。

Recyclers靜態代碼塊

private static final int DEFAULT_INITIAL_MAX_CAPACITY_PER_THREAD = 4 * 1024; // Use 4k instances as default.
private static final int DEFAULT_MAX_CAPACITY_PER_THREAD;
private static final int INITIAL_CAPACITY;

static {
    // 每個線程的最大對象池容量
    int maxCapacityPerThread = SystemPropertyUtil.getInt("jraft.recyclers.maxCapacityPerThread", DEFAULT_INITIAL_MAX_CAPACITY_PER_THREAD);
    if (maxCapacityPerThread < 0) {
        maxCapacityPerThread = DEFAULT_INITIAL_MAX_CAPACITY_PER_THREAD;
    }

    DEFAULT_MAX_CAPACITY_PER_THREAD = maxCapacityPerThread;
    if (LOG.isDebugEnabled()) {
        if (DEFAULT_MAX_CAPACITY_PER_THREAD == 0) {
            LOG.debug("-Djraft.recyclers.maxCapacityPerThread: disabled");
        } else {
            LOG.debug("-Djraft.recyclers.maxCapacityPerThread: {}", DEFAULT_MAX_CAPACITY_PER_THREAD);
        }
    }
    // 設置初始化容量信息
    INITIAL_CAPACITY = Math.min(DEFAULT_MAX_CAPACITY_PER_THREAD, 256);
}

 public static final Handle NOOP_HANDLE = new Handle() {};

Recyclers會在靜態代碼塊中做一些對象池容量初始化的工作,初始化了最大對象池容量和初始化容量信息。

從對象池中獲取對象

Recyclers#get

// 線程變量,保存每個線程的對象池信息,通過 ThreadLocal 的使用,避免了不同線程之間的競爭情況
private final ThreadLocal<Stack<T>> threadLocal = new ThreadLocal<Stack<T>>() {

    @Override
    protected Stack<T> initialValue() {
        return new Stack<>(Recyclers.this, Thread.currentThread(), maxCapacityPerThread);
    }
};

public final T get() {
    if (maxCapacityPerThread == 0) {
        return newObject(NOOP_HANDLE);
    }
    //從threadLocal中獲取一個棧對象
    Stack<T> stack = threadLocal.get();
    //拿出棧頂元素
    DefaultHandle handle = stack.pop();
    //如果棧裏面沒有元素,那麼就實例化一個
    if (handle == null) {
        handle = stack.newHandle();
        handle.value = newObject(handle);
    }
    return (T) handle.value;
}

Get方法會從threadLocal中去獲取數據,如果獲取不到,那麼會初始化一個Stack,並傳入當前Recyclers實例,當前線程,與最大容量。然後從stack中pop拿出棧頂元素,如果獲取的元素為空,那麼直接調用newHandle新建一個DefaultHandle實例,並調用Recyclers實現類的newObject獲取實現類的實例。也就是說DefaultHandle是用來封裝真正的對象的實例。

從stack中申請一個對象

Stack(Recyclers<T> parent, Thread thread, int maxCapacity) {
    this.parent = parent;
    this.thread = thread;
    this.maxCapacity = maxCapacity;
    elements = new DefaultHandle[Math.min(INITIAL_CAPACITY, maxCapacity)];
}

DefaultHandle pop() {
    int size = this.size;
    if (size == 0) {
        if (!scavenge()) {
            return null;
        }
        size = this.size;
    }
    //size表示整個stack中的大小
    size--;
    //獲取最後一個元素
    DefaultHandle ret = elements[size];
    if (ret.lastRecycledId != ret.recycleId) {
        throw new IllegalStateException("recycled multiple times");
    }
    // 清空回收信息,以便判斷是否重複回收
    ret.recycleId = 0;
    ret.lastRecycledId = 0;
    this.size = size;
    return ret;
}

獲取對象的邏輯也比較簡單,當 Stack 中的 DefaultHandle[] 的 size 為 0 時,需要從其他線程的 WeakOrderQueue 中轉移數據到 Stack 中的 DefaultHandle[],即 scavenge方法,該方法下面再聊。當 Stack 中的 DefaultHandle[] 中最終有了數據時,直接獲取最後一個元素

對象池回收對象

我們再來看看RecyclableByteBufferList是怎麼回收對象的。
RecyclableByteBufferList#recycle

public boolean recycle() {
    clear();
    this.capacity = 0;
    return recyclers.recycle(this, handle);
}

RecyclableByteBufferList回收對象的時候首先會調用clear方法清空屬性,然後調用recyclers的recycle方法進行對象回收。

Recyclers#recycle

public final boolean recycle(T o, Handle handle) {
    if (handle == NOOP_HANDLE) {
        return false;
    }

    DefaultHandle h = (DefaultHandle) handle;
    //stack在實例化的時候會在構造器中傳入一個Recyclers作為parent
    //所以這裡是校驗一下,如果不是當前線程的, 直接不回收了
    if (h.stack.parent != this) {
        return false;
    }
    if (o != h.value) {
        throw new IllegalArgumentException("o does not belong to handle");
    }
    h.recycle();
    return true;
}

這裡會接着調用DefaultHandle的recycle方法進行回收

DefaultHandle

static final class DefaultHandle implements Handle {
    //在WeakOrderQueue的add方法中會設置成ID
    //在push方法中設置成為OWN_THREAD_ID
    //在pop方法中設置為0
    private int lastRecycledId;
    //只有在push方法中才會設置OWN_THREAD_ID
    //在pop方法中設置為0
    private int recycleId;
    //當前的DefaultHandle對象所屬的Stack
    private Stack<?> stack;
    private Object value;

    DefaultHandle(Stack<?> stack) {
        this.stack = stack;
    }

    public void recycle() {
        Thread thread = Thread.currentThread();
        //如果當前線程正好等於stack所對應的線程,那麼直接push進去
        if (thread == stack.thread) {
            stack.push(this);
            return;
        }
        // we don't want to have a ref to the queue as the value in our weak map
        // so we null it out; to ensure there are no races with restoring it later
        // we impose a memory ordering here (no-op on x86)
        // 如果不是當前線程,則需要延遲回收,獲取當前線程存儲的延遲回收WeakHashMap
        Map<Stack<?>, WeakOrderQueue> delayedRecycled = Recyclers.delayedRecycled.get();
        // 當前 handler 所在的 stack 是否已經在延遲回收的任務隊列中
        // 並且 WeakOrderQueue是一個多線程間可以共享的Queue
        WeakOrderQueue queue = delayedRecycled.get(stack);
        if (queue == null) {
            delayedRecycled.put(stack, queue = new WeakOrderQueue(stack, thread));
        }
        queue.add(this);
    }
}

DefaultHandle在實例化的時候會傳入一個stack實例,代表當前實例是屬於這個stack的。
所以在調用recycle方法的時候,會判斷一下,當前的線程是不是stack所屬的線程,如果是那麼直接push到stack裏面就好了,不是則調用延遲隊列delayedRecycled;
從delayedRecycled隊列中獲取Map<Stack<?>, WeakOrderQueue> delayedRecycled ,根據stack作為key來獲取WeakOrderQueue,然後將當前的DefaultHandle實例放入到WeakOrderQueue中。

同線程回收對象

Stack#push

void push(DefaultHandle item) {
    // (item.recycleId | item.lastRecycleId) != 0 等價於 item.recycleId!=0 && item.lastRecycleId!=0
    // 當item開始創建時item.recycleId==0 && item.lastRecycleId==0
    // 當item被recycle時,item.recycleId==x,item.lastRecycleId==y 進行賦值
    // 當item被pop之後, item.recycleId = item.lastRecycleId = 0
    // 所以當item.recycleId 和 item.lastRecycleId 任何一個不為0,則表示回收過
    if ((item.recycleId | item.lastRecycledId) != 0) {
        throw new IllegalStateException("recycled already");
    }
    // 設置對象的回收id為線程id信息,標記自己的被回收的線程信息
    item.recycleId = item.lastRecycledId = OWN_THREAD_ID;

    int size = this.size;
    if (size >= maxCapacity) {
        // Hit the maximum capacity - drop the possibly youngest object.
        return;
    }
    // stack中的elements擴容兩倍,複製元素,將新數組賦值給stack.elements
    if (size == elements.length) {
        elements = Arrays.copyOf(elements, Math.min(size << 1, maxCapacity));
    }

    elements[size] = item;
    this.size = size + 1;
}

同線程回收對象 DefaultHandle#recycle 步驟:

  1. stack 先檢測當前的線程是否是創建 stack 的線程,如果不是,則走異線程回收邏輯;如果是,則首先判斷是否重複回收,然後判斷 stack 的 DefaultHandle[] 中的元素個數是否已經超過最大容量(4k),如果是,直接返回;
  2. 判斷當前的 DefaultHandle[] 是否還有空位,如果沒有,以 maxCapacity 為最大邊界擴容 2 倍,之後拷貝舊數組的元素到新數組,然後將當前的 DefaultHandle 對象放置到 DefaultHandle[] 中
  3. 最後重置 stack.size 屬性

異線程回收對象

WeakOrderQueue

static final class Stack<T> {
    //使用volatile可以立即讀取到該queue
      private volatile WeakOrderQueue head;
}
WeakOrderQueue(Stack<?> stack, Thread thread) {
    head = tail = new Link();
    //使用的是WeakReference ,作用是在poll的時候,如果owner不存在了
    // 則需要將該線程所包含的WeakOrderQueue的元素釋放,然後從鏈表中刪除該Queue。
    owner = new WeakReference<>(thread);
    //假設線程B和線程C同時回收線程A的對象時,有可能會同時創建一個WeakOrderQueue,就坑同時設置head,所以這裏需要加鎖
    synchronized (stackLock(stack)) {
        next = stack.head;
        stack.head = this;
    }
}

創建WeakOrderQueue對象的時候會初始化一個WeakReference的owner,作用是在poll的時候,如果owner不存在了, 則需要將該線程所包含的WeakOrderQueue的元素釋放,然後從鏈表中刪除該Queue。

然後給stack加鎖,假設線程B和線程C同時回收線程A的對象時,有可能會同時創建一個WeakOrderQueue,就坑同時設置head,所以這裏需要加鎖。

以head==null的時候為例
加鎖:
線程B先執行,則head = 線程B的queue;之後線程C執行,此時將當前的head也就是線程B的queue作為線程C的queue的next,組成鏈表,之後設置head為線程C的queue
不加鎖:
線程B先執行 next = stack.head此時線程B的queue.next=null->線程C執行next = stack.head;線程C的queue.next=null-> 線程B執行stack.head = this;設置head為線程B的queue -> 線程C執行stack.head = this;設置head為線程C的queue,此時線程B和線程C的queue沒有連起來。

WeakOrderQueue#add

void add(DefaultHandle handle) {
    // 設置handler的最近一次回收的id信息,標記此時暫存的handler是被誰回收的
    handle.lastRecycledId = id;

    Link tail = this.tail;
    int writeIndex;
    // 判斷一個Link對象是否已經滿了:
    // 如果沒滿,直接添加;
    // 如果已經滿了,創建一個新的Link對象,之後重組Link鏈表,然後添加元素的末尾的Link(除了這個Link,前邊的Link全部已經滿了)
    if ((writeIndex = tail.get()) == LINK_CAPACITY) {
        this.tail = tail = tail.next = new Link();
        writeIndex = tail.get();
    }
    tail.elements[writeIndex] = handle;
    // 如果使用者在將DefaultHandle對象壓入隊列后,將Stack設置為null
    // 但是此處的DefaultHandle是持有stack的強引用的,則Stack對象無法回收;
    //而且由於此處DefaultHandle是持有stack的強引用,WeakHashMap中對應stack的WeakOrderQueue也無法被回收掉了,導致內存泄漏
    handle.stack = null;
    // we lazy set to ensure that setting stack to null appears before we unnull it in the owning thread;
    // this also means we guarantee visibility of an element in the queue if we see the index updated
    // tail本身繼承於AtomicInteger,所以此處直接對tail進行+1操作
    tail.lazySet(writeIndex + 1);
}

Stack異線程push對象流程

  1. 首先獲取當前線程的 Map<Stack<?>, WeakOrderQueue> 對象,如果沒有就創建一個空 map;
  2. 然後從 map 對象中獲取 key 為當前的 Stack 對象的 WeakOrderQueue;
  3. 如果獲取的WeakOrderQueue對象為null,那麼創建一個WeakOrderQueue對象,並將對象放入到map中,最後調用WeakOrderQueue#add添加對象

WeakOrderQueue 的創建流程:

  1. 創建一個Link對象,將head和tail的引用都設置為此對象
  2. 創建一個WeakReference指向owner對象,設置當前的 WeakOrderQueue 所屬的線程為當前線程。
  3. 先將原本的 stack.head 賦值給剛剛創建的 WeakOrderQueue 的 next 節點,之後將剛剛創建的 WeakOrderQueue 設置為 stack.head(這一步非常重要:假設線程 A 創建對象,此處是線程 C 回收對象,則線程 C 先獲取其 Map<Stack<?>, WeakOrderQueue> 對象中 key=線程A的stack對象的 WeakOrderQueue,然後將該 Queue 賦值給線程 A 的 stack.head,後續的 pop 操作打基礎),形成 WeakOrderQueue 的鏈表結構。

WeakOrderQueue#add添加對象流程

  1. 首先設置 item.lastRecycledId = 當前 WeakOrderQueue 的 id
  2. 然後看當前的 WeakOrderQueue 中的 Link 節點鏈表中的尾部 Link 節點的 DefaultHandle[] 中的元素個數是否已經達到 LINK_CAPACITY(16)
  3. 如果不是,則直接將當前的 DefaultHandle 元素插入尾部 Link 節點的 DefaultHandle[] 中,之後置空當前的 DefaultHandle 元素的 stack 屬性,最後記錄當前的 DefaultHandle[] 中的元素數量;
  4. 如果是,則新建一個 Link,並且放在當前的 Link 鏈表中的尾部節點處,與之前的 tail 節點連起來(鏈表),之後進行第三步的操作。

從異線程獲取對象

我再把pop方法搬下來一次:

DefaultHandle pop() {
    int size = this.size;
    // size=0 則說明本線程的Stack沒有可用的對象,先從其它線程中獲取。
    if (size == 0) {
        // 當 Stack<T> 此時的容量為 0 時,去 WeakOrder 中轉移部分對象到 Stack 中
        if (!scavenge()) {
            return null;
        }
        //由於在transfer(Stack<?> dst)的過程中,可能會將其他線程的WeakOrderQueue中的DefaultHandle對象傳遞到當前的Stack,
        //所以size發生了變化,需要重新賦值
        size = this.size;
    }
    //size表示整個stack中的大小
    size--;
    //獲取最後一個元素
    DefaultHandle ret = elements[size];
    if (ret.lastRecycledId != ret.recycleId) {
        throw new IllegalStateException("recycled multiple times");
    }
    // 清空回收信息,以便判斷是否重複回收
    ret.recycleId = 0;
    ret.lastRecycledId = 0;
    this.size = size;
    return ret;
}
  1. 首先獲取當前的 Stack 中的 DefaultHandle 對象中的元素個數。
  2. 如果為 0,則從其他線程的與當前的 Stack 對象關聯的 WeakOrderQueue 中獲取元素,並轉移到 Stack 的 DefaultHandle[] 中(每一次 pop 只轉移一個有元素的 Link),如果轉移不成功,說明沒有元素可用,直接返回 null;
  3. 如果轉移成功,則重置 size屬性 = 轉移后的 Stack 的 DefaultHandle[] 的 size,之後直接獲取 Stack 對象中 DefaultHandle[] 的最後一位元素,之後做防護性檢測,最後重置當前的 stack 對象的 size 屬性以及獲取到的 DefaultHandle 對象的 recycledId 和 lastRecycledId 回收標記,返回 DefaultHandle 對象。

scavenge轉移

Stack#scavenge

boolean scavenge() {
    // continue an existing scavenge, if any
    // 掃描判斷是否存在可轉移的 Handler
    if (scavengeSome()) {
        return true;
    }
    
    // reset our scavenge cursor
    prev = null;
    cursor = head;
    return false;
}

調用scavengeSome掃描判斷是否存在可轉移的 Handler,如果沒有,那麼就返回false,表示沒有可用對象

Stack#scavengeSome

boolean scavengeSome() {
    WeakOrderQueue cursor = this.cursor;
    if (cursor == null) {
        cursor = head;
        // 如果head==null,表示當前的Stack對象沒有WeakOrderQueue,直接返回
        if (cursor == null) {
            return false;
        }
    }

    boolean success = false;
    WeakOrderQueue prev = this.prev;
    do {
        // 從當前的WeakOrderQueue節點進行 handler 的轉移
        if (cursor.transfer(this)) {
            success = true;
            break;
        }
        // 遍歷下一個WeakOrderQueue
        WeakOrderQueue next = cursor.next;
        // 如果 WeakOrderQueue 的實際持有線程因GC回收了
        if (cursor.owner.get() == null) {
            // If the thread associated with the queue is gone, unlink it, after
            // performing a volatile read to confirm there is no data left to collect.
            // We never unlink the first queue, as we don't want to synchronize on updating the head.
            // 如果當前的WeakOrderQueue的線程已經不可達了
            //如果該WeakOrderQueue中有數據,則將其中的數據全部轉移到當前Stack中
            if (cursor.hasFinalData()) {
                for (;;) {
                    if (cursor.transfer(this)) {
                        success = true;
                    } else {
                        break;
                    }
                }
            }
            //將當前的WeakOrderQueue的前一個節點prev指向當前的WeakOrderQueue的下一個節點,
            // 即將當前的WeakOrderQueue從Queue鏈表中移除。方便後續GC
            if (prev != null) {
                prev.next = next;
            }
        } else {
            prev = cursor;
        }

        cursor = next;

    } while (cursor != null && !success);

    this.prev = prev;
    this.cursor = cursor;
    return success;
}
  1. 首先設置當前操作的 WeakOrderQueue cursor,如果為 null,則賦值為 stack.head 節點,如果 stack.head 為 null,則表明外部線程沒有回收過當前線程創建的 對象,外部線程在回收對象的時候會創建一個WeakOrderQueue,並將stack.head 指向新創建的WeakOrderQueue對象,則直接返回 false;如果不為 null,則繼續向下執行;
  2. 首先對當前的 cursor 進行元素的轉移,如果轉移成功,則跳出循環,設置 prev 和 cursor 屬性;
  3. 如果轉移不成功,獲取下一個線程 Y 中的與當前線程的 Stack 對象關聯的 WeakOrderQueue,如果該 queue 所屬的線程 Y 還可達,則直接設置 cursor 為該 queue,進行下一輪循環;如果該 queue 所屬的線程 Y 不可達了,則判斷其內是否還有元素,如果有,全部轉移到當前線程的 Stack 中,之後將線程 Y 的 queue 從查詢 queue 鏈表中移除。

transfer轉移

    boolean transfer(Stack<?> dst) {
        //尋找第一個Link
        Link head = this.head;
        // head == null,沒有存儲數據的節點,直接返回
        if (head == null) {
            return false;
        }
        // 讀指針的位置已經到達了每個 Node 的存儲容量,如果還有下一個節點,進行節點轉移
        if (head.readIndex == LINK_CAPACITY) {
            //判斷當前的Link節點的下一個節點是否為null,如果為null,說明已經達到了Link鏈表尾部,直接返回,
            if (head.next == null) {
                return false;
            }
            // 否則,將當前的Link節點的下一個Link節點賦值給head和this.head.link,進而對下一個Link節點進行操作
            this.head = head = head.next;
        }
        // 獲取Link節點的readIndex,即當前的Link節點的第一個有效元素的位置
        final int srcStart = head.readIndex;
        // 獲取Link節點的writeIndex,即當前的Link節點的最後一個有效元素的位置
        int srcEnd = head.get();
        // 本次可轉移的對象數量(寫指針減去讀指針)
        final int srcSize = srcEnd - srcStart;
        if (srcSize == 0) {
            return false;
        }
        // 獲取轉移元素的目的地Stack中當前的元素個數
        final int dstSize = dst.size;
        // 計算期盼的容量
        final int expectedCapacity = dstSize + srcSize;
        // 期望的容量大小與實際 Stack 所能承載的容量大小進行比對,取最小值
        if (expectedCapacity > dst.elements.length) {
            final int actualCapacity = dst.increaseCapacity(expectedCapacity);
            srcEnd = Math.min(srcStart + actualCapacity - dstSize, srcEnd);
        }

        if (srcStart != srcEnd) {
            // 獲取Link節點的DefaultHandle[]
            final DefaultHandle[] srcElems = head.elements;
            // 獲取目的地Stack的DefaultHandle[]
            final DefaultHandle[] dstElems = dst.elements;
            // dst數組的大小,會隨着元素的遷入而增加,如果最後發現沒有增加,那麼表示沒有遷移成功任何一個元素
            int newDstSize = dstSize;
            //// 進行對象轉移
            for (int i = srcStart; i < srcEnd; i++) {
                DefaultHandle element = srcElems[i];
                // 表明自己還沒有被任何一個 Stack 所回收
                if (element.recycleId == 0) {
                    element.recycleId = element.lastRecycledId;
                //  避免對象重複回收
                } else if (element.recycleId != element.lastRecycledId) {
                    throw new IllegalStateException("recycled already");
                }
                // 將可轉移成功的DefaultHandle元素的stack屬性設置為目的地Stack
                element.stack = dst;
                // 將DefaultHandle元素轉移到目的地Stack的DefaultHandle[newDstSize ++]中
                dstElems[newDstSize++] = element;
                // 設置為null,清楚暫存的handler信息,同時幫助 GC
                srcElems[i] = null;
            }
            // 將新的newDstSize賦值給目的地Stack的size
            dst.size = newDstSize;

            if (srcEnd == LINK_CAPACITY && head.next != null) {
                // 將Head指向下一個Link,也就是將當前的Link給回收掉了
                // 假設之前為Head -> Link1 -> Link2,回收之後為Head -> Link2
                this.head = head.next;
            }
            // 設置讀指針位置
            head.readIndex = srcEnd;
            return true;
        } else {
            // The destination stack is full already.
            return false;
        }
    }
}
  1. 尋找 cursor 節點中的第一個 Link如果為 null,則表示沒有數據,直接返回;
  2. 如果第一個 Link 節點的 readIndex 索引已經到達該 Link 對象的 DefaultHandle[] 的尾部,則判斷當前的 Link 節點的下一個節點是否為 null,如果為 null,說明已經達到了 Link 鏈表尾部,直接返回,否則,將當前的 Link 節點的下一個 Link 節點賦值給 head ,進而對下一個 Link 節點進行操作;
  3. 獲取 Link 節點的 readIndex,即當前的 Link 節點的第一個有效元素的位置
  4. 獲取 Link 節點的 writeIndex,即當前的 Link 節點的最後一個有效元素的位置
  5. 計算 Link 節點中可以被轉移的元素個數,如果為 0,表示沒有可轉移的元素,直接返回
  6. 獲取轉移元素的目標 Stack 中當前的元素個數(dstSize)並計算期盼的容量 expectedCapacity,如果 expectedCapacity 大於目標Stack 的長度(dst.elements.length),則先對目的地 Stack 進行擴容,計算 Link 中最終的可轉移的最後一個元素的下標;
  7. 如果發現目的地 Stack 已經滿了( srcStart != srcEnd為false),則直接返回 false
  8. 獲取 Link 節點的 DefaultHandle[] (srcElems)和目標 Stack 的 DefaultHandle[](dstElems)
  9. 根據可轉移的起始位置和結束位置對 Link 節點的 DefaultHandle[] 進行循環操作
  10. 將可轉移成功的 DefaultHandle 元素的stack屬性設置為目標 Stack(element.stack = dst),將 DefaultHandle 元素轉移到目的地 Stack 的 DefaultHandle[newDstSize++] 中,最後置空 Link 節點的 DefaultHandle[i]
  11. 如果當前被遍歷的 Link 節點的 DefaultHandle[] 已經被掏空了(srcEnd == LINK_CAPACITY),並且該 Link 節點還有下一個 Link 節點
  12. 重置當前 Link 的 readIndex

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

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

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

程序員一般通過什麼途徑接私活?

二哥,你好,我想知道一般程序猿都如何接私活,我也想接,能告訴我一些方法嗎?

上面是一個讀者“煩不煩”問我的一個問題。其實不止是“煩不煩”,還有很多讀者問過我類似這樣的問題。

我接的私活不算多,掙到的錢也沒有多少,加起來不到 20W。說實話,這個數目說出來我是有點心虛的,畢竟太少了,大家輕噴。但我想,恰好配得上“一般程序員”這個稱號啊。畢竟蒼蠅再小也是肉,我也算是有經驗的人了。

唾棄接私活、做外包的程序員有很多很多,曾經高傲的我也嫌棄過。但沒辦法,為了掙點零花錢,我垂下了高昂的頭。記得有位朋友曾說過,當年沈從文為了生計,寫了很多稱不上他自己喜歡的文字給報刊。

聽朋友這麼一說,我也不再覺得“接私活”是多麼一件值得羞愧的事情了。人首先要活着,才有體力講情懷啊。好了,言歸正傳,我來替“煩不煩”同學介紹幾個容易上手的操作。

01、朋友介紹

大體上,天底下做生意都只有一條捷徑:從熟人下手。

“哥們,聽說你有個朋友是做程序員的,我這有台電腦不知道為啥黑屏了,能問問他知道什麼原因嗎?要是能修好,保准請你吃頓大餐。”

“老弟啊,我有一個朋友說最近流行炒鞋,我想你不是程序員嘛,找你最合適了,要不我把他推薦給你,談成的話給我發個紅包就行了。”

我的第一個私活,就是之前在蘇州的一個同事介紹的。不過最後黃了。我搞了兩周時間(技術框架用的 JEPF),同事說甲方換方案了,沒把我氣壞。

同事礙於情面,說有機會請我吃頓飯。這一等就是 3 年,3 年過去了,飯還是沒有吃到。主要是因為我這位朋友在蘇州,我在洛陽,吃飯是沒辦法遠程完成啊。

第二個私活,是之前在蘇州的一個領導介紹的。由同事升級為領導,多少靠譜了點。這次做的是蘇州相城區的一個电子商務網站。前後做了三個多月,最後拿到手的錢也就不到一萬塊錢。

現在感覺自己當時是在出售廉價勞動力,何止是廉價,簡直是公益事業。不過,第一次接私活,拿到錢買了個華為的 MateBook,真香。

第二個私活做完后,領導可能覺得虧待了我,良心難安,就介紹了第三個私活給我。這次蠻輕鬆的,一個月搞定,還不累,兩萬塊到手。

既然是私活,當然都是利用業餘時間做的。這個投入的成本和實際得到的回報是一定要考慮的

我第一個私活打了水漂,辛苦了兩周,零回報。不過,這也是接私活常有的事,需要用平常心來對待。

第二個私活說實話非常辛苦,有幾次熬到半夜兩三點,當時覺得太不划算了。但當初自己接了,就只能忍着拼到底。畢竟咱是敬業愛崗的好同志。

第三個私活就相對輕鬆多了,單位時間內的收益非常高,算下來一個小時有 500 的工時費吧,就彷彿是對前兩個的補償。

總結一下,朋友介紹的項目相對來說還是比較靠譜的,前提條件是要有一定的“人情世故”原始成本積累。如果我當時在蘇州表現得不夠優異,和同事、領導的關係相處的不夠融洽,那自然他們也不會時隔多年後再找到我。

記住一點,做事的同時要好好的做人。當你既有能力,又值得信任的時候,私活就會找上門來

02、個人品牌

既然是朋友,自然就不會有很多。也就意味着,單純依賴朋友介紹的私活來源是有限度的。那如果想接更多的私活,該怎麼辦呢?

這就需要個人品牌了。

我平常不是喜歡寫作嘛,分享了很多技術文章在各大平台上,瀏覽量還算不錯。博客園上的排名和瀏覽量都能拿得出手。

博客地址:

當你做了一件事,並且一直在堅持,況且還做出了一定的成績,自然就會有生意主動找上門來——花香蜂自來嘛

寫博客的好處有很多,比如說吸引一批忠實的讀者,他們追隨你的文字,喜歡你的風格;再比如說勾引一些出版社,他們欣賞你的文字,願意合作互利共贏。

最後,還會有一些做私活的甲方。以前,我總覺得這是不可能發生的事情,他們是怎麼找到我的?很不可思議,但互聯網就是這麼神奇,你覺得不可能,它卻悄悄地發生着。

第一個通過這個途徑找到我的甲方,姓康。康哥找到我后,一上來就對我一頓吹捧(甭管是真是假)。信任建立起來后,他就說自己在醞釀一個很牛逼的項目,看我有沒有意向一起做。

然後呢,承諾項目成功后,再給我一定數額的獎勵金,並且寫到了合同里。吃完他這個大餅,我很飽,忍不住打了好幾個嗝。

再然後,我們就開始整理需求,然後我出報價,他再砍價;他再提需求,我再加價。最後呢,項目總款談到 7.5 萬,兩個多月的工期。合同的細節也敲定的差不多了。

結果,黃了。和我合夥的一個開發人員小何覺得甲方新提的需求需要再追加 600 塊,甲方覺得這點錢擱不住再追加了。總之呢,7.5W 的項目就因為這個細節黃了,很遺憾。

第二個通過這個途徑找到我的甲方,叫鵬哥。開發一個網站,總價一萬多,吃了上次的虧后,我自己就不想參与了,就找了一個讀者(小李)做。

結果這個項目爛尾了。小李交付的產物我自己都覺得不好意思,bug 非常多。在我看來,既然項目的訂金已經收了,作為開發人員,至少應該交付一個說得過去的產物——負責任吧。

很遺憾,個人品牌招攬來的前兩個私活最後都搞砸了。這裡有必要總結一下:作為程序員,既然打定主意要接私活,那麼接到的時候一定要珍惜。如果一開始覺得價錢低,就趁早拒絕,免得因為需求變動等等原因砸了招牌

當然了,通過這個途徑也做成了四單,每單的價格差不多兩萬。這裏就不再詳談了。

個人品牌的確可以引流來更多的私活,但與此同時,也會浪費很多時間。

像這種泛泛之談的意向客戶有很多。話說,我啥時候變成“社會王”了,我特么是正兒八經的“王老師”好不好?

03、外包平台

外包平台有很多,我就不再一一列舉了。只說幾個我認為還不錯的平台,也不打算細說,免得有些讀者“誇我”良苦用心地在打廣告。

04、一點忠告

在我寫這篇文章的時候,突然收到朋友的一條信息,說她們公司剛剛辭退了一位員工,還通報批評了,就因為接私活被舉報了——她們公司一般不辭退員工,這下子相當於鐵飯碗丟了。

所以說呢,接私活是有風險的。並且在我看來,如果主業沒有遇到瓶頸,強烈不建議接私活。就好比一個小孩子走路還不會,就要求他要跑起來。

時間對於一個程序員來說很寶貴,尤其是一個正在成長中的程序員。

如果你確實急用錢,價格又合適,那就去做。如果不怎麼缺錢,我再強調一次,別去接私活。私活的錢不好掙是一個方面,更重要的是如果你把做私活的時間花在提升自己上,產生的價值就要大得多。等你提升了自己,提升了固定薪水,遠比拿的這點私活的錢划算。千萬不要“撿了芝麻丟了西瓜”。

如果你像我,主業上遇到了瓶頸,平時的時間比較充分,想有一些額外的收入,同時為了保持技術的熟練度,這種情況下,是可以考慮接一些私活的。對於那種投入時間巨大,回報很可憐的項目,千萬不要接!

另外呢,如果甲方只提供幾個簡單的想法,甚至幾張圖片,更或者發一個參照的效果網站,就可以直接忽視了,這類通通不靠譜!

最後呢,還要說一句,如果訂金都收了,自己就算是覺得吃了虧,也應該有點職業素質,把像樣的產品交付,千萬別應付。

謝謝大家的閱讀,原創不易,喜歡就隨手點個贊,這將是我最強的寫作動力。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

你真的會用JavaScript中的sort方法嗎

  在平時的業務開發中,數組(Array) 是我們經常用到的數據類型,那麼對數組的排序也很常見,除去使用循環遍曆數組的方法來排列數據,使用JS數組中原生的方法 sort 來排列(沒錯,比較崇尚JS原生的力量)。

1、舉個栗子

  數組中能夠直接用來排序的方法有:reverse() 和 sort(),由於 reverse()方法不夠靈活,才有了sort()方法。在默認情況下,sort()方法按升序排列數組。

var arr=[1,3,5,9,4];
console.log(arr.sort());
// 輸出: [1, 3, 4, 5, 9]

這時發現數據按照從小到大排列,沒問題;於是再把數組改成:var arr=[101,1,3,5,9,4,11];,再調用sort()方法打印排序結果。

var arr=[101,1,3,5,9,4,11];
console.log(arr.sort());
// 輸出: [1, 101, 11, 3, 4, 5, 9]

這個時候發現數組101,11都排在3前面,是因為 sort() 方法會調用數組的toString()轉型方法,然後比較得到的字符串,確定如何排序,即使數組中的每一項都是數值,sort()方法比較的也是字符串。

那麼字符串又是怎麼排序的呢,是根據字符串的unicode編碼從小到大排序的。下面我們嘗試打印出數組每一項的unicode編碼看一下。

...
// 轉碼方法
function getUnicode (charCode) {
    return charCode.charCodeAt(0).toString(16);
}
// 打印轉碼
arr.forEach((n)=>{
  console.log(getUnicode(String(n)))
});

// 輸出: 31 31 31 33 34 35 39

驚奇地發現,1,101,11的字符串unicode編碼都是31

2、傳入比較函數以指定順序

  以上發現sort()方法不是按照我們想要的順序排序的,那麼,怎麼解決呢,sort()方法可以接收一個比較函數作為參數,以便指定哪個值位於哪個值前面

比較函數(compare)接收兩個參數,如果第一個參數位於第二個之前則返回一個負數,如果兩個參數相等則返回0,如果第一個參數位於第二個之後則返回一個整數。

function compare(value1,value2){
  if (value1 < value2){
    return -1;
  } else if (value1 > value2){
    return 1;
  } else{
    return 0;
  }
}

我們把比較函數傳遞給sort()方法,在對arr數組進行排列,打印結果如下:

var arr=[101,1,3,5,9,4,11];
console.log(arr.sort(compare));
// 輸出: [1, 3, 4, 5, 9, 11, 101];

可以發現排序從小到大沒有什麼問題。

3、對象數組的排序

  sort() 方法通過傳入一個比較函數來排序数字數組,但是在開發中,我們會對一個對象數組的某個屬性進行排序,例如id,年齡等等,那麼怎麼解決呢?

要解決這個問題:我們可以定義一個函數,讓它接收一個屬性名,然後根據這個屬性名來創建一個比較函數並作為返回值返回來(JS中函數可以作為值來使用,不僅可以像傳遞參數一樣把一個函數傳遞給另一個函數,也可以將一個函數作為另一個函數的結果返回,函數作為JS中的第一等公民不是沒有原因的,確實很靈活。),代碼如下。

function compareFunc(prop){
  return function (obj1,obj2){
    var value1=obj1[prop];
    var value2=obj2[prop];
    if (value1 < value2){
        return -1;
    } else if (value1 > value2){
        return 1;
    } else{
        return 0;
    }
  }
}

定義一個數組users,調用sort()方法傳入compareFunc(prop)打印輸出結果:

var users=[
    {name:'tom',age:18},
    {name:'lucy',age:24},
    {name:'jhon',age:17},
];
console.log(users.sort(compareFunc('age')));
// 輸出結果
[{name: "jhon", age: 17},
{name: "tom", age: 18},
{name: "lucy", age: 24}]

在默認情況下,調用sort()方法不傳入比較函數時,sort()方法會調用每個對象的toString()方法來確定他們的次序,當我們調用compareFunc(‘age’)方法創建一個比較函數,排序是按照對象的age屬性排序的。

4、XML節點的排序

  儘管現在很多後台返回數據就是JSON格式的,很輕量又方便解析。但是之前有個項目因為後台返回的都是XML字符串,前端拿到數據后還得進行序列化,有些需要排序,之前的排序都是把XML轉換成數組對象進行排序的,這樣做沒有什麼問題,只不過感覺代碼寫的很冗餘麻煩。後來就突發奇想,xml獲取得到也是類數組對象,把類數組對象轉換成數組不就可以直接排序了么。

// 1.模擬後端返回的XML字符串
var str=`
<root>
  <user>
    <name>tom</name>
    <age>18</age>
  </user>
  <user>
    <name>lucy</name>
    <age>24</age>
  </user>
  <user>
    <name>jhon</name>
    <age>17</age>
  </user>
<root>
`   
// 2.定義比較函數
function compareFunction(prop){
  return function (a, b) {
      var value1= a.getElementsByTagName(prop)[0].textContent;
      var value2= b.getElementsByTagName(prop)[0].textContent;
      if (value1 < value2){
        return -1;
      } else if (value1 > value2){
        return 1;
      } else{
        return 0;
    }
  }
}
// 3.xml字符串轉換成xml對象
var domParser = new DOMParser();
var xmlDoc = domParser.parseFromString(str, 'text/xml');
var userElements=xmlDoc.getElementsByTagName('user'));
// 4.userElements類數組對象轉換成數組再排序
var userElements=Array.prototype.slice.call(xmlDoc.getElementsByTagName('user'));
var _userElements=userElements.sort(compareFunction('age'));
// 5.打印排序后的結果
_userElements.forEach((user)=>{
  console.log(user.innerHTML);
});

打印排序后的結果

可以發現,XML節點已經按照age從小到大排序了。

5、總結

  JS數組的sort方法因為有了傳入比較函數使得排序靈活了許多,還有根據時間,漢字拼音首字母排序等等,我們只要牢記通過傳入比較函數明確比較兩個對象屬性值,通過比較屬性值來決定對象的排序順序即可。自己也是在工作中遇到問題從而發現解決問題的新思路,以上就簡單總結這麼多了,如有不足,多多指正。

參考資料:
《JavaScript高級教程》

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

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

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

【NServiceBus】什麼是Saga,Saga能做什麼

前言

          Saga單詞翻譯過來是指尤指古代挪威或冰島講述冒險經歷和英雄業績的長篇故事,對,這裏強調長篇故事。許多系統都存在長時間運行的業務流程,NServiceBus使用基於事件驅動的體繫結構將容錯性和可伸縮性融入這些業務處理過程中。
          當然一個單一接口調用則算不上一個長時間運行的業務場景,那麼如果在給定的用例中有兩個或多個調用,則應該考慮數據一致性的問題,這裡有可能第一個接口調用成功,第二次調用則可能失敗或者超時,Saga的設計以簡單而健壯的方式處理這樣的業務用例。

認識Saga

         先來通過一段代碼簡單認識一下Saga,在NServiceBus里,使用Saga的話則需要實現抽象類Saga ,SqlSaga ,這裏的T的是Saga業務實體,封裝數據,用來在長時間運行過程中封裝業務數據。

public class Saga:Saga<State>,
        IAmStartedByMessages<StartOrder>,
        IHandleMessages<CompleteOrder>
    {
        protected override void ConfigureHowToFindSaga(SagaPropertyMapper<State> mapper)
        {
            mapper.ConfigureMapping<StartOrder>(message=>message.OrderId).ToSaga(saga=>saga.OrderId);
            mapper.ConfigureMapping<CompleteOrder>(message=>message.OrderId).ToSaga(saga=>saga.OrderId);
        }

        public Task Handle(StartOrder message, IMessageHandlerContext context)
        {
            return Task.CompletedTask;
        }

        public Task Handle(CompleteOrder message, IMessageHandlerContext context)
        {
            MarkAsComplete();
            return Task.CompletedTask;
        }
    }

臨時狀態

     長時間運行則意味着有狀態,任何涉及多個網絡調用的進程都需要一個臨時狀態,這個臨時狀態可以存儲在內存中,序列化在磁盤中,也可以存儲在分佈式緩存中。在NServiceBus中我們定義實體,繼承抽象類ContainSagaData即可,默認情況下,所有公開訪問的屬性都會被持久化。

public class State:ContainSagaData
{
    public Guid OrderId { get; set; }
}

添加行為

      在NServiceBus里,處理消息的有兩種接口:IHandlerMessages 、IAmStartedByMessages 。

開啟一個Saga

       在前面的代碼片段里,我們看到已經實現了接口IAmStartedByMessages ,這個接口告訴NServiceBus,如果收到了StartOrder 消息,則創建一個Saga實例(Saga Instance),當然Saga長流程處理的實體至少有一個需要開啟Saga流程。

處理無序消息

       如果你的業務用例中確實存在無序消息的情況,則還需要業務流程正常輪轉,那麼則需要多個messaeg都要事先接口IAmStartedByMessages接口,也就是說多個message都可以創建Saga實例。

依賴可恢復性

      在處理無序消息和多個消息類型的時候,就存在消息丟失的可能,必須在你的Saga狀態完成以後,這個Saga實例又收到一條消息,但這時Saga狀態已經是完結狀態,這條消息則仍然需要處理,這裏則實現NServiceBus的IHandleSagaNotFound接口。

 public class SagaNotFoundHandler:IHandleSagaNotFound
 {
    public Task Handle(object message, IMessageProcessingContext context)
    {
        return context.Reply(new SagaNotFoundMessage());
    }
 }
  
 public class SagaNotFoundMessage
 {
        
 }

結束Saga

      當你的業務用例不再需要Saga實例時,則調用MarkComplete()來結束Saga實例。這個方法在前面的代碼片段中也可以看到,其實本質也就是設置Saga.Complete屬性,這是個bool值,你在業務用例中也可以用此值來判斷Saga流程是否結束。

namespace NServiceBus
{
    using System;
    using System.Threading.Tasks;
    using Extensibility;

    public abstract class Saga
    {
        /// <summary>
        /// The saga's typed data.
        /// </summary>
        public IContainSagaData Entity { get; set; }

        
        public bool Completed { get; private set; }

        internal protected abstract void ConfigureHowToFindSaga(IConfigureHowToFindSagaWithMessage sagaMessageFindingConfiguration);

       
        protected Task RequestTimeout<TTimeoutMessageType>(IMessageHandlerContext context, DateTime at) where TTimeoutMessageType : new()
        {
            return RequestTimeout(context, at, new TTimeoutMessageType());
        }

        
        protected Task RequestTimeout<TTimeoutMessageType>(IMessageHandlerContext context, DateTime at, TTimeoutMessageType timeoutMessage)
        {
            if (at.Kind == DateTimeKind.Unspecified)
            {
                throw new InvalidOperationException("Kind property of DateTime 'at' must be specified.");
            }

            VerifySagaCanHandleTimeout(timeoutMessage);

            var options = new SendOptions();

            options.DoNotDeliverBefore(at);
            options.RouteToThisEndpoint();

            SetTimeoutHeaders(options);

            return context.Send(timeoutMessage, options);
        }

        
        protected Task RequestTimeout<TTimeoutMessageType>(IMessageHandlerContext context, TimeSpan within) where TTimeoutMessageType : new()
        {
            return RequestTimeout(context, within, new TTimeoutMessageType());
        }

        
        protected Task RequestTimeout<TTimeoutMessageType>(IMessageHandlerContext context, TimeSpan within, TTimeoutMessageType timeoutMessage)
        {
            VerifySagaCanHandleTimeout(timeoutMessage);

            var sendOptions = new SendOptions();

            sendOptions.DelayDeliveryWith(within);
            sendOptions.RouteToThisEndpoint();

            SetTimeoutHeaders(sendOptions);

            return context.Send(timeoutMessage, sendOptions);
        }

        
        protected Task ReplyToOriginator(IMessageHandlerContext context, object message)
        {
            if (string.IsNullOrEmpty(Entity.Originator))
            {
                throw new Exception("Entity.Originator cannot be null. Perhaps the sender is a SendOnly endpoint.");
            }

            var options = new ReplyOptions();

            options.SetDestination(Entity.Originator);
            context.Extensions.Set(new AttachCorrelationIdBehavior.State { CustomCorrelationId = Entity.OriginalMessageId });

            
            options.Context.Set(new PopulateAutoCorrelationHeadersForRepliesBehavior.State
            {
                SagaTypeToUse = null,
                SagaIdToUse = null
            });

            return context.Reply(message, options);
        }

        //這個方法結束saga流程,標記Completed屬性
        protected void MarkAsComplete()
        {
            Completed = true;
        }

        void VerifySagaCanHandleTimeout<TTimeoutMessageType>(TTimeoutMessageType timeoutMessage)
        {
            var canHandleTimeoutMessage = this is IHandleTimeouts<TTimeoutMessageType>;
            if (!canHandleTimeoutMessage)
            {
                var message = $"The type '{GetType().Name}' cannot request timeouts for '{timeoutMessage}' because it does not implement 'IHandleTimeouts<{typeof(TTimeoutMessageType).FullName}>'";
                throw new Exception(message);
            }
        }

        void SetTimeoutHeaders(ExtendableOptions options)
        {
            options.SetHeader(Headers.SagaId, Entity.Id.ToString());
            options.SetHeader(Headers.IsSagaTimeoutMessage, bool.TrueString);
            options.SetHeader(Headers.SagaType, GetType().AssemblyQualifiedName);
        }
    }
}

    

Saga持久化

      本機開發環境我們使用LearningPersistence,但是投產的話則需要使用數據庫持久化,這裏我們基於MySQL,SQL持久化需要引入NServiceBus.Persistence.Sql。SQL Persistence會生成幾種關係型數據庫的sql scripts,然後會根據你的斷言配置選擇所需數據庫,比如SQL Server、MySQL、PostgreSQL、Oracle。
     持久化Saga自動創建所需表結構,你只需手動配置即可,配置后編譯成功後項目執行目錄下會生成sql腳本,文件夾名稱是NServiceBus.Persistence.Sql,下面會有Saga子目錄。


/* TableNameVariable */

set @tableNameQuoted = concat('`', @tablePrefix, 'Saga`');
set @tableNameNonQuoted = concat(@tablePrefix, 'Saga');


/* Initialize */

drop procedure if exists sqlpersistence_raiseerror;
create procedure sqlpersistence_raiseerror(message varchar(256))
begin
signal sqlstate
    'ERROR'
set
    message_text = message,
    mysql_errno = '45000';
end;

/* CreateTable */

set @createTable = concat('
    create table if not exists ', @tableNameQuoted, '(
        Id varchar(38) not null,
        Metadata json not null,
        Data json not null,
        PersistenceVersion varchar(23) not null,
        SagaTypeVersion varchar(23) not null,
        Concurrency int not null,
        primary key (Id)
    ) default charset=ascii;
');
prepare script from @createTable;
execute script;
deallocate prepare script;

/* AddProperty OrderId */

select count(*)
into @exist
from information_schema.columns
where table_schema = database() and
      column_name = 'Correlation_OrderId' and
      table_name = @tableNameNonQuoted;

set @query = IF(
    @exist <= 0,
    concat('alter table ', @tableNameQuoted, ' add column Correlation_OrderId varchar(38) character set ascii'), 'select \'Column Exists\' status');

prepare script from @query;
execute script;
deallocate prepare script;

/* VerifyColumnType Guid */

set @column_type_OrderId = (
  select concat(column_type,' character set ', character_set_name)
  from information_schema.columns
  where
    table_schema = database() and
    table_name = @tableNameNonQuoted and
    column_name = 'Correlation_OrderId'
);

set @query = IF(
    @column_type_OrderId <> 'varchar(38) character set ascii',
    'call sqlpersistence_raiseerror(concat(\'Incorrect data type for Correlation_OrderId. Expected varchar(38) character set ascii got \', @column_type_OrderId, \'.\'));',
    'select \'Column Type OK\' status');

prepare script from @query;
execute script;
deallocate prepare script;

/* WriteCreateIndex OrderId */

select count(*)
into @exist
from information_schema.statistics
where
    table_schema = database() and
    index_name = 'Index_Correlation_OrderId' and
    table_name = @tableNameNonQuoted;

set @query = IF(
    @exist <= 0,
    concat('create unique index Index_Correlation_OrderId on ', @tableNameQuoted, '(Correlation_OrderId)'), 'select \'Index Exists\' status');

prepare script from @query;
execute script;
deallocate prepare script;

/* PurgeObsoleteIndex */

select concat('drop index ', index_name, ' on ', @tableNameQuoted, ';')
from information_schema.statistics
where
    table_schema = database() and
    table_name = @tableNameNonQuoted and
    index_name like 'Index_Correlation_%' and
    index_name <> 'Index_Correlation_OrderId' and
    table_schema = database()
into @dropIndexQuery;
select if (
    @dropIndexQuery is not null,
    @dropIndexQuery,
    'select ''no index to delete'';')
    into @dropIndexQuery;

prepare script from @dropIndexQuery;
execute script;
deallocate prepare script;

/* PurgeObsoleteProperties */

select concat('alter table ', table_name, ' drop column ', column_name, ';')
from information_schema.columns
where
    table_schema = database() and
    table_name = @tableNameNonQuoted and
    column_name like 'Correlation_%' and
    column_name <> 'Correlation_OrderId'
into @dropPropertiesQuery;

select if (
    @dropPropertiesQuery is not null,
    @dropPropertiesQuery,
    'select ''no property to delete'';')
    into @dropPropertiesQuery;

prepare script from @dropPropertiesQuery;
execute script;
deallocate prepare script;

/* CompleteSagaScript */

生成的表結構:

持久化配置

      Saga持久化需要依賴NServiceBus.Persistence.Sql。引入后需要實現SqlSaga抽象類,抽象類需要重寫ConfigureMapping,配置Saga工作流程業務主鍵。

public class Saga:SqlSaga<State>,
        IAmStartedByMessages<StartOrder>
{
   protected override void ConfigureMapping(IMessagePropertyMapper mapper)
   {
      mapper.ConfigureMapping<StartOrder>(message=>message.OrderId);
   }

   protected override string CorrelationPropertyName => nameof(StartOrder.OrderId);

   public Task Handle(StartOrder message, IMessageHandlerContext context)
   {
       Console.WriteLine($"Receive message with OrderId:{message.OrderId}");

       MarkAsComplete();
       return Task.CompletedTask;
    }
 }
    
 static async Task MainAsync()
 {
     Console.Title = "Client-UI";

     var configuration = new EndpointConfiguration("Client-UI");
     //這個方法開啟自動建表、自動創建RabbitMQ隊列
     configuration.EnableInstallers(); 
     configuration.UseSerialization<NewtonsoftSerializer>();
     configuration.UseTransport<LearningTransport>();

     string connectionString = "server=127.0.0.1;uid=root;pwd=000000;database=nservicebus;port=3306;AllowUserVariables=True;AutoEnlist=false";
     var persistence = configuration.UsePersistence<SqlPersistence>();
     persistence.SqlDialect<SqlDialect.MySql>();
     //配置mysql連接串
     persistence.ConnectionBuilder(()=>new MySqlConnection(connectionString));

     var instance = await Endpoint.Start(configuration).ConfigureAwait(false);

     var command = new StartOrder()
     {
         OrderId = Guid.NewGuid()
     };

     await instance.SendLocal(command).ConfigureAwait(false);

     Console.ReadKey();

     await instance.Stop().ConfigureAwait(false);
 }

     

Saga Timeouts

     在消息驅動類型的環境中,雖然傳遞的無連接特性可以防止在線等待過程中消耗資源,但是畢竟等待時間需要有一個上線。在NServiceBus里已經提供了Timeout方法,我們只需訂閱即可,可以在你的Handle方法中根據需要訂閱Timeout,可參考如下代碼:

public class Saga:Saga<State>,
        IAmStartedByMessages<StartOrder>,
        IHandleMessages<CompleteOrder>,
        IHandleTimeouts<TimeOutMessage>
    {
        
        public Task Handle(StartOrder message, IMessageHandlerContext context)
        {
            var model=new TimeOutMessage();
            
            //訂閱超時消息
            return RequestTimeout(context,TimeSpan.FromMinutes(10));
        }

        public Task Handle(CompleteOrder message, IMessageHandlerContext context)
        {
            MarkAsComplete();
            return Task.CompletedTask;
        }

        protected override string CorrelationPropertyName => nameof(StartOrder.OrderId);


        public Task Timeout(TimeOutMessage state, IMessageHandlerContext context)
        {
            //處理超時消息
        }

        protected override void ConfigureHowToFindSaga(SagaPropertyMapper<State> mapper)
        {
            mapper.ConfigureMapping<StartOrder>(message=>message.OrderId).ToSaga(saga=>saga.OrderId);
            mapper.ConfigureMapping<CompleteOrder>(message=>message.OrderId).ToSaga(saga=>saga.OrderId);
        }
    }
//從Timeout的源碼看,這個方法是通過設置SendOptions,然後再把當前這個消息發送給自己來實現 
protected Task RequestTimeout<TTimeoutMessageType>(IMessageHandlerContext context, TimeSpan within, TTimeoutMessageType timeoutMessage)
 {
     VerifySagaCanHandleTimeout(timeoutMessage);
     var sendOptions = new SendOptions();
     sendOptions.DelayDeliveryWith(within);
     sendOptions.RouteToThisEndpoint();
     SetTimeoutHeaders(sendOptions);

     return context.Send(timeoutMessage, sendOptions);
 }

總結

       NServiceBus因為是商業產品,對分佈式消息系統所涉及到的東西都做了實現,包括分佈式事務(Outbox)、DTC都有,還有心跳檢測,監控都有,全而大,目前我們用到的也只是NServiceBus里很小的一部分功能。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

就該這樣理解 OSI 七層參考模型、淺談不同局域網之間的通信

簡介

說到OSI參考模型,理解網絡與網絡之間的關係,不說太深入難以理解的東西,只求能最大程度上理解與使用。

參考模型是國際標準化組織(ISO)制定的一個用於計算機或通信系統間互聯的標準體系,一般稱為OSI參考模型或七層模型。

概念性的東西,先知道這些就夠了,我們先來聊一聊一個常見的一個模型。

 

局域網與互聯網

互聯網就是許許多多個局域網組成的,從我們最簡單的一個局域網入手,開始理解

這裏舉例兩個不同的局域網,計算機用網線接入交換機、交換機連接網關路由器,另一處

也是通過相同的方式進行連接。先來了解一下OSI參考模型是如何定義這七層的

OSI 參考模型

參考:

這裏定義的七層只是為了方便我們去理解,實際上是不存在的。

簡單的了解一下這七層是如何定義的,具體的功能還是得舉例子來理解說明。

從最上層的應用層開始說起:如何一步步的封裝數據,到最後進行發送。

應用層

應用層是直接面向用戶的最高層,但它卻不是應用程序,它只是為引用程序提供服務的

就好比,我們用的電腦版微信吧!它就是一個實實在在的應用程序,假設要與一個遠方的小姐姐進行聊天會話,這個時候呢,發送一個Hello給遠方的小姐姐。

當你點擊發送的時候,其實做了很多事情,我們就來梳理一下。

需要發送的數據就是:Hello ,當然,應用層首先給這個數據拼接一個AH,這裏就是應用層的報頭,就好比是微信的一個特有的數據,就這樣先理解。

 

表示層

當然,我們總不能發送明文吧,將發送的文本數據進行編碼,平常我們計算機使用的萬國碼UTF-8,肯定要進行一下加密吧

表示層更關心的是所傳送數據的語法和語義,主要包括數據格式變化、與解密、與解壓等

 

會話層

字面意思,就可以理解出這一層表示的意思,建立一個會話,就好比使用Http訪問web的時候,都會存在一個Session 作為標識

讓服務器來區別訪問的計算機。主要功能是負責維護兩個節點之間的傳輸聯接,確保點到點傳輸不中斷,以及管理數據交換等功能。

會話層在應用進程中建立、管理和終止會話。會話層還可以通過對話控制來決定使用何種通信方式,通信或通信。會話層通過自身協議對請求與應答進行協調

 

傳輸層 端到端

傳輸層作為OSI參考模型中,最重要的一層,這裏主要是以端口到端口來區分。這裏涉及到兩個特別重要的協議TCP 以及UDP

一太計算機上同時運行着QQ、微信、以及瀏覽器等。發送數據報,這個數據包到底是哪個程序發出去的呢?當然要從指定的一個端口發出去

計算機的端口範圍 0-65535

0-1023 就是1024個端口為系統佔用端口

了解到這些,就該先來說說UDP協議

UDP 協議

UDP協議定義了端口,同一個主機上的每個應用程序都需要指定唯一的端口號,並且規定網絡中傳輸的數據包必須加上端口信息,當數據包到達主機以後,就可以根據端口號找到對應的應用程序了。UDP協議比較簡單,實現容易,但它沒有確認機制,數據包一旦發出,無法知道對方是否收到,因此可靠性較差,為了解決這個問題,提高網絡可靠性,TCP協議就誕生了。

 

參考:

一個UDP報文包含首部與數據部分,UDP首部佔用8個字節,數據部分最長長度為65535B(字節) 即 64KB

UDP協議是無連接,不保證穩定傳輸的協議,但處理速度較快,通常的音頻、視頻在傳送時候使用UDP較多。

我們這裏的例子是微信,微信能保證數據百分百到達,所以我們採用TCP來具體說明數據的封裝

 

TCP 協議

TCP即傳輸控制協議,是一種面向連接的、可靠的、基於字節流的通信協議。簡單來說TCP就是有確認機制的UDP協議,每發出一個數據包都要求確認,如果有一個數據包丟失,就收不到確認,發送方就必須重發這個數據包。為了保證傳輸的可靠性,TCP協議在UDP基礎之上建立了三次對話的確認機制,即在正式收發數據前,必須和對方建立可靠的連接。TCP數據包和UDP一樣,都是由首部和數據兩部分組成,唯一不同的是,TCP數據包沒有長度限制,理論上可以無限長,但是為了保證網絡的效率,通常TCP數據包的長度不會超過IP數據包的長度,以確保單個TCP數據包不必再分割

 

參考:

 這裏只需要了解的是TCP的基本封裝過程,這裏只涉及到源端口以及目的端口,還未涉及到IP相關的內容。它和UDP協議一樣。就好像是一個改進版的

UDP協議,它能保證數據的可靠傳輸,這個特點記住即可。這裏模擬一下,我們數據的封裝過程。假設微信使用的端口是6666,目標端口就是遠方小姐姐微信

的端口,當然也是一樣的。這裏我為了理解只做簡寫

 

網絡層

從上面幾層來看,我們已經將微信的數據封裝成來一個TCP數據報,裡面包含來微信的端口 假設是6666,當然,就好比寫信一樣,我的信封

已經準備好勒,裏面要發送的內容我也已經準備好了,接下來就是地址了。肯定要指定這個報文我要發送到哪裡去。所以呢IP 網際協議,就誕生了。

IP 網際協議

網絡層引入了IP協議,制定了一套新地址,使得我們能夠區分兩台主機是否同屬一個網絡,這套地址就是網絡地址,也就是所謂的IP地址。IP協議將這個32位的地址分為兩部分,前面部分代表網絡地址,後面部分表示該主機在局域網中的地址。如果兩個IP地址在同一個子網內,則網絡地址一定相同。為了判斷IP地址中的網絡地址,IP協議還引入了子網掩碼,IP地址和子網掩碼通過按位與運算后就可以得到網絡地址

IP地址在這裏我們就比較好理解了。我們平時的生活中都會涉及到。一個IP指向的就是互聯網當中的一台機器或者就是一台路由器了。

我們來封裝數據。再把上面的圖拿下來,說明一下,我們要給E電腦的小姐姐發送消息。比如我是A電腦,小姐姐在另外一個網關下的E電腦

 

 

比較重要的兩個參數:

源地址:192.168.0.120

目標地址:192.168.1.135

進行封裝后的數據,這裏將源地址,告訴路由器(郵局) 發件人 就是源地址,以及收件人 也就是目標地址

 

ARP 協議

這裏暫時不細說這個ARP協議的內容。我們只需要知道 ARP協議是用來拿IP換MAC地址的,上面的IP協議也已經提過了,通過子網掩碼和IP地址的換算,可以得到

網絡號,網絡號就可以區別這兩個IP是否在同一個局域網內。 參考這個秒懂:

數據鏈路層

到這一層,就已經到網卡、網絡設備(交換機)的範疇了。數據鏈路層最重要的協議是以太網協議,數據鏈路層最重要的一點就是數據成幀。

以太網協議

接入以太網的設備必須包含一塊以太網網卡,也就是我們常用的網卡,一組電信號稱作是一個數據幀 、或者叫做一個數據包

網卡都包含一個全球唯一的MAC地址,發送端的和接收端的地址便是指網卡的地址,即Mac地址。 

每塊網卡出廠時都被燒錄上一個實際上唯一的Mac地址,長度為48位2進制,通常由12位16進制數表示,(前六位是廠商編碼,后六位是流水線號)

 進行數據鏈路層的封裝,將本機的MAC(源MAC地址) 和目標MAC地址封裝在頭部,在尾部加入DT報尾,這樣 一個數據幀算是封裝完成了!

等等。我們好像還不知道小姐姐那邊的目標MAC地址,這時候就需要用ARP協議了。我們知道ARP協議就是用來用IP來換MAC地址的。

 

ARP協議

上面已經簡單的了解過了,我們要和在B局域網下的E電腦進行通信,但是我們不知道它的MAC地址,於是我們發送一個ARP請求,來獲取目標的MAC地址

目標MAC 為FF:FF:FF:FF:FF:FF 表示的是廣播地址,這個數據包發出去后,所有的子網機器都會收到,收到的機器判斷目標MAC是否是自己,若不是,則直接丟棄

若是,收到報文的主機會通過單播的形式,將MAC地址回傳給我們。

通過路由協議我們可以得知,若不在一個一個子網內,則會交給路由器

 路由器返回的包裏面,目標MAC就會變成路由器的MAC地址,我們拿路由器的MAC地址組裝數據鏈路層報文即可。

物理層 

經過以上的每一層的層層包裝,這時候,我們已經包裝好了一個以太網數據幀,包含源MAC,目標MAC,源IP,目標IP等等一系列數據。

物理層就是將這個數據通過電信號、光信號的方式傳遞過去的,物理層一般都是我么所說的光纜以及網線這些硬件設備。

 

 

 不同子網間的通信

通過上面的知識,我們已經了解到如何封裝成一個數據幀,以及一些協議的相關內容。那麼這裏就會有一個問題,同一子網、

不同子網、以及相隔很遠的兩個子網是如何進行通信的呢?以及我們撥號上網后,公網IP與內網IP是怎麼一回事呢?

 

同一子網通信

我們先來看一個圖,計算機A要與計算機B進行通信,這時候他們是同處於一個子網內的,這個時候就很簡單了。

按照上面的七層進行封裝數據,這裏的具體參數需要說明一下:

源IP: 0.120(簡寫)

目標IP:0.113

源MAC : A電腦的MAC

目標MAC:B電腦MAC(這裏若不知道就先發送ARP請求)

 

 

A將數據報發送出去后,交換機直接查詢目標MAC所轉發的端口,將這個數據報準確的推送到B電腦連接的那個端口即可。

不同子網通信

A電腦需要與E電腦進行通信,這時候發現A與E不在一個子網內,這時候呢,就需要路由器來協助了

源IP: A的IP

目標IP: E的IP

源MAC:A的 MAC

目標MAC: 路由器C的MAC

 

 

因為不在一個子網內,需要路由器來進行路由這個數據包,送至D路由器后,D路由器拿出數據報中目標的IP,發送ARP請求,

請求E的MAC地址,知道后,將數據報裏面的目標MAC進行替換,然後發送給E即可。

 

公網IP與內網IP通信的方式理解

我們在使用路由器上網后,運營商就會給我們分配一個公網IP,按照圖上的指示,C路由器在進行撥號后,就會給C路由器分配一個公網IP

我這裏假設有這樣兩個。這時候需要封裝數據,該如何封裝呢,還是以A電腦與E電腦進行通信,大家肯定會很迷惑。

這裏就需要了解一個協議:網路地址轉換協議

以下簡稱NAT,NAT 在IPV4 之前起到很大的作用,我們現在也在用,因為IPV4 IP數量的限制,但接入互聯網的電腦又那麼多

該怎麼辦呢。就是給一個路由下分配一個公網IP,路由器下面的IP與公網IP進行一個轉換,這裏面說的轉換就是:NAT

圖中黑色的就是轉換部分,通過端口的轉換,將多個子網IP映射到公網的一個IP上面

 

網絡地址端口轉換(NAPT)

這種方式支持端口的映射,並允許多台主機共享一個公網IP地址。 支持端口轉換的NAT又可以分為兩類:源地址轉換和目的地址轉換。前一種情形下發起連接的計算機的IP地址將會被重寫,使得內網主機發出的數據包能夠到達外網主機。后一種情況下被連接計算機的IP地址將被重寫,使得外網主機發出的數據包能夠到達內網主機。實際上,以上兩種方式通常會一起被使用以支持雙向通信。 還是舉例,這時候,我們的A電腦需要與E電腦進行通信,E電腦在廣東省,他們撥號后,都會分配一個公網IP,並且已經在路由器裏面完成了NAT映射,
源IP: A電腦IP
目標IP: E電腦映射后的公網IP
源MAC :A電腦MAC
目標MAC :  本地路由器MAC地址   封裝完成后,將數據報送到C路由器,路由器通過映射表,將源IP進行一個替換

 

替換后,交給互聯網上的路由器進行數據報的轉發,這就好像發快遞時候一樣,經過一系列的中轉站,到達目的路由。

到達D路由后,D路由將數據報中的目標地址也進行一個轉換,這個地址是可以相互轉的。現在就是公網映射轉到本機IP

 

轉換后就輕鬆了。按照ARP請求到E機器的MAC地址,然後發報即可。

 

小結

以上內容皆是自己查看一些博主的總結,通過學習后,能夠加深自己對OIS模型、以及TCP、IP、ARP

這些非常重要的協議的一個認識。以及了解到不同層級下面。兩台電腦如何完成一個通行。這裏講的比較淺,

互聯網的奧妙不是那麼容易就可以理解透的。還是那句,不要停止學習的腳步。就好

 

參考:

  • ARP請求 
  • 不同子網內兩台機器的通信方式 
  • OSI 參考模型 
  • 內網端口與外網端口的理解 
  • 網絡地址轉換協議:

 

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

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

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