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 "";
        }
    }
}
相关推荐
旺仔小拳头..16 分钟前
Maven相关
java·maven
要一起看日出17 分钟前
数据结构---------红黑树
java·数据结构·红黑树
被AI抢饭碗的人21 分钟前
算法题(246):负环(bellman_ford算法)
算法
程序定小飞26 分钟前
基于springboot的民宿在线预定平台开发与设计
java·开发语言·spring boot·后端·spring
FREE技术39 分钟前
山区农产品售卖系统
java·spring boot
大数据张老师1 小时前
数据结构——折半查找
数据结构·算法·查找·折半查找
星光一影1 小时前
Java医院管理系统HIS源码带小程序和安装教程
java·开发语言·小程序
YA3332 小时前
java设计模式七、代理模式
java·设计模式·代理模式
熬了夜的程序员2 小时前
【LeetCode】87. 扰乱字符串
算法·leetcode·职场和发展·排序算法
helloworddm2 小时前
Orleans 自定义二进制协议在 TCP 上层实现的完整过程
java·网络协议·tcp/ip