[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...

相关文章
相关标签/搜索