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 "";
        }
    }
}
相关推荐
老鼠只爱大米5 分钟前
LeetCode经典算法面试题 #114:二叉树展开为链表(递归、迭代、Morris等多种实现方案详细解析)
算法·leetcode·二叉树·原地算法·morris遍历·二叉树展开
是萧萧吖5 分钟前
每日一练——有效的括号
java·开发语言·javascript
程序员欣宸10 分钟前
LangChain4j实战之十六:RAG (检索增强生成),Naive RAG
java·人工智能·ai·langchain4j
Ivanqhz11 分钟前
现代异构高性能计算(HPC)集群节点架构
开发语言·人工智能·后端·算法·架构·云计算·边缘计算
qq_3363139324 分钟前
javaweb-Maven
java·maven
Sayuanni%325 分钟前
数据结构_Map和Set
java·数据结构
Demon_Hao25 分钟前
Spring Boot开启虚拟线程ScopedValue上下文传递
java·spring boot·后端
野犬寒鸦33 分钟前
从零起步学习并发编程 || 第三章:JMM(Java内存模型)详解及对比剖析
java·服务器·开发语言·分布式·后端·学习·spring
一勺菠萝丶37 分钟前
Jenkins 构建日志出现 `[INFO]` 乱码?原因与完整解决方案(小白必看)
java·servlet·jenkins
HyperAI超神经37 分钟前
覆盖天体物理/地球科学/流变学/声学等19种场景,Polymathic AI构建1.3B模型实现精确连续介质仿真
人工智能·深度学习·学习·算法·机器学习·ai编程·vllm