ARP 缓存与报文转发模拟

题目:ARP 缓存与报文转发模拟

背景介绍

在计算机网络中,当一台设备(如路由器)需要向另一个在同一局域网内的设备发送数据包时,它需要知道对方的物理地址(MAC 地址)。地址解析协议 (ARP) 就是用来根据目标设备的 IP 地址查询其 MAC 地址的。

为了提高效率,设备会维护一张 ARP 表(也称 ARP 缓存),记录近期通信过的 IP 与 MAC 地址的映射关系。当需要发送数据包时:

  1. 首先查找 ARP 表。如果找到,就能直接封装并发送数据包。
  2. 如果找不到,设备就不能立即发送,需要先通过 ARP 请求广播来获取 MAC 地址。在此期间,待发送的数据包会被缓存起来。
  3. 一旦通过后续操作(如手动配置)学习到了目标 MAC 地址,设备就会立即将缓存的报文补发出去。

你的任务就是实现这套包含 LRU 缓存淘汰策略报文缓存机制的系统。

核心组件

  1. ARP 表 (ARP Table):

    • 功能: 存储 IP 地址到 MAC 地址的映射。

    • 容量: 最大条目数为 arpCap

    • 淘汰策略: 当表满时,采用 LRU (Least Recently Used, 最久未被命中) 策略。即,当需要添加新条目而表已满时,移除那个最久没有被"命中"过的条目。

    • 命中 (Access): 以下操作均视为一次"命中",会将被操作的 IP 地址更新为"最近使用"的:

      • update 一个已存在的 IP。
      • update 一个新添加的 IP。
      • forward 时成功查找到一个 IP。
  2. 报文缓存 (Packet Cache):

    • 功能: 暂存因 ARP 表中没有对应条目而无法立即发送的报文。

    • 容量限制:

      • 全局上限 cachedPktCap: 设备最多能缓存的报文总数。
      • 单 IP 上限 perIpCap: 同一个目的 IP 最多能缓存的报文数。

功能要求

你需要实现一个 ArpSys 类,支持以下操作:

ArpSys(int arpCap, int cachedPktCap, int perIpCap)
  • 功能: 初始化系统,设置三个容量上限。
update(int ip, String macAddr)
  • 功能: 配置或更新一条 ARP 表项。

  • 规则:

    • 如果 IP 已存在: 直接用新的 macAddr 覆盖旧的。此操作会更新该 IP 为最近使用

    • 如果 IP 不存在:

      • 若 ARP 表未满: 直接添加新表项。
      • 若 ARP 表已满: 淘汰最久未被使用的条目,然后添加新表项。
      • 添加或替换成功后,此操作也会更新该 IP 为最近使用
    • 补发报文: update 操作完成后,检查报文缓存中是否有发往该 ip 的报文。如果有,将这些报文pktId 升序 返回,并从缓存中清空

  • 返回值: 返回被补发的报文 ID 列表 List<Integer>;如果没有报文需要补发,则返回空列表 []

forward(int ip, int pktId)
  • 功能: 模拟一次报文转发请求。

  • 规则:

    • ARP 命中: 如果在 ARP 表中找到ip 对应的 macAddr

      • 将该表项更新为最近使用
      • 返回对应的 macAddr 字符串。
    • ARP 未命中: 如果在 ARP 表中找不到 ip

      • 尝试将报文 pktId 存入报文缓存。
      • 缓存检查: 只有在全局缓存数量该 IP 的缓存数量都未达到上限时,才能缓存成功。
      • 如果任一缓存已满,则缓存失败,该报文被丢弃。
      • 无论缓存成功还是失败,都返回空字符串 ""

解答要求与输入输出格式

  • 时间/内存限制: 1000ms / 256MB。

  • 输入:

    • arpCap: 1 <= arpCap <= 100
    • cachedPktCap: 1 <= perIpCap <= cachedPktCap <= 100
    • ip: 1 <= ip <= 2^31 - 1
    • pktId: 0 <= pktId <= 10000
    • macAddr: 固定长度为 17 的十六进制字符串。
  • 输出:

    • 根据每个函数的原型要求返回相应的值。评测框架会自动处理 null (针对构造函数) 和其他返回值的输出。

样例 1 解读

初始化: ArpSys(3, 3, 2) -> ARP 容量=3, 全局缓存=3, 单IP缓存=2。

# 调用 解释与系统状态变化 返回值
1 update(123, "...") ARP 表为空,添加 123ARP (LRU: head->tail): [123] []
2 forward(456, 23) ARP 未命中。缓存报文 23。Cache: {456: [23]}Total Cached: 1。 ""
3 forward(456, 0) ARP 未命中。缓存报文 0。Cache: {456: [23, 0]}Total Cached: 2。 ""
4 forward(456, 8) ARP 未命中。IP 456 的缓存已达上限(2),缓存失败。Cache: {456: [23, 0]}Total Cached: 2。 ""
5 update(456, "...") ARP 表未满,添加 456ARP: [123, 456]。补发 IP 456 的报文 [23, 0],排序后为 [0, 23]Cache: {}Total Cached: 0。 [0, 23]
6 update(123, "...") IP 123 已存在,覆盖 MAC 地址。123 变为最近使用。ARP: [456, 123] []
7 forward(123, 5) ARP 命中。123 变为最近使用。ARP: [456, 123] "CD-EF-AB-AA-AB-AB"
java 复制代码
import java.util.*;

/**
 * 模拟 ARP 表和报文缓存的系统。
 *
 * 核心数据结构:
 * - arpTable: 使用 LinkedHashMap 实现,自带 LRU 淘汰策略。
 * - packetCache: 使用 HashMap 存储 IP 到其待发报文列表的映射。
 * - totalCachedPacketCount: 整数,用于 O(1) 检查全局缓存是否已满。
 */
public class ArpSys {
    // --- 成员变量 ---
    private final int arpCapacity;           // ARP 表的最大容量
    private final int globalCacheCapacity;   // 总报文缓存的上限
    private final int perIpCacheCapacity;    // 单个 IP 缓存报文的上限

    // ARP 表: Key=IP地址, Value=MAC地址.
    // 使用 LinkedHashMap 实现 LRU (最久未使用) 淘汰策略。
    private final Map<Integer, String> arpTable;

    // 报文缓存: Key=IP地址, Value=等待补发的报文ID列表
    private final Map<Integer, List<Integer>> packetCache;

    // 当前已缓存的报文总数,用于快速检查全局缓存容量
    private int totalCachedPacketCount;

    /**
     * 构造函数 - 初始化 ARP 系统
     * @param arpCap          ARP 表的最大容量
     * @param cachedPktCap    设备能缓存的总报文数上限
     * @param perIpCap        单个 IP 能缓存的报文数上限
     */
    public ArpSys(int arpCap, int cachedPktCap, int perIpCap) {
        this.arpCapacity = arpCap;
        this.globalCacheCapacity = cachedPktCap;
        this.perIpCacheCapacity = perIpCap;
        this.totalCachedPacketCount = 0;
        this.packetCache = new HashMap<>();

        // 初始化支持 LRU 的 LinkedHashMap。
        // 构造函数的第三个参数 accessOrder=true 是关键,它使得 get/put 操作
        // 都会将被访问的条目移动到内部链表的末尾(表示最近使用)。
        this.arpTable = new LinkedHashMap<Integer, String>(arpCap, 0.75f, true) {
            /**
             * 重写此方法,使得在 put 操作后,如果 Map 的大小超过了设定的容量,
             * LinkedHashMap 会自动移除最旧的条目(链表头部的条目)。
             */
            @Override
            protected boolean removeEldestEntry(Map.Entry<Integer, String> eldest) {
                return this.size() > arpCapacity;
            }
        };
    }

    /**
     * 配置或更新一条 ARP 表项。
     * @param ip      IP 地址
     * @param macAddr MAC 地址
     * @return 如果此 IP 有缓存的报文,返回补发的报文 ID 列表(升序);否则返回空列表。
     */
    public List<Integer> update(int ip, String macAddr) {
        // 1. 更新或插入 ARP 表项。
        //    由于 accessOrder=true,此 put 操作会将 ip 标记为最近使用。
        //    如果 put 后 size() > arpCapacity,重写的 removeEldestEntry 会自动触发,移除最旧条目。
        arpTable.put(ip, macAddr);

        // 2. 检查此 IP 是否有缓存的报文需要补发。
        //    使用 getOrDefault 返回一个空列表,避免 null 检查。
        List<Integer> packetsToSend = packetCache.getOrDefault(ip, Collections.emptyList());

        if (!packetsToSend.isEmpty()) {
            // 如果有报文需要补发:
            List<Integer> sortedPackets = new ArrayList<>(packetsToSend);
            // a. 按 pktId 升序排序
            Collections.sort(sortedPackets);

            // b. 更新缓存的总数
            this.totalCachedPacketCount -= packetsToSend.size();
            // c. 从缓存中移除此 IP 的条目
            packetCache.remove(ip);

            // d. 返回补发的报文列表
            return sortedPackets;
        } else {
            // 此 IP 没有缓存的报文,返回空列表
            return Collections.emptyList();
        }
    }

    /**
     * 模拟报文转发。
     * @param ip    目的 IP 地址
     * @param pktId 报文 ID
     * @return 如果 ARP 表中有对应表项,返回 MAC 地址;否则返回空字符串 ""。
     */
    public String forward(int ip, int pktId) {
        // 1. 在 ARP 表中查找 IP
        if (arpTable.containsKey(ip)) {
            // ARP 命中!
            // get 操作会自动将此条目更新为最近使用,满足 LRU 要求
            return arpTable.get(ip);
        } else {
            // 2. ARP 未命中,尝试缓存报文
            
            // a. 检查全局缓存是否已满
            if (this.totalCachedPacketCount >= this.globalCacheCapacity) {
                // 全局缓存满,缓存失败,丢弃报文
                return "";
            }

            // b. 获取或创建该 IP 的缓存列表
            List<Integer> ipPacketList = this.packetCache.computeIfAbsent(ip, k -> new ArrayList<>());

            // c. 检查单个 IP 的缓存是否已满
            if (ipPacketList.size() >= this.perIpCacheCapacity) {
                // 单个 IP 的缓存满,缓存失败,丢弃报文
                return "";
            }

            // d. 缓存成功
            ipPacketList.add(pktId);
            this.totalCachedPacketCount++;
            return "";
        }
    }
}
相关推荐
永日4567029 分钟前
学习日记-spring-day45-7.10
java·学习·spring
学不动CV了1 小时前
C语言32个关键字
c语言·开发语言·arm开发·单片机·算法
小屁孩大帅-杨一凡2 小时前
如何解决ThreadLocal内存泄漏问题?
java·开发语言·jvm·算法
学习3人组2 小时前
在 IntelliJ IDEA 系列中phpstorm2025设置中文界面
java·ide·intellij-idea
Y1nhl3 小时前
力扣_二叉树的BFS_python版本
python·算法·leetcode·职场和发展·宽度优先
cainiao0806054 小时前
Java 大视界:基于 Java 的大数据可视化在智慧城市能源消耗动态监测与优化决策中的应用(2025 实战全景)
java
长风破浪会有时呀4 小时前
记一次接口优化历程 CountDownLatch
java
云朵大王5 小时前
SQL 视图与事务知识点详解及练习题
java·大数据·数据库
向阳逐梦5 小时前
PID控制算法理论学习基础——单级PID控制
人工智能·算法
2zcode5 小时前
基于Matlab多特征融合的可视化指纹识别系统
人工智能·算法·matlab