目录
小项目开发——内部网关协议 RIP 的模拟程序
一、需求分析
- 该程序的主要目的是模拟内部网关协议RIP(Routing Information Protocol),这是一种用于 Internet 协议(IP)网络的距离矢量路由协议,是互联网的标准协议,其最大优点就是简单。
1. RIP 工作原理
- RIP 协议要求网络中的每一个路由器都要维护从它自己到其它每一个目的网络的 距离记录。
- RIP 允许一条路径最多只能包含15个网络,因此“距离”等于 16时相当于不可达。
- 距离:从一路由器到直接连接的网络的距离定义为1,从一主机到非直接连接的网络的距离定义为经过的路由器数加1。
- RIP 的特点是:
- 仅和相邻路由器交换信息。
- 路由器交换的信息是当前本路由器所知道的全部信息,即自己现在的路由表。
- 按固定的时间间隔交换路由信息。
2. 程序功能叙述分析
程序功能 | 叙述分析 |
---|---|
网络初始化和路由表建立 | 在模拟开始时,程序需要初始化模拟网络的拓扑结构并建立每台路由器的路由表,包括定义网络、路由器之间的连接关系 |
网络拓扑变化与更新 | 程序应能够处理网络拓扑的动态变化,比如新网络的加入、现有网络的退出、路由器的故障等,并据此更新路由表 |
定期更新 | RIP 协议要求路由器定期与相邻交换路由信息。因此,程序需要有一个定时机制来模拟这种定期更新过程 |
用户交互 | 用户应能够通过命令或图形界面更改网络拓扑,并能够查询和输出指定路由器的路由表 |
二、概要设计
1. 设计思想
- RIP 协议模拟程序将采用 模块化 的设计思想。它将由不同的模块组成,每个模块负责实现协议的一部分功能。这些模块将协同工作以模拟 RIP 协议的整个工作流程。该程序的设计将遵循 面向对象的程序设计原则,利用类和对象来模拟网络、路由器和路由表。
2. 程序模块设计
模块 | 功能 |
---|---|
网络拓扑模块 | 实现NetworkTopology 类,负责管理整个网络的拓扑结构,包括添加和删除节点(网络模块)和边(连接),以及处理网络的动态变化 |
路由器模块 | 实现Router 类,负责模拟路由器的行为,包括维护路由表、处理来自相邻路由器的更新以及发送自己的更新 |
网络模块 | 实现Network 类,代表网络拓扑中的单个网络,负责维护单个网络信息 |
路由表模块 | 实现RouteTableEntry 类,负责存储和管理路由信息,包括目的网络、跳数和下一跳路由器 |
用户交互模块 | 实现RIPGUI 类,提供一个界面供用户进行交互,如更改网络拓扑结构和查询路由表等 |
定时器模块 | 负责定期触发路由更新事件,模拟 RIP 协议中的定时器功能,确保了周期性的路由信息交换 |
事件处理模块 | 负责响应用户输入的命令和网络事件,如路由器故障或网络的加入和退出 |
3. 存储结构设计
⑴ 路由器
属性 | 类型 | 说明 |
---|---|---|
routerName | String | 路由器名称 |
routingTable | Map<Network, RouteTableEntry> | 路由表 |
neighbors | List<Router> | 相邻路由器列表 |
scheduler | ScheduledExecutorService | 定时器 |
routingTableInfo | StringBuilder | 路由表信息 |
⑵ 路由表项
属性 | 类型 | 说明 |
---|---|---|
destination | Network | 目的网络 |
hops | int | 跳数(距离) |
nextHop | Router | 下一跳(路由器) |
⑶ 网络
属性 | 类型 | 说明 |
---|---|---|
networkName | String | 网络名称 |
⑷ 网路拓扑
属性 | 类型 | 说明 |
---|---|---|
routers | List<Router> | 路由器列表 |
networks | List<Network> | 网络列表 |
routingTablesInfo | StringBuilder | 某一时刻所有路由表信息 |
4. 算法设计
⑴ 距离向量算法
-
RIP 协议的核心算法是基于 Bellman-Ford 距离向量算法,其基本步骤如下:
-
初始化:每个路由器初始化自身的路由表,对于直接连接的网络设置跳数为1、下一跳路由器为自身。
-
交换路由信息:每个路由器定期向其所有邻居发送整个路由表。
-
更新路由表:当收到邻居的路由表时,对每个路由表项进行以下步骤:
- 把下一跳路由器设置为此相邻路由器,并把距离加1.
- 如果此时收到的表项中跳数大于等于16(使用16表示无穷大),则设置跳数为16。
- 如果自身路由表中没有这个目的网络的表项,则把该表项添加到自身路由表中。
- 否则(自身路由表中有这个目的网络的表项),如果下一跳路由器为相邻路由器,则把该表项替换自身路由表的相应表项。
- 否则(自身路由表中有这个目的网络的表项,下一跳路由器不是此相邻路由器),如果收到的表项中的跳数小于自身路由表中的跳数,则更新路由表。
- 否则,什么也不做。
-
处理拓扑变化:当网络拓扑发生变化时(例如,网络加入、退出、路由器故障等),相关路由器需要更新路由表,并将变化通知其邻居。
⑵ 初始化网络拓扑
- 网络拓扑构建:
- 定义所有路由器的基础属性,例如名称、相邻路由器以及初始路由表等。
- 定义所有网络的基础属性,例如名称等。
- 建立路由器、网络间的连接关系,包括目的网络、跳数、下一跳等。
- 路由器初始化:
- 每个路由器加载初始配置,其中包括直接连接的网络及其跳数(设置为1)。
- 对于不是直接连接的网络,初始时将不添加至路由表中。
- 网络初始化:
- 每个网络加载初始配置,其中包括网络名称等。
- 邻居关系建立:
- 确定每个路由器的邻居,并在路由器的邻居列表中记录这些信息。
- 为每个路由器设置一个定时器,以触发定期的路由更新。
- 路由表的初始广播:
- 一旦网络初始化完成,每个路由器将其路由表广播给所有直接连接的邻居。
- 邻居路由器接收到广播后,更新自己的路由表,并继续向自己的邻居广播。
⑶ 路由器加入
- 初始化:当路由器加入网络时,它会首先被赋予一个基本的路由表,其中包括直接连接的路由器/网络的路由信息。
- 广播:新加入的路由器将它的初始路由表广播给所有直接连接的邻居。
- 更新:接收到新路由表信息的邻居路由器根据 RIP 协议更新自己的路由表,并将此更新传播给它们的邻居,这个过程持续进行直到所有路由器接收到信息并更新路由表。
⑷ 路由器退出
- 标记为不可达:退出的路由器从网络中移除,所有以它为下一跳的路由路径都被更新为不可达(跳数设置为16)。
- 传播更新:这种变化被广播到网络中的所有路由器,它们更新自己的路由表以反映这种变化。
⑸ 网络加入
- 邻接路由器更新:当新网络加入时,所有与其直接相连的路由器会在它们的路由表中添加一条新的路由记录,并设置适当的跳数(通常为1)。
- 广播更新:这些路由器将更新后的路由表广播给邻居路由器,后者再根据 RIP 协议进行相应的处理。
⑹ 网络退出
- 路由表中标记为不可达:退出的网络在所有知道这个网络的路由器的路由表中删除目的网络为此网络的表项。
- 广播不可达状态:路由器将这个状态广播给它们的所有邻居,以更新网络中的路由表。
⑺ 路由器故障
-
标记为不可达:故障路由器在其它所有路由表中的所有表项被标记为不可达(跳数设置为16)。
-
更新路由表:每个检测到邻居故障的路由器将更新自己的路由表。
-
广播更新:然后它们将这个更新广播给它们的邻居,邻居们继续更新自己的路由表并广播,直到所有受影响的路由器都被更新。
三、详细设计
注:省略关于 UI 设计的代码,只展示 关键算法的实现源代码。
1. 开发环境
名称 | 概述 |
---|---|
操作系统 | Windows 11 |
集成开发环境 | IntelliJ IDEA 2022.2.4 |
编程语言 | Java |
JDK | openjdk-21.0.2 |
2. 程序结构
3. 路由器(Router.java)
import java.util.*;
import java.util.concurrent.*;
/**
* 路由器类
*
* @author wzy
* @date 2024-03-04 18:50:54
*/
public class Router {
private String routerName; // 路由器名称
private Map<Network, RouteTableEntry> routingTable; // 路由表
private List<Router> neighbors; // 相邻路由器列表
private ScheduledExecutorService scheduler; // 定时器
private StringBuilder routingTableInfo; // 路由表信息
public Router(String routerName) {
this.routerName = routerName;
this.routingTable = new HashMap<>();
this.neighbors = new ArrayList<>();
this.scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(this::sendUpdates, 0, 1, TimeUnit.SECONDS);
} // end Router()
/**
* 添加相邻路由器
*
* @param neighbor 相邻路由器
*/
public void addNeighbor(Router neighbor) {
neighbors.add(neighbor);
} // end addNeighbor()
/**
* 移除相邻路由器
*
* @param neighbor 相邻路由器
*/
public void removeNeighbor(Router neighbor) {
neighbors.remove(neighbor);
} // end removeNeighbor()
/**
* 添加直连网络(直连网络的跳数为1)
*
* @param network 直连网络
*/
public void addDirectlyConnectedNetwork(Network network) {
RouteTableEntry entry = new RouteTableEntry(network, 1, this);
this.routingTable.put(network, entry);
} // end addDirectlyConnectedNetwork()
/**
* 定时向所有相邻路由器发送路由更新
*/
public void sendUpdates() {
for (Router neighbor : neighbors) {
neighbor.receiveUpdate(this, new HashMap<>(this.routingTable));
}
} // end sendUpdates()
/**
* 接收来自相邻路由器的路由更新
*
* @param sender 相邻路由器
* @param receivedRoutingTable 路由更新表
*/
public void receiveUpdate(Router sender, Map<Network, RouteTableEntry> receivedRoutingTable) {
// ...这里省略,见“5.距离向量算法”
} // end receiveUpdate()
/**
* 从路由表中移除指定路由器的所有条目
*
* @param router 路由器
*/
public void removeEntriesForRouter(Router router) {
// ...这里省略,见“9.路由器加入/退出”
} // end removeEntriesForRouter()
/**
* 从路由表中移除指定网络的所有条目
*
* @param network 网络
*/
public void removeEntriesForNetwork(Network network) {
// ...这里省略,见“10.网络加入/退出”
} // end removeEntriesForNetwork()
/**
* 路由器故障
*/
public void failure() {
// ... 这里省略,见“11.路由器故障”
} // end failure()
/**
* 通知所有相邻路由器当前路由器故障
*/
private void notifyNeighborsForFailure() {
// ... 这里省略,见“11.路由器故障”
} // end notifyNeighborsForFailure()
/**
* 当前路由器故障时,更新路由表
*
* @param failedRouter 故障路由器
*/
public void updateRoutingTableForFailedRouter(Router failedRouter) {
// ... 这里省略,见“11.路由器故障”
} // end updateRoutingTableForFailedRouter()
/**
* 停止定时器
*/
public void stopScheduler() {
// ... 这里省略,见“11.路由器故障”
} // end stopScheduler()
/**
* 打印路由表信息
*
* @return 路由表信息
*/
public String printRoutingTable() {
// ...这里省略,见“8. 初始化网络拓扑”
} // end printRoutingTable()
// ...Getters and Setters
} // end class Router
- 构造函数:
- 在创建
Router
对象时,初始化路由表、邻居列表,并设置一个定时器用于定时向所有邻居路由器发送路由更新。- 路由器的基本操作:
addNeighbor
和removeNeighbor
方法允许添加或移除邻居路由器。addDirectlyConnectedNetwork
方法用于添加直连网络,即直接与路由器相连的网络,这些网络的跳数设为1。sendUpdates
方法定时向所有邻居路由器发送路由更新。receiveUpdate
方法用于接收来自邻居路由器的路由更新,并据此更新本地的路由表。- 处理路由器故障:
failure
方法模拟路由器故障,将所有路由表项的跳数设置为16,表示不可达,并通知所有邻居路由器。notifyNeighborsForFailure
方法用于通知所有邻居路由器当前路由器的故障。updateRoutingTableForFailedRouter
方法用于更新路由表,标记通过故障路由器作为下一跳的路由项为不可达。- 辅助功能:
removeEntriesForRouter
和removeEntriesForNetwork
方法允许从路由表中移除指定路由器或网络的所有条目。stopScheduler
方法停止定时任务,通常在路由器故障时调用。printRoutingTable
方法返回格式化的路由表信息,便于查看和调试。
4. 路由表项(RouteTableEntry.java)
/**
* 路由表项类
*
* @author wzy
* @date 2024-03-04 18:51:05
*/
public class RouteTableEntry {
private Network destination; // 目的网络
private int hops; // 跳数(距离)
private Router nextHop; // 下一跳(路由器)
public RouteTableEntry(Network destination, int hops, Router nextHop) {
this.destination = destination;
this.hops = hops;
this.nextHop = nextHop;
} // end RouteTable()
// ...Getters and Setters
} // end class RouteTableEntry
5. 网络(Network.java)
/**
* 网络类
*
* @author wzy
* @date 2024-03-03 15:02:36
*/
public class Network {
private String networkName; // 网络名称
public Network(String networkName) {
this.networkName = networkName;
} // end Network()
public String getNetworkName() {
return networkName;
} // end getNetworkName()
public void setNetworkName(String networkName) {
this.networkName = networkName;
} // end setNetworkName()
} // end class Network
6. 网络拓扑(NetworkTopology.java)
import java.util.*;
import java.util.concurrent.*;
/**
* 网络拓扑类
*
* @author wzy
* @date 2024-03-04 18:50:36
*/
public class NetworkTopology {
private static final String SEPARATOR1 = "-------------------------------".repeat(5) + "\n"; // 分割符
private static final String SEPARATOR2 = "-------------------------------".repeat(3) + "\n"; // 分割符
private List<Router> routers; // 路由器列表
private List<Network> networks; // 网络列表
private StringBuilder routingTablesInfo; // 所有路由表信息
public NetworkTopology() {
this.routers = new ArrayList<>();
this.networks = new ArrayList<>();
this.routingTablesInfo = new StringBuilder();
initNetworkTopology();
} // end NetworkTopology()
/**
* 初始化网络拓扑
*/
public void initNetworkTopology() {
// ...这里省略,见“6.初始化网络拓扑”
} // end initNetworkTopology()
/**
* 路由器加入
*
* @param router 路由器
*/
public void joinRouter(Router router) {
routers.add(router);
} // end joinRouter()
/**
* 路由器退出
*
* @param router 路由器
*/
public void exitRouter(Router router) {
// ...这里省略,见“9.路由器加入/退出”
} // end exitRouter()
/**
* 网络加入
*
* @param network 网络
*/
public void joinNetwork(Network network) {
networks.add(network);
} // end joinNetwork()
/**
* 网络退出
*
* @param network 网络
*/
public void exitNetwork(Network network) {
// ...这里省略,见“10.网络加入/退出”
} // end exitNetwork()
/**
* 查找路由器
*
* @param routerName 路由器名称
* @return 查找结果
*/
public Router findRouter(String routerName) {
return routers.stream()
.filter(router -> routerName.equals(router.getRouterName()))
.findFirst()
.orElse(null);
} // end findRouter()
/**
* 查找网络
*
* @param networkName 网络名称
* @return 查找结果
*/
public Network findNetwork(String networkName) {
return networks.stream()
.filter(network -> networkName.equals(network.getNetworkName()))
.findFirst()
.orElse(null);
} // end findNetwork()
/**
* 获取最新的路由表信息
*/
public String getRoutingTablesInfo() {
// ...这里省略,见“8. 初始化网络拓扑”
} // end getRoutingTablesInfo()
// ...Getters and Setters
} // end class NetworkTopology
- 构造函数:
- 在
NetworkTopology
对象创建时,初始化路由器列表和网络列表,并调用initNetworkTopology
方法来初始化网络拓扑。- 网络拓扑的初始化:
initNetworkTopology
方法中创建了六个路由器(Router
)实例和六个网络(Network
)实例,并通过addNeighbor
方法设置路由器之间的邻接关系,通过addDirectlyConnectedNetwork
方法将网络直接连接到特定路由器。之后,通过joinRouter
和joinNetwork
方法将路由器和网络加入到拓扑中。- 网络和路由器的管理:
joinRouter
和exitRouter
方法分别用于将路由器加入或从网络拓扑中移除。在移除路由器时,同时从所有其他路由器的邻居列表和路由表中删除与之相关的表项。joinNetwork
和exitNetwork
方法用于将网络加入或从网络拓扑中移除。在移除网络时,同时从所有路由器的路由表中删除与该网络相关的表项。- 查找功能:
findRouter
和findNetwork
方法提供了按名称查找路由器和网络的功能,使用 Java 8 的 Stream API 进行过滤和查找。- 路由表信息获取:
getRoutingTablesInfo
方法用于收集并返回拓扑中所有路由器的路由表信息。使用了分隔符SEPARATOR1
和SEPARATOR2
来格式化输出,增强了信息的可读性。
7. 距离向量算法
- 【文件】:Router.java
/**
* 定时向所有相邻路由器发送路由更新
*/
public void sendUpdates() {
for (Router neighbor : neighbors) {
neighbor.receiveUpdate(this, new HashMap<>(this.routingTable));
}
} // end sendUpdates()
/**
* 接收来自相邻路由器的路由更新
*
* @param sender 相邻路由器
* @param receivedRoutingTable 路由更新表
*/
public void receiveUpdate(Router sender, Map<Network, RouteTableEntry> receivedRoutingTable) {
for (Map.Entry<Network, RouteTableEntry> entry : receivedRoutingTable.entrySet()) {
Network destination = entry.getKey(); // 目的网络
RouteTableEntry receivedEntry = entry.getValue(); // 路由表项
int newHops = receivedEntry.getHops() + 1; // 计算新的跳数
newHops = Math.min(newHops, 16); // 如果收到的跳数已经是16,或者加1后变为16,则直接使用16作为跳数
RouteTableEntry currentEntry = routingTable.get(destination); // 原来的路由表项
if (currentEntry == null) {
// 原来的路由表中没有目的网络 destination,则把该项目添加到原来的路由表中
routingTable.put(destination, new RouteTableEntry(destination, newHops, sender));
} else { // 在原来的路由表中有目的网络 destination
if (currentEntry.getNextHop().equals(sender)) {
// 若下一跳路由器是 sender,则把收到的项目替换原路由表中的项目
routingTable.put(destination, new RouteTableEntry(destination, newHops, sender));
} else if (newHops < currentEntry.getHops()) { // 若下一跳不是 sender
// 若收到的项目中的跳数<原来的路由表中的跳数,则进行更新
routingTable.put(destination, new RouteTableEntry(destination, newHops, sender));
}
}
}
} // end receiveUpdate()
sendUpdates
方法:这个方法的作用是向所有邻居路由器发送当前路由器的路由表信息。方法通过遍历当前路由器的邻居列表neighbors
,对每个邻居调用receiveUpdate
方法,传递当前路由器的实例(this
)作为发送者以及当前路由器的路由表的一个副本(new HashMap<>(this.routingTable)
)。这个副本是为了避免在传递过程中原路由表被修改。
receiveUpdate
方法:当路由器接收到来自邻居的路由更新时,会调用此方法。方法参数包括:
sender
: 发送路由更新的邻居路由器。receivedRoutingTable
: 接收到的路由更新表,包含目的网络和对应的路由表项(RouteTableEntry
)。接收到路由更新后,对于更新表中的每一项,方法执行以下步骤:
- 计算新跳数:将接收到的路由项的跳数加1(表示从当前路由器到目的网络需要额外一跳),并确保跳数不超过16(16通常表示不可达)。
- 更新路由表:
- 如果当前路由表中没有目的网络的条目,则直接添加接收到的条目,将跳数设置为计算后的新跳数,下一跳设置为发送更新的路由器。
- 如果当前路由表中已存在目的网络的条目,则需要进一步判断:
- 如果原条目的下一跳就是发送者,无论新的跳数如何,都用接收到的信息更新路由表。
- 如果原条目的下一跳不是发送者,但接收到的信息中的跳数比当前路由表中的跳数小,也更新路由表。这表示找到了一条更短的路径。
8. 初始化网络拓扑
- 【文件】:RIPGUI.java
/**
* 初始化网络拓扑
*/
private void initNetworkTopology() {
networkTopology = new NetworkTopology();
/* 在网络拓扑初始化后,恢复面板的除初始化的其它操作 */
initNetworkTopologyBtn.setEnabled(false);
resetNetworkTopologyBtn.setEnabled(true);
joinNetworkBtn.setEnabled(true);
exitNetworkBtn.setEnabled(true);
joinRouterBtn.setEnabled(true);
exitRouterBtn.setEnabled(true);
failureRouterBtn.setEnabled(true);
Runnable printTask = () -> {
routingTablesInfoTextArea.setText(ROUTING_TABLE_INFO);
routingTablesInfoTextArea.append(networkTopology.getRoutingTablesInfo());
};
scheduler.scheduleAtFixedRate(printTask, 0, 2, TimeUnit.SECONDS); // 每2s在输出文本框中更新所有路由表信息
} // end initNetworkTopology()
- 【文件】:NetworkTopology.java
/**
* 初始化网络拓扑
*/
public void initNetworkTopology() {
Router routerA = new Router("A");
Router routerB = new Router("B");
Router routerC = new Router("C");
Router routerD = new Router("D");
Router routerE = new Router("E");
Router routerF = new Router("F");
Network network1 = new Network("网1");
Network network2 = new Network("网2");
Network network3 = new Network("网3");
Network network4 = new Network("网4");
Network network5 = new Network("网5");
Network network6 = new Network("网6");
routerA.addNeighbor(routerB);
routerA.addNeighbor(routerD);
routerA.addNeighbor(routerE);
routerB.addNeighbor(routerA);
routerB.addNeighbor(routerC);
routerC.addNeighbor(routerB);
routerC.addNeighbor(routerF);
routerD.addNeighbor(routerA);
routerD.addNeighbor(routerE);
routerD.addNeighbor(routerF);
routerE.addNeighbor(routerA);
routerE.addNeighbor(routerD);
routerE.addNeighbor(routerF);
routerF.addNeighbor(routerC);
routerF.addNeighbor(routerD);
routerF.addNeighbor(routerE);
routerA.addDirectlyConnectedNetwork(network1);
routerA.addDirectlyConnectedNetwork(network2);
routerA.addDirectlyConnectedNetwork(network3);
routerB.addDirectlyConnectedNetwork(network3);
routerB.addDirectlyConnectedNetwork(network4);
routerC.addDirectlyConnectedNetwork(network4);
routerC.addDirectlyConnectedNetwork(network5);
routerD.addDirectlyConnectedNetwork(network2);
routerD.addDirectlyConnectedNetwork(network6);
routerE.addDirectlyConnectedNetwork(network1);
routerE.addDirectlyConnectedNetwork(network6);
routerF.addDirectlyConnectedNetwork(network5);
routerF.addDirectlyConnectedNetwork(network6);
joinRouter(routerA);
joinRouter(routerB);
joinRouter(routerC);
joinRouter(routerD);
joinRouter(routerE);
joinRouter(routerF);
joinNetwork(network1);
joinNetwork(network2);
joinNetwork(network3);
joinNetwork(network4);
joinNetwork(network5);
joinNetwork(network6);
} // end initNetworkTopology()
/**
* 获取最新的路由表信息
*/
public String getRoutingTablesInfo() {
int index = 0;
int size = routers.size();
routingTablesInfo = new StringBuilder();
routingTablesInfo.append(SEPARATOR1);
for (Router router : routers) {
String routerTableInfo = router.printRoutingTable();
routingTablesInfo.append(routerTableInfo);
if (index != size - 1) {
routingTablesInfo.append(SEPARATOR2);
}
index++;
}
return routingTablesInfo.toString();
} // end getRoutingTablesInfo()
- 【文件】:Router.java
/**
* 打印路由表信息
*
* @return 路由表信息
*/
public String printRoutingTable() {
routingTableInfo = new StringBuilder();
routingTableInfo.append(routerName).append("的路由表:\n");
routingTableInfo.append("目的网络\t\t跳数\t\t下一跳\n");
/* 保证路由表无重复项 */
Set<String> networkNames = new HashSet<>();
Iterator<Map.Entry<Network, RouteTableEntry>> it = routingTable.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Network, RouteTableEntry> entry = it.next();
RouteTableEntry routeEntry = entry.getValue();
String networkName = routeEntry.getDestination().getNetworkName();
if (networkNames.contains(networkName) && routeEntry.getHops() == 16) {
it.remove();
} else {
networkNames.add(networkName);
}
}
for (RouteTableEntry entry : routingTable.values()) {
routingTableInfo.append(entry.getDestination().getNetworkName()).append("\t\t")
.append(entry.getHops()).append("\t\t");
int hops = entry.getHops();
if (hops == 1) {
routingTableInfo.append("直接交付\n");
} else if (hops < 16) {
routingTableInfo.append(entry.getNextHop().getRouterName()).append("\n");
} else {
routingTableInfo.append("不可达\n");
}
}
return routingTableInfo.toString();
} // end printRoutingTable()
9. 路由器加入/退出
- 【文件】:JoinRouterDialog.java
/**
* 路由器加入
*/
private void joinRouter() {
String routerName = routerNameField.getText();
String neighbors = neighborField.getText();
String networks = directlyConnectedNetworkField.getText();
if (routerName.isEmpty() || neighbors.isEmpty() || networks.isEmpty()) {
JOptionPane.showMessageDialog(null, "信息未输入完整!!", "输入警告",
JOptionPane.WARNING_MESSAGE);
return;
}
Router findRouter = networkTopology.findRouter(routerName.trim());
if (findRouter != null) {
JOptionPane.showMessageDialog(null, "网络拓扑中已存在该路由器!!!", "加入错误",
JOptionPane.ERROR_MESSAGE);
return;
}
Router newRouter = new Router(routerName);
String[] neighborNames = neighbors.split(",");
String[] networkNames = networks.split(",");
if (ripgui.checkRoutersExist(neighborNames) && ripgui.checkNetworksExist(networkNames)) {
for (String neighborName : neighborNames) {
Router findNeighbor = networkTopology.findRouter(neighborName);
newRouter.addNeighbor(findNeighbor); // 路由器存在,则添加为相邻路由器
findNeighbor.addNeighbor(newRouter);
}
for (String networkName : networkNames) {
Network findNetwork = networkTopology.findNetwork(networkName.trim());
newRouter.addDirectlyConnectedNetwork(findNetwork); // 网络存在,则直连到路由器
}
} else return;
networkTopology.joinRouter(newRouter);
dispose();
JOptionPane.showMessageDialog(null, "路由器“" + routerName + "”已成功加入到网络拓扑中!");
} // end joinRouter()
- 输入验证:首先,通过获取
routerNameField
、neighborField
、directlyConnectedNetworkField
文本字段的内容来检查用户是否已输入所有必需的信息。如果任何字段为空,将显示一个警告消息框通知用户信息输入不完整,并退出方法。- 查找路由器:接着,方法使用用户输入的路由器名称调用
networkTopology.findRouter
方法来检查网络拓扑中是否已存在具有相同名称的路由器。如果找到了,显示一个错误消息框,并退出方法。- 创建新路由器并添加邻居和网络:
- 创建一个新的
Router
实例。- 分割
neighbors
和networks
字符串,得到邻居路由器名称和直连网络名称的数组。- 通过调用
ripgui.checkRoutersExist
和ripgui.checkNetworksExist
方法来检查输入的邻居路由器和网络是否都存在于网络拓扑中。- 如果所有输入的邻居路由器和网络都有效,那么对于每个邻居路由器名称,找到对应的
Router
实例,并通过调用addNeighbor
方法互相添加为邻居。- 对于每个网络名称,找到对应的
Network
实例,并通过调用addDirectlyConnectedNetwork
方法将网络直连到新路由器。- 将新路由器加入到网络拓扑中:调用
networkTopology.joinRouter
方法将新创建的路由器加入到网络拓扑。- 清理和通知:最后,方法调用
dispose
方法可能是用来关闭当前操作的窗口,并显示一个消息框,告知用户新路由器已成功加入到网络拓扑中。
- 【文件】:ExitRouterDialog.java
/**
* 路由器退出
*/
private void exitRouter() {
String routers = routerNameField.getText();
if (routers.isEmpty()) {
JOptionPane.showMessageDialog(null, "信息未输入完整!!", "输入警告",
JOptionPane.WARNING_MESSAGE);
return;
}
String[] routerNames = routers.split(","); // 使用逗号分割路由器名称
if (ripgui.checkRoutersExist(routerNames)) {
for (String routerName : routerNames) {
Router findRouter = networkTopology.findRouter(routerName);
networkTopology.exitRouter(findRouter);
}
} else return;
dispose();
JOptionPane.showMessageDialog(null, "选定的路由器已成功退出网络拓扑!");
} // end exitRouter()
/**
* 路由器退出
*
* @param router 路由器
*/
public void exitRouter(Router router) {
routers.remove(router);
for (Router router1 : routers) {
router.removeNeighbor(router1);
router1.removeNeighbor(router);
router1.removeEntriesForRouter(router);
}
} // end exitRouter()
/**
* 从路由表中移除指定路由器的所有条目
*
* @param router 路由器
*/
public void removeEntriesForRouter(Router router) {
/* 使用 Iterator 来避免在遍历时修改集合,避免 ConcurrentModificationException 异常 */
Iterator<Map.Entry<Network, RouteTableEntry>> it = routingTable.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Network, RouteTableEntry> entry = it.next();
if (entry.getValue().getNextHop().equals(router)) {
it.remove(); // 移除该条目
}
}
} // end removeEntriesForRouter()
- 输入验证:首先从
routerNameField
文本字段获取用户输入的路由器名称。如果用户没有输入任何内容,即文本字段为空,则显示一个警告消息框提示用户“信息未输入完整”,并退出方法。- 处理路由器名称:如果用户输入了路由器名称,输入的字符串将使用逗号(
,
)分割,生成一个包含一个或多个路由器名称的字符串数组routerNames
。- 检查路由器存在性:使用
ripgui.checkRoutersExist(routerNames)
方法检查用户输入的所有路由器名称是否都存在于网络拓扑中。这一步确保了只有存在于网络拓扑中的路由器才会被尝试移除。- 移除路由器:如果所有输入的路由器名称都通过了存在性检查,对于
routerNames
数组中的每个路由器名称,方法执行以下操作:
- 调用
networkTopology.findRouter(routerName)
方法根据路由器名称找到对应的Router
对象。- 调用
networkTopology.exitRouter(findRouter)
方法将找到的路由器从网络拓扑中移除。- 清理和通知:完成所有路由器的移除操作后,方法调用
dispose
可能用于关闭当前的操作窗口,最后显示一个消息框通知用户所选定的路由器已成功退出网络拓扑。
10. 网络加入/退出
- 【文件】:JoinNetworkDialog.java
/**
* 网络加入
*/
private void joinNetwork() {
String networkName = networkNameField.getText();
String routers = directlyConnectedRouterField.getText();
if (networkName.isEmpty() || routers.isEmpty()) {
JOptionPane.showMessageDialog(null, "信息未输入完整!!", "输入警告",
JOptionPane.WARNING_MESSAGE);
return;
}
Network findNetwork = networkTopology.findNetwork(networkName);
if (findNetwork != null) {
JOptionPane.showMessageDialog(null, "网络拓扑中已存在该网络!!!", "加入错误",
JOptionPane.ERROR_MESSAGE);
return;
}
Network newNetwork = new Network(networkName);
String[] routerNames = routers.split(","); // 使用逗号分割路由器名称
if (ripgui.checkRoutersExist(routerNames)) {
for (String routerName : routerNames) {
Router findRouter = networkTopology.findRouter(routerName);
/* 直连到新网络的所有路由器互为相邻路由器 */
for (String routerName1 : routerNames) {
Router findRouter1 = networkTopology.findRouter(routerName1);
if (!findRouter.equals(findRouter1)) {
findRouter.addNeighbor(findRouter1);
findRouter1.addNeighbor(findRouter);
}
}
findRouter.addDirectlyConnectedNetwork(newNetwork); // 路由器存在,则直连到网络
}
} else return;
networkTopology.joinNetwork(newNetwork);
dispose();
JOptionPane.showMessageDialog(null, "网络“" + networkName + "”已成功加入到网络拓扑中!");
} // end joinNetwork()
- 输入验证:首先从
networkNameField
和directlyConnectedRouterField
文本字段获取用户输入的网络名称和直接连接的路由器名称列表。如果用户未完全填写这些信息,则显示一个警告消息框提示用户“信息未输入完整”,并退出方法。- 检查网络存在性:使用用户输入的网络名称调用
networkTopology.findNetwork
方法来检查网络拓扑中是否已存在具有相同名称的网络。如果网络已存在,则显示一个错误消息框,并退出方法。- 创建新网络并连接路由器:
- 创建一个新的
Network
实例。- 用户输入的路由器名称列表(由逗号分割的字符串)被分割成字符串数组
routerNames
。- 使用
ripgui.checkRoutersExist(routerNames)
方法检查所有输入的路由器名称是否都存在于网络拓扑中。这一步确保只有存在的路由器才会被连接到新网络。- 如果所有输入的路由器名称都有效,则对于
routerNames
数组中的每个路由器名称:
- 调用
networkTopology.findRouter(routerName)
方法根据路由器名称找到对应的Router
对象。- 调用
findRouter.addNeighbor(findRouter1)
方法将网路直连的路由器互为相邻路由器。- 调用
findRouter.addDirectlyConnectedNetwork(newNetwork)
方法将新创建的网络直接连接到找到的路由器。- 将新网络加入到网络拓扑中:调用
networkTopology.joinNetwork(newNetwork)
方法将新创建的网络加入到网络拓扑。- 清理和通知:完成操作后,方法调用
dispose
可能用于关闭当前的操作窗口,并显示一个消息框通知用户新网络已成功加入到网络拓扑中。
- 【文件】:ExitNetworkDialog.java
/**
* 网路退出
*/
private void exitNetwork() {
String networks = networkNameField.getText();
if (networks.isEmpty()) {
JOptionPane.showMessageDialog(null, "信息未输入完整!!", "输入警告",
JOptionPane.WARNING_MESSAGE);
return;
}
String[] networkNames = networks.split(","); // 使用逗号分割网络名称
if (ripgui.checkNetworksExist(networkNames)) {
for (String networkName : networkNames) {
Network findNetwork = networkTopology.findNetwork(networkName.trim());
networkTopology.exitNetwork(findNetwork); // 网络存在,则从网络拓扑中移除
}
} else return;
dispose();
JOptionPane.showMessageDialog(null, "选定的网络已成功退出网络拓扑!");
} // end exitNetwork()
/**
* 网络退出
*
* @param network 网络
*/
public void exitNetwork(Network network) {
networks.remove(network);
for (Router router : routers) {
router.removeEntriesForNetwork(network);
}
} // end exitNetwork()
/**
* 从路由表中移除指定网络的所有条目
*
* @param network 网络
*/
public void removeEntriesForNetwork(Network network) {
/* 使用 Iterator 来避免在遍历时修改集合,避免 ConcurrentModificationException 异常 */
Iterator<Map.Entry<Network, RouteTableEntry>> it = routingTable.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Network, RouteTableEntry> entry = it.next();
if (entry.getKey().equals(network)) {
it.remove(); // 移除该条目
}
}
} // end removeEntriesForNetwork()
- 输入验证:首先,从
networkNameField
文本字段获取用户输入的网络名称。如果用户没有输入任何内容,即文本字段为空,则显示一个警告消息框提示用户“信息未输入完整”,并退出方法。- 处理网络名称:用户输入的网络名称列表(可能包含一个或多个网络名称,使用逗号
,
分割)被分割成字符串数组networkNames
。- 检查网络存在性并移除:
- 使用
ripgui.checkNetworksExist(networkNames)
方法检查所有输入的网络名称是否都存在于网络拓扑中。这一步确保了只有存在于网络拓扑中的网络才会被尝试移除。- 如果所有输入的网络名称都有效,则对于
networkNames
数组中的每个网络名称:
- 调用
networkTopology.findNetwork(networkName.trim())
方法根据网络名称查找对应的Network
对象。这里使用trim()
方法是为了确保名称两端没有多余的空白字符。- 调用
networkTopology.exitNetwork(findNetwork)
方法将找到的网络从网络拓扑中移除。- 清理和通知:完成所有指定网络的移除操作后,方法调用
dispose
可能用于关闭当前的操作窗口,并显示一个消息框通知用户所选定的网络已成功退出网络拓扑。
11. 路由器故障
- 【文件】:FailureRouterDialog.java
/**
* 路由器故障
*/
private void failureRouter() {
String routers = routerNameField.getText();
if (routers.isEmpty()) {
JOptionPane.showMessageDialog(null, "信息未输入完整!!", "输入警告",
JOptionPane.WARNING_MESSAGE);
return;
}
String[] routerNames = routers.split(","); // 使用逗号分割路由器名称
if (ripgui.checkRoutersExist(routerNames)) {
for (String routerName : routerNames) {
Router findRouter = networkTopology.findRouter(routerName.trim());
findRouter.failure(); // 路由器存在,则设置为故障状态
}
} else return;
dispose();
JOptionPane.showMessageDialog(null, "选定的路由器已成功设置为故障状态!");
} // end failureRouter()
/**
* 路由器故障
*/
public void failure() {
stopScheduler();
for (RouteTableEntry entry : new ArrayList<>(routingTable.values())) {
entry.setHops(16); // 使用16表示不可达
}
notifyNeighborsForFailure(); // 通知所有相邻路由器当前路由器故障
} // end failure()
/**
* 通知所有相邻路由器当前路由器故障
*/
private void notifyNeighborsForFailure() {
for (Router neighbor : neighbors) {
neighbor.removeNeighbor(this);
neighbor.updateRoutingTableForFailedRouter(this);
}
} // end notifyNeighborsForFailure()
/**
* 当前路由器故障时,更新路由表
*
* @param failedRouter 故障路由器
*/
public void updateRoutingTableForFailedRouter(Router failedRouter) {
for (RouteTableEntry entry : new ArrayList<>(routingTable.values())) {
if (entry.getNextHop().equals(failedRouter)) {
entry.setHops(16); // 使用16表示不可达
}
}
} // end updateRoutingTableForFailedRouter()
/**
* 停止定时器
*/
public void stopScheduler() {
if (scheduler != null && !scheduler.isShutdown()) {
scheduler.shutdown(); // 停止发送路由更新
}
} // end stopScheduler()
- 输入验证:首先检查用户是否在
routerNameField
文本字段中输入了路由器名称。如果没有输入(即文本字段为空),则会弹出一个警告消息框提示用户“信息未输入完整”,并终止方法执行。- 获取并处理路由器名称:如果用户输入了路由器名称,代码将使用逗号(
,
)作为分隔符将字符串分割成路由器名称的数组routerNames
。- 检查路由器存在性:通过调用
ripgui.checkRoutersExist(routerNames)
方法,检查用户输入的所有路由器名称是否都存在于网络拓扑中。这一步骤确保了只对存在的路由器执行故障模拟操作。- 模拟路由器故障:对于数组
routerNames
中的每一个路由器名称,执行以下操作:
- 使用
networkTopology.findRouter(routerName.trim())
方法根据路由器名称查找相应的Router
对象。这里trim()
方法的使用是为了确保名称字符串两端没有空白字符。- 对找到的
Router
对象调用failure()
方法,将该路由器设置为故障状态。具体的故障处理逻辑(在Router
类的failure()
方法中定义)包括停止所有定时任务、将路由表中的条目标记为不可达、通知所有邻接路由器该路由器已故障等操作。- 清理和用户反馈:完成上述操作后,调用
dispose()
方法用于关闭当前操作的对话框。然后,弹出一个消息框通知用户所选定的路由器已成功设置为故障状态。
12. 程序容错
⑴ 操作容错
- 【文件】:RIPGUI.java
- 【操作规则】:在网络拓扑还未初始化时,禁止面板除初始化以外的所有操作;在网络拓扑初始化后,恢复面板的除初始化的其它所有操作。
/**
* 初始化主面板的左部组件
*/
private void initLeftComponents() {
leftPanel = new JPanel(new GridBagLayout());
// ...其它组件初始化
/* 在网络拓扑还未初始化时,禁止面板除初始化以外的所有操作 */
resetNetworkTopologyBtn.setEnabled(false);
joinNetworkBtn.setEnabled(false);
exitNetworkBtn.setEnabled(false);
joinRouterBtn.setEnabled(false);
exitRouterBtn.setEnabled(false);
failureRouterBtn.setEnabled(false);
// ...其它组件初始化
mainPanel.add(leftPanel);
} // end initLeftComponents()
/**
* 初始化网络拓扑
*/
private void initNetworkTopology() {
networkTopology = new NetworkTopology();
/* 在网络拓扑初始化后,恢复面板的除初始化的其它操作 */
initNetworkTopologyBtn.setEnabled(false);
resetNetworkTopologyBtn.setEnabled(true);
joinNetworkBtn.setEnabled(true);
exitNetworkBtn.setEnabled(true);
joinRouterBtn.setEnabled(true);
exitRouterBtn.setEnabled(true);
failureRouterBtn.setEnabled(true);
Runnable printTask = () -> {
routingTablesInfoTextArea.setText(ROUTING_TABLE_INFO);
routingTablesInfoTextArea.append(networkTopology.getRoutingTablesInfo());
};
scheduler.scheduleAtFixedRate(printTask, 0, 2, TimeUnit.SECONDS);
} // end initNetworkTopology()
⑵ 输入容错
注:在路由器加入/退出、网络加入/退出、路由器故障功能中,使用以下两个方法实现了对输入框的验证。
- 【文件】:RIPGUI.java
/**
* 检查多个路由器是否存在
*
* @param routerNames 多个路由器名称
* @return 检查结果
*/
public boolean checkRoutersExist(String[] routerNames) {
StringBuilder notFoundRouters = new StringBuilder();
boolean isRoutersExist = true;
/* 对于每个路由器,检查是否存在于网络拓扑中 */
for (String routerName : routerNames) {
Router findRouter = networkTopology.findRouter(routerName.trim());
if (findRouter == null) {
if (!notFoundRouters.isEmpty()) {
notFoundRouters.append(", ");
}
notFoundRouters.append(routerName.trim());
isRoutersExist = false;
}
}
if (!isRoutersExist) {
JOptionPane.showMessageDialog(null, "网络拓扑中不存在路由器:\n"
+ notFoundRouters, "输入错误", JOptionPane.ERROR_MESSAGE);
}
return isRoutersExist;
} // end checkRoutersExist()
/**
* 检查多个网络是否存在
*
* @param networkNames 多个网络名称
* @return 检查结果
*/
public boolean checkNetworksExist(String[] networkNames) {
StringBuilder notFoundNetworks = new StringBuilder();
boolean isNetworksExist = true;
/* 对于每个网络,检查是否存在于网络拓扑中 */
for (String networkName : networkNames) {
Network findNetwork = networkTopology.findNetwork(networkName.trim());
if (findNetwork == null) {
if (!notFoundNetworks.isEmpty()) {
notFoundNetworks.append(", ");
}
notFoundNetworks.append(networkName.trim());
isNetworksExist = false;
}
}
if (!isNetworksExist) {
JOptionPane.showMessageDialog(null, "网络拓扑中不存在以下网络:\n"
+ notFoundNetworks, "输入错误", JOptionPane.ERROR_MESSAGE);
}
return isNetworksExist;
} // end checkNetworksExist()
checkRoutersExist
方法
- 参数:
routerNames
是一个包含多个路由器名称的字符串数组。- 处理流程:
- 初始化一个
StringBuilder
对象notFoundRouters
用于记录不存在于网络拓扑中的路由器名称。- 遍历
routerNames
数组中的每个路由器名称,对于每个名称:
- 使用
networkTopology.findRouter(routerName.trim())
方法检查该路由器是否存在于网络拓扑中。- 如果找不到路由器(即
findRouter
返回null
),则将该路由器名称添加到notFoundRouters
中,并将标记变量isRoutersExist
设置为false
。- 如果有一个或多个路由器不存在,显示一个错误消息框列出所有未找到的路由器名称。
- 返回值:如果所有指定的路由器都存在于网络拓扑中,则返回
true
;否则返回false
。checkNetworksExist
方法
- 参数:
networkNames
是一个包含多个网络名称的字符串数组。- 处理流程:
- 初始化一个
StringBuilder
对象notFoundNetworks
用于记录不存在于网络拓扑中的网络名称。- 遍历
networkNames
数组中的每个网络名称,对于每个名称:
- 使用
networkTopology.findNetwork(networkName.trim())
方法检查该网络是否存在于网络拓扑中。- 如果找不到网络(即
findNetwork
返回null
),则将该网络名称添加到notFoundNetworks
中,并将标记变量isNetworksExist
设置为false
。- 如果有一个或多个网络不存在,显示一个错误消息框列出所有未找到的网络名称。
- 返回值:如果所有指定的网络都存在于网络拓扑中,则返回
true
;否则返回false
。
四、调试分析
注:对于时间复杂度,定义 路由器数量为 m m m、网络数量为 n n n,且只分析自主设计的程序主要部分。
1. 主界面
2. 初始化网络拓扑
⑴ 测试数据
⑵ 测试输出的结果
- 初始路由表:
- 运行距离向量算法后:
⑶ 时间复杂度
- 时间复杂度 = O ( m n ) 时间复杂度=O(mn) 时间复杂度=O(mn)
- 因为初始化网络拓扑之后,第一层要遍历所有 **路由表(数量 m m m)**获取信息 O ( m ) O(m) O(m);同时最坏情况下,第二层路由表中有所有网络的表项,则 遍历所有路由表项 O ( n ) O(n) O(n)。
- 两层循环嵌套,总的时间复杂度为 O ( m n ) O(mn) O(mn)。
for (Router router : routers) {
String routerTableInfo = router.printRoutingTable(); // 遍历所有路由表项
routingTablesInfo.append(routerTableInfo);
if (index != size - 1) {
routingTablesInfo.append(SEPARATOR2);
}
index++;
}
/**
* 打印路由表信息
*
* @return 路由表信息
*/
public String printRoutingTable() {
routingTableInfo = new StringBuilder();
routingTableInfo.append(routerName).append("的路由表:\n");
routingTableInfo.append("目的网络\t\t跳数\t\t下一跳\n");
/* 保证路由表无重复项 */
Set<String> networkNames = new HashSet<>();
Iterator<Map.Entry<Network, RouteTableEntry>> it = routingTable.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Network, RouteTableEntry> entry = it.next();
RouteTableEntry routeEntry = entry.getValue();
String networkName = routeEntry.getDestination().getNetworkName();
if (networkNames.contains(networkName) && routeEntry.getHops() == 16) {
it.remove();
} else {
networkNames.add(networkName);
}
}
for (RouteTableEntry entry : routingTable.values()) {
routingTableInfo.append(entry.getDestination().getNetworkName()).append("\t\t")
.append(entry.getHops()).append("\t\t");
int hops = entry.getHops();
if (hops == 1) {
routingTableInfo.append("直接交付\n");
} else if (hops < 16) {
routingTableInfo.append(entry.getNextHop().getRouterName()).append("\n");
} else {
routingTableInfo.append("不可达\n");
}
}
return routingTableInfo.toString();
} // end printRoutingTable()
3. 路由器加入
⑴ 测试数据
⑵ 测试输出的结果
⑶ 时间复杂度
- 时间复杂度 = O ( m 2 + n 2 ) 时间复杂度=O(m^2+n^2) 时间复杂度=O(m2+n2)
- 最坏情况下,新路由器与所有已有路由器相邻、与所有网络直连,则以下代码两层
for
循环:一层遍历输入的所有相邻路由器名称 O ( m ) O(m) O(m);一层遍历路由器列表寻找该相邻路由器 O ( m ) O(m) O(m);网路同理 O ( n 2 ) O(n^2) O(n2)。 - 路由器两层循环嵌套和网络两层循环嵌套叠加,总的时间负责为 O ( m 2 + n 2 ) O(m^2+n^2) O(m2+n2)。
if (ripgui.checkRoutersExist(neighborNames) && ripgui.checkNetworksExist(networkNames)) {
for (String neighborName : neighborNames) { // 第一层循环
Router findNeighbor = networkTopology.findRouter(neighborName); // 第二层循环
newRouter.addNeighbor(findNeighbor); // 路由器存在,则添加为相邻路由器
findNeighbor.addNeighbor(newRouter);
}
for (String networkName : networkNames) { // 网络同理
Network findNetwork = networkTopology.findNetwork(networkName.trim());
newRouter.addDirectlyConnectedNetwork(findNetwork); // 网络存在,则直连到路由器
}
} else return;
/**
* 查找路由器
*
* @param routerName 路由器名称
* @return 查找结果
*/
public Router findRouter(String routerName) {
return routers.stream()
.filter(router -> routerName.equals(router.getRouterName()))
.findFirst()
.orElse(null);
} // end findRouter()
// ...省略 findNetwork 方法实现
4. 路由器退出
⑴ 测试数据
⑵ 测试输出的结果
⑶ 时间复杂度
- 时间复杂度 = O ( m 2 n ) 时间复杂度=O(m^2n) 时间复杂度=O(m2n)
- 最坏情况下,所有路由表退出,则以下代码有三层
for
循环:一层遍历输入的所有路由器名称 O ( m ) O(m) O(m);一层遍历路由器列表寻找该路由器 O ( m ) O(m) O(m) 和遍历路由器列表使直连同一网路的路由器移除相邻关系 O ( m ) O(m) O(m);一层遍历路由表项移除指定路由器的所有条目 O ( n ) O(n) O(n)。 - 三层循环嵌套和其中不同循环叠加,总的时间复杂度为 O ( m ( m + m n ) ) = O ( m 2 ( 1 + n ) ) ≈ O ( m 2 n ) O(m(m+mn))=O(m^2(1+n))\approx O(m^2n) O(m(m+mn))=O(m2(1+n))≈O(m2n)。
if (ripgui.checkRoutersExist(routerNames)) {
for (String routerName : routerNames) { // 第一层循环
Router findRouter = networkTopology.findRouter(routerName);
networkTopology.exitRouter(findRouter); // 第二层循环
}
} else return;
/**
* 路由器退出
*
* @param router 路由器
*/
public void exitRouter(Router router) {
routers.remove(router);
for (Router router1 : routers) {
router.removeNeighbor(router1);
router1.removeNeighbor(router);
router1.removeEntriesForRouter(router); // 第三层循环
}
} // end exitRouter()
/**
* 从路由表中移除指定路由器的所有条目
*
* @param router 路由器
*/
public void removeEntriesForRouter(Router router) {
/* 使用 Iterator 来避免在遍历时修改集合,避免 ConcurrentModificationException 异常 */
Iterator<Map.Entry<Network, RouteTableEntry>> it = routingTable.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Network, RouteTableEntry> entry = it.next();
if (entry.getValue().getNextHop().equals(router)) {
it.remove(); // 移除该条目
}
}
} // end removeEntriesForRouter()
5. 网络加入
⑴ 测试数据
⑵ 测试输出的结果
⑶ 时间复杂度
- 时间复杂度 = O ( m 3 ) 时间复杂度=O(m^3) 时间复杂度=O(m3)
- 最坏情况下,新网络与所有路由器直连,则以下代码有三层
for
循环:一层遍历输入的所有路由器名称 O ( m ) O(m) O(m);一层遍历路由器列表寻找该路由器 O ( m ) O(m) O(m) 和再次遍历输入的所有路由器名称 O ( m ) O(m) O(m) 以便将直连到新网络的所有路由器互为相邻路由器;一层遍历路由器列表寻找该路由器 O ( m ) O(m) O(m)。 - 三层循环嵌套和其中不同循环叠加,总的时间复杂度为 O ( m ( m + m 2 ) ) = O ( m 2 + m 3 ) ≈ O ( m 3 ) O(m(m+m^2))=O(m^2+m^3)\approx O(m^3) O(m(m+m2))=O(m2+m3)≈O(m3)。
if (ripgui.checkRoutersExist(routerNames)) {
for (String routerName : routerNames) { // 第一层循环
Router findRouter = networkTopology.findRouter(routerName);
/* 直连到新网络的所有路由器互为相邻路由器 */
for (String routerName1 : routerNames) { // 第二层循环
Router findRouter1 = networkTopology.findRouter(routerName1); // 第三层循环
if (!findRouter1.equals(findRouter)) {
findRouter.addNeighbor(findRouter1);
findRouter1.addNeighbor(findRouter);
}
}
findRouter.addDirectlyConnectedNetwork(newNetwork); // 路由器存在,则直连到网络
}
} else return;
6. 网络退出
⑴ 测试数据
⑵ 测试输出的结果
⑶ 时间复杂度
- 时间复杂度 = O ( m 3 ) 时间复杂度=O(m^3) 时间复杂度=O(m3)
- 最坏情况下,所有网络退出,则下面代码有三层循环:一层循环输入的所有网络名称 O ( n ) O(n) O(n);一层遍历网络列表寻找该网络 O ( n ) O(n) O(n) 和遍历路由器列表 O ( m ) O(m) O(m);一层再次遍历路由器列表 O ( m ) O(m) O(m) 使直连同一网路的路由器移除相邻关系和从路由表中移除指定网络的所有条目 O ( n ) O(n) O(n)。
- 三层循环嵌套和和其中不同循环叠加,总的时间复杂度为 O ( n ( n + n ( m + n ) ) ) = O ( n 2 + m n 2 + n 3 ) ≈ O ( n 3 ) O(n(n+n(m+n)))=O(n^2+mn^2+n^3)\approx O(n^3) O(n(n+n(m+n)))=O(n2+mn2+n3)≈O(n3)。
if (ripgui.checkNetworksExist(networkNames)) {
for (String networkName : networkNames) { // 第一层循环
Network findNetwork = networkTopology.findNetwork(networkName.trim());
networkTopology.exitNetwork(findNetwork); // 网络存在,则从网络拓扑中移除
}
} else return;
/**
* 网络退出
*
* @param network 网络
*/
public void exitNetwork(Network network) {
networks.remove(network);
for (Router router : routers) { // 第二层循环
/* 直连同一网路的路由器移除相邻关系 */
if (router.isDirectlyConnectedNetwork(network)) {
for (Router router1 : routers) { // 第三层循环
if (!router1.equals(router) && router1.isDirectlyConnectedNetwork(network)) {
router.removeNeighbor(router1);
router1.removeNeighbor(router);
}
}
}
router.removeEntriesForNetwork(network);
}
} // end exitNetwork()
/**
* 从路由表中移除指定网络的所有条目
*
* @param network 网络
*/
public void removeEntriesForNetwork(Network network) {
/* 使用 Iterator 来避免在遍历时修改集合,避免 ConcurrentModificationException 异常 */
Iterator<Map.Entry<Network, RouteTableEntry>> it = routingTable.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Network, RouteTableEntry> entry = it.next();
Router nextHop = entry.getValue().getNextHop();
if (entry.getKey().equals(network)) {
entry.getValue().setHops(16);
} else if (!nextHop.equals(this) && nextHop.isDirectlyConnectedNetwork(network)) {
entry.getValue().setHops(16);
}
}
} // end removeEntriesForNetwork()
7. 路由器故障
⑴ 测试数据
⑵ 测试输出的结果
⑶ 时间复杂度
- 时间复杂度 = O ( m 2 n ) 时间复杂度=O(m^2n) 时间复杂度=O(m2n)
- 最坏情况下,所有路由器故障且互为邻居,则以下代码有四层循环:一层遍历输入的所有路由器名称 O ( m ) O(m) O(m);一层遍历路由器列表寻找该路由器 O ( m ) O(m) O(m) 和遍历自身路由表 O ( n ) O(n) O(n) 设置所有表项的跳数为16表示该路由器故障;一层遍历相邻路由器列表 O ( m ) O(m) O(m) 通知所有相邻路由器当前路由器故障;一层相邻路由器遍历自己的路由表 O ( n ) O(n) O(n) 将下一跳为故障路由器的表项的跳数设为16。
- 四层循环嵌套和其中不同循环叠加,总的时间复杂度为 O ( m ( m + n + m n ) ) = O ( m 2 + m n + m 2 n ) ≈ O ( m 2 n ) O(m(m+n+mn))=O(m^2+mn+m^2n)\approx O(m^2n) O(m(m+n+mn))=O(m2+mn+m2n)≈O(m2n)。
if (ripgui.checkRoutersExist(routerNames)) {
for (String routerName : routerNames) { // 第一层循环
Router findRouter = networkTopology.findRouter(routerName.trim());
findRouter.failure(); // 路由器存在,则设置为故障状态
}
} else return;
/**
* 路由器故障
*/
public void failure() {
stopScheduler();
for (RouteTableEntry entry : new ArrayList<>(routingTable.values())) { // 第二层循环
entry.setHops(16); // 使用16表示不可达
}
notifyNeighborsForFailure(); // 通知所有相邻路由器当前路由器故障
} // end failure()
/**
* 通知所有相邻路由器当前路由器故障
*/
private void notifyNeighborsForFailure() {
for (Router neighbor : neighbors) { // 第三层循环
neighbor.removeNeighbor(this);
neighbor.updateRoutingTableForFailedRouter(this);
}
} // end notifyNeighborsForFailure()
/**
* 当前路由器故障时,更新路由表
*
* @param failedRouter 故障路由器
*/
public void updateRoutingTableForFailedRouter(Router failedRouter) {
for (RouteTableEntry entry : new ArrayList<>(routingTable.values())) { // 第四层循环
if (entry.getNextHop().equals(failedRouter)) {
entry.setHops(16); // 使用16表示不可达
}
}
} // end updateRoutingTableForFailedRouter()