[Angular, TypeScript, 路由算法] 模擬IP層路由協議,實現LS算法、洪泛算法、DV算法、路由毒化

Github: https://github.com/Joilence/n...前端

路由模擬項目介紹

鏈路狀態協議(Link-state routing protocol)和距離向量路由協議(Distance-vector routing protocol)是分組交換(Packet switching)網絡中最主要的兩種路由協議。本項目的模擬路由器實現了LS路由算法、LS廣播洪泛、DV路由算法,以及防止DV路由環路和無窮計數問題的策略。此外還實現了完整的先後端以便研究者經過UI界面自定義網絡拓撲、控制路由器、查看路由器信息和日誌。node

鏈路狀態算法(LS)

LS算法要求網絡中每一個節點都收集完整的網絡信息,以鄰接表的形式存儲整個網絡的拓撲結構和全部鏈路的費用,而後根據這個圖來運行路由選擇算法(在這裏咱們選擇Dijkstra算法),計算出從本節點到網絡中全部其餘節點的最低費用路徑。git

LS廣播

爲了讓每一個節點都知道整個網絡的拓撲結構和全部鏈路費用,每一個節點都要將本身直連的鏈路信息廣播給網絡中的全部節點(LS廣播)。要廣播的信息包括本身的鄰居有哪些、到達它們的鏈路開銷分別是多少。github

爲了更新它自己存儲的網絡拓撲圖,在接收到其餘節點的LS廣播時,要根據廣播中的鏈路信息更新本身的鄰接表。算法

同時,在接收到其餘節點的LS廣播時,要將它轉發給本身的全部鄰居,從而這個LS廣播能散播到整個網絡。爲了不廣播風暴(廣播包在網絡中無休止地傳播,致使網絡癱瘓),每一個路由器要辨別接收到的LS廣播包是否是已經接收過。這能夠經過一個廣播包中的序列號字段來作到。每臺ls路由器,每次廣播使用一個遞增的序列號,若是屢次收到來自同一臺路由器且序號相同的廣播,則不更新鄰接表,也不轉發給鄰居,防止廣播風暴。typescript

迪傑斯特拉算法(Dijkstra's algorithm)

Dijkstra算法可以計算出圖中全部節點到某個節點的最短路徑。對於一個圖和一個給定的原點,Dijkstra算法不斷選擇一個距離源點最近且還沒有擴展的節點w來擴展,並更新w的鄰居節點到原點的距離。最終,全部被擴展的節點就是從原點能夠到達的節點,它們被擴展時到原點的距離就是最終的最短距離。
npm

何時觸發Dijkstra算法計算出新的路由表

Dijkstra算法的輸入就是節點維護的鄰接表,所以只要鄰接表有更新,就要觸發Dijkstra算法計算出新的路由表。
那麼鄰接表何時會更新呢?有2種狀況:後端

  1. 本節點的直連鏈路發生變化,要更新鄰接表中對應的鏈路。
  2. 接收到新的LS廣播,要根據LS廣播中的信息更新鄰接表。

距離向量算法(DV)

DV算法不須要全局網絡信息。每一個節點只從直連鄰居接收路由通告,執行DV計算,而後將計算結果分發給直連鄰居。重複這個過程,直到每一個節點的DV計算結果都與上一次的DV計算結果相同,此時網絡中再也不有路由通告,算法終止。瀏覽器

DV算法

DV算法的思想相對比較簡單:鄰居能到達的節點,我通過這個鄰居也能到達,而且我去目標節點的費用 = 我到鄰居的費用 + 鄰居到目標節點的費用。一個節點的DV存儲的就是這個節點能到達哪些節點、費用分別是多少。
服務器

DV算法的輸入是全部鄰居的DV和本身的直連鏈路信息,輸出是本身的DV(也就是路由表)。若是輸出的DV與上次輸出的不一樣,也就是本身的DV發生了變化,那麼要將本身的DV通告給全部鄰居。

有幾點須要注意:

  1. 爲了保證DV算法的自我終止(網絡中再也不有DV通告,也再也不運行DV算法),僅僅當DV發生變化時才通告給鄰居。
  2. DV的計算徹底不依賴於本身上次計算獲得的DV(也就是本身當前的路由表)。

路由環路和無窮計數問題

因爲DV算法沒有全局網絡信息,DV算法中可能會出現路由環路和無窮計數的問題。

The Bellman–Ford algorithm does not prevent routing loops from happening and suffers from the count-to-infinity problem. The core of the count-to-infinity problem is that if A tells B that it has a path somewhere, there is no way for B to know if the path has B as a part of it.
若是A告訴B:A能到達C。因爲B沒有全局網絡信息,它沒法知道本身是否已經處於從A到C的路徑上。
https://en.wikipedia.org/wiki...

爲了不路由環路和無窮計數,咱們使用了Split-horizon routing with poison reverseHolddown的策略。詳見路由器設計文檔。

  • 路由毒化:若是一個節點A發現節點B變爲不可達,那麼A就要毒化從A到B的路由(也就是將去往B節點的費用設置爲無窮),而且將毒化信息傳播給全部鄰居。若是A的某個鄰居C本來是通過A去往B的,那麼C去往B的路由也被毒化,同時C向它的鄰居也傳播毒化信息,以此類推。這個過程就像是毒性的傳播同樣。最終,經過A-B鏈路去往B的那些節點都會被毒化,從而沒有路由會再利用這個失效的A-B鏈路。
  • 橫向分割:若是節點A是經過B去往C的,那麼A不告訴B:「我能到達C」。從而避免再B-C鏈路失效之後,B選擇經過A來到達C。這個方法只能避免2個節點組成的路由環路。
  • Holddown:在去往C的路由被毒化的一段時間內,再也不接受鄰居通告的任何去往C節點的路由,由於這個路由可能也使用了失效的鏈路,只不過還沒有被毒化。
  • 毒性逆轉:通常與橫向分割和Holddown一塊兒使用。若是C接受到A的毒化路由之後也中毒,那麼C要把本身毒化後的路由發給A。從而A能更新鄰居的DV使其不包含失效的路由。

設計文檔

路由器設計

Router類設計圖: https://www.processon.com/vie...

咱們的路由器實例是運行在同一臺機器上的,它們之間經過UDP進行通訊。

路由器類的主要成員

  • prot是本路由器的監聽的端口。路由器與路由器之間經過UDP socket進行通訊(交換路由信息、發送普通消息報文)。因爲prot確定是全局惟一的,所以咱們也將它用做路由器的標識符。
  • neighbors存儲直連的鄰居信息,包括鄰居的port、鏈路的cost。爲了加速查詢,它的數據結構是一個以鄰居port爲key的Map。

    • 它是網絡拓撲變化的根原本源,它的更新會觸發ls算法或dv算法的執行、ls廣播或dv通告。
    • 當修改網絡拓撲時,修改它,網絡拓撲的變化信息就能擴散到整個網絡
    • adjacencyList、DVs的更新從根本上來講都來自於它的更新。
    • 詳見 https://www.processon.com/dia...
    • 算法爲ls時,將它廣播到整個網絡
    • 算法爲dv時,在計算本身的dv(也就是路由表)的時候須要用到它
  • routeTable是路由器的路由表。用來轉發數據包。

    • 它是ls或dv的計算結果,不要直接修改routeTable,而是修改數據來源(也就是neighbors或adjacencyList或neighborsDVs),而後觸發路由算法,從而更新路由表。
    • 道理相似於:咱們不該該直接修改編譯器的輸出代碼,而應該去修改輸入編譯器的源代碼,而後從新編譯。從而輸出會相應地改變。
  • adjacencyList只在鏈路算法爲ls的時候使用,它是存儲了整個網絡信息的鄰接表。當接收到ls廣播,或本身的直連鏈路變化(也就是neighbors變化),都要觸發它的更新。

    • 使用鄰接鏈表運行Dijkstra算法時,要忽略那些單向的鏈路(也就是說,若是A的鄰居中有B,但B的鄰居中沒有A,那麼不算這條鏈路)。
    • Dijkstra算法的輸出只包括從本節點可達的節點,利用這一點,能夠按期將adjacencyList中已經不可達的節點的鄰居表刪除。
  • neighborsDVs(在設計圖中的DVs)維護了全部鄰居發來的DV通告。爲了加速存取,它的數據結構是以鄰居port爲key的Map。

先後端設計

前端是用Angular5和typescript製做的簡單UI,運行在瀏覽器中;後端是用Node.js寫的服務器,模擬路由是徹底在後端進行的。前端與後端之間經過WebSocket而不是HTTP來通訊,以便後端能主動、實時地發送信息給前端顯示。

前端主要包含4個部分:

  1. BackendService負責經過WebSocket與後端進行通訊;
  2. NetworkService負責根據後端發來的消息來繪製UI,並響應用戶在UI上的操做;
  3. PanelComponent負責展現用戶選中的路由器或鏈路的信息,並提供一些針對選中對象的操做。
  4. Chrome(或其餘瀏覽器)控制檯。後端運行的路由器實例產生的日誌將經過Websocket鏈接發送到前端,前端將日誌打印在控制檯。用戶若是想要查看路由器的運行過程須要打開控制檯再刷新頁面。因爲瀏覽器的控制檯自帶filter功能,用戶能夠選擇只查看某個路由器發出的日誌、某種操做發出的日誌。

後端主要包含3個文件:

  1. server.ts負責監聽WebSocket端口、調用RouterController來操做路由器和鏈路;
  2. RouterController.ts負責維護並操做網絡中全部的路由器實例,好比鏈接路由器、關閉路由器、改變路由器之間的鏈路,它提供操做網絡的接口給server.ts;
  3. router.ts定義了路由器類,其實現了ls算法和dv算法,並提供操做單個路由器的接口給RouterController.ts。

配置和運行

先安裝node.js
克隆這個倉庫切換到dev分支

  1. 運行後端程序。命令行進入server文件夾,依次執行「npm install」來安裝後端依賴,「npm run build」來編譯後端項目(此命令會一直監視文件變化並從新編譯)。再打開一個命令行窗口並進入server文件夾,執行「npm run serve」來運行後端項目,看到server is listening on port 8999表示服務端成功運行。

run server

  1. 運行前端程序。而後從命令行進入client文件夾,依次執行「npm install」和「ng serve」。看到已下信息表示客戶端網頁已經能夠能夠訪問。

client serve
打開瀏覽器,訪問「http://localhost:4200/」便可。若是還想要查看路由器日誌能夠打開瀏覽器控制檯並刷新頁面。若是出現「socket發生錯誤,點擊肯定刷新頁面」彈窗,表示客戶端沒法經過WebSocket鏈接到後端,請確保後端正在運行。

默認狀況下,運行的是dv算法的路由器。若是要換成ls算法,修改「router.ts」的這一行:

將「dv」改成「ls」(「npm run build」命令行窗口會監測到變化並從新編譯)。而後從新執行「npm run serve」來運行後端項目。

運行結果

result
左上角的操做欄能夠添加、刪除路由器和鏈路。點擊路由器或鏈路,會在右邊欄顯示它的信息和一些操做。路由日誌顯示在瀏覽器控制檯中,若是想要只查看某個路由器的日誌或某個操做的日誌,只須要在控制帶的Filter輸入框中輸入過濾字符串,好比「9014log」,或「route table has changed」。

能夠經過這個項目來自定義網絡拓撲、操做網絡拓撲,並觀察路由表的變化。具體的例子在視頻中展現。

閱讀資料

《計算機網絡 自頂向下方法 第六版》
https://en.wikipedia.org/wiki...
https://en.wikipedia.org/wiki...
https://en.wikipedia.org/wiki...
https://en.wikipedia.org/wiki...
https://en.wikipedia.org/wiki...
https://en.wikipedia.org/wiki...

相關文章
相關標籤/搜索