java接口限流详解

目录

  • 1.简介
    • 1.1.为什么需要限流?
    • 1.2.限流和熔断有什么区别?
    • 1.3.限流和削峰有什么区别?
    • 1.4 缓存,降级,限流简介
  • 2.应用级限流
    • 2.1 控制并发数量
    • 2.2 控制访问速率
      • 2.2.1 令牌桶算法
      • 2.2.2 漏桶算法
  • 3.分布式限流
  • 4.交流群

1.简介

接口限流是对某一时间窗口内的请求数进行限制,以保持系统的可用性和稳定性,防止因流量暴增而导致的系统运行缓慢或宕机。此外,接口限流也可以通过限制每个用户或每个接口调用的频率和并发数,来控制对服务资源的访问。

1.1.为什么需要限流?

大量正常用户高频访问导致服务器宕机
恶意用户高频访问导致服务器宕机
网页爬虫 ,对于这些情况我们需要对用户的访问进行限流访问

1.2.限流和熔断有什么区别?

限流发生在流量进来之前,超过的流量进行限制。

熔断是一种应对故障的机制,发生在流量进来之后,如果系统发生故障或者异常,熔断会自动切断请求,防止故障进一步扩展,导致服务雪崩。

1.3.限流和削峰有什么区别?

削峰是对流量的平滑处理,通过缓慢地增加请求的处理速率来避免系统瞬时过载。

削峰大概就是水库,把流量储存起来,慢慢流,限流大概就是闸口,拒绝超出的流量。

1.4 缓存,降级,限流简介

  • 缓存 缓存的目的是提升系统访问速度和增大系统处理容量
  • 降级 降级是当服务出现问题或者影响到核心流程时,需要暂时屏蔽掉,待高峰或者问题解决后再打开
  • 限流 限流的目的是通过对并发访问/请求进行限速,或者对一个时间窗口内的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务、排队或等待、降级等处理

2.应用级限流

2.1 控制并发数量

在Java中,可以使用信号量(Semaphore)机制来控制并发数量。信号量是一个计数器,用于限制对共享资源的访问。以下是一个使用信号量机制控制并发数量的示例:

import java.util.concurrent.Semaphore;  public class ConcurrencyControlExample {  private static final int MAX_CONCURRENT_THREADS = 5; // 最大并发线程数  private static Semaphore semaphore = new Semaphore(MAX_CONCURRENT_THREADS);  public static void main(String[] args) {  for (int i = 0; i < 10; i++) {  new Thread(new WorkerThread("" + i)).start();  }  }  static class WorkerThread implements Runnable {  private String command;  public WorkerThread(String s) {  this.command = s;  }  @Override  public void run() {  try {  // 获取信号量,如果信号量为0,则当前线程需要等待  semaphore.acquire();  // 执行任务  System.out.println(Thread.currentThread().getName() + "开始处理:" + command);  processCommand();  System.out.println(Thread.currentThread().getName() + "结束处理:" + command);  } catch (InterruptedException e) {  e.printStackTrace();  } finally {  // 释放信号量,允许其他线程获取信号量并执行任务  semaphore.release();  }  }  private void processCommand() {  try {  Thread.sleep(2000); // 模拟耗时任务  } catch (InterruptedException e) {  e.printStackTrace();  }  }  }  
}

2.2 控制访问速率

在工程实践中,常见的是使用令牌桶算法来实现这种模式,常用的限流算法有两种:漏桶算法和令牌桶算法。

2.2.1 令牌桶算法

如图所示,令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务,令牌桶算法通过发放令牌,根据令牌的rate频率做请求频率限制,容量限制等。
在这里插入图片描述

在Wikipedia上,令牌桶算法是这么描述的:

1、每过1/r秒桶中增加一个令牌。
2、桶中最多存放b个令牌,如果桶满了,新放入的令牌会被丢弃。
3、当一个n字节的数据包到达时,消耗n个令牌,然后发送该数据包。
4、如果桶中可用令牌小于n,则该数据包将被缓存或丢弃。
令牌桶控制的是一个时间窗口内通过的数据量,在API层面我们常说的QPS、TPS,正好是一个时间窗口内的请求量或者事务量,只不过时间窗口限定在1s罢了。以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。令牌桶的另外一个好处是可以方便的改变速度,一旦需要提高速率,则按需提高放入桶中的令牌的速率。
在我们的工程实践中,通常使用Google开源工具包Guava提供的限流工具类RateLimiter来实现控制速率,该类基于令牌桶算法来完成限流,非常易于使用,而且非常高效。如我们不希望每秒的任务提交超过1个

  public static void main(String[] args) {String start = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());RateLimiter limiter = RateLimiter.create(1.0); // 这里的1表示每秒允许处理的量为1个for (int i = 1; i <= 10; i++) {double waitTime = limiter.acquire(i);// 请求RateLimiter, 超过permits会被阻塞System.out.println("cutTime=" + System.currentTimeMillis() + " call execute:" + i + " waitTime:" + waitTime);}String end = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());System.out.println("start time:" + start);System.out.println("end time:" + end);}

首先通过RateLimiter.create(1.0);创建一个限流器,参数代表每秒生成的令牌数,通过limiter.acquire(i);来以阻塞的方式获取令牌,令牌桶算法允许一定程度的突发(允许消费未来的令牌),所以可以一次性消费i个令牌;当然也可以通过tryAcquire(int permits, long timeout, TimeUnit unit)来设置等待超时时间的方式获取令牌,如果超timeout为0,则代表非阻塞,获取不到立即返回,支持阻塞或可超时的令牌消费。

从输出来看,RateLimiter支持预消费,比如在acquire(5)时,等待时间是4秒,是上一个获取令牌时预消费了3个两排,固需要等待3*1秒,然后又预消费了5个令牌,以此类推。

ateLimiter通过限制后面请求的等待时间,来支持一定程度的突发请求(预消费),在使用过程中需要注意这一点,Guava有两种限流模式,一种为稳定模式(SmoothBursty:令牌生成速度恒定,平滑突发限流),一种为渐进模式(SmoothWarmingUp:令牌生成速度缓慢提升直到维持在一个稳定值,平滑预热限流) 两种模式实现思路类似,主要区别在等待时间的计算上。

  • SmoothBursty 模式:RateLimiter limiter = RateLimiter.create(5);
    RateLimiter.create(5)表示桶容量为5且每秒新增5个令牌,即每隔200毫秒新增一个令牌;limiter.acquire()表示消费一个令牌,如果当前桶中有足够令牌则成功(返回值为0),如果桶中没有令牌则暂停一段时间,比如发令牌间隔是200毫秒,则等待200毫秒后再去消费令牌,这种实现将突发请求速率平均为了固定请求速率。
  • SmoothWarmingUp模式:RateLimiter limiter = RateLimiter.create(5,1000, TimeUnit.MILLISECONDS);
    创建方式:RateLimiter.create(doublepermitsPerSecond, long warmupPeriod, TimeUnit unit),permitsPerSecond表示每秒新增的令牌数,warmupPeriod表示在从冷启动速率过渡到平均速率的时间间隔。速率是梯形上升速率的,也就是说冷启动时会以一个比较大的速率慢慢到平均速率;然后趋于平均速率(梯形下降到平均速率)。可以通过调节warmupPeriod参数实现一开始就是平滑固定速率。

注:RateLimiter控制的是速率,Samephore控制的是并发量。RateLimiter的原理就是令牌桶,它主要由许可发出的速率来定义,如果没有额外的配置,许可证将按每秒许可证规定的固定速度分配,许可将被平滑地分发,若请求超过permitsPerSecond则RateLimiter按照每秒 1/permitsPerSecond 的速率释放许可。注意:RateLimiter适用于单体应用,且RateLimiter不保证公平性访问。

使用上述方式使用RateLimiter的方式不够优雅,自定义注解+AOP的方式实现(适用于单体应用),详细见下面代码:

自定义注解:

import java.lang.annotation.*;/*** 自定义注解可以不包含属性,成为一个标识注解*/
@Inherited
@Documented
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimitAspect {}

自定义切面类

import com.google.common.util.concurrent.RateLimiter;
import com.test.cn.springbootdemo.util.ResultUtil;
import net.sf.json.JSONObject;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;@Component
@Scope
@Aspect
public class RateLimitAop {@Autowiredprivate HttpServletResponse response;private RateLimiter rateLimiter = RateLimiter.create(5.0); //比如说,我这里设置"并发数"为5@Pointcut("@annotation(com.test.cn.springbootdemo.aspect.RateLimitAspect)")public void serviceLimit() {}@Around("serviceLimit()")public Object around(ProceedingJoinPoint joinPoint) {Boolean flag = rateLimiter.tryAcquire();Object obj = null;try {if (flag) {obj = joinPoint.proceed();//这个方法用于执行原来的方法或继续原来的控制流程。}else{String result = JSONObject.fromObject(ResultUtil.success1(100, "failure")).toString();output(response, result);}} catch (Throwable e) {e.printStackTrace();}System.out.println("flag=" + flag + ",obj=" + obj);return obj;}public void output(HttpServletResponse response, String msg) throws IOException {response.setContentType("application/json;charset=UTF-8");ServletOutputStream outputStream = null;try {outputStream = response.getOutputStream();    //这行代码获取了与当前HTTP响应关联的输出流,并将其赋值给outputStream变量。outputStream.write(msg.getBytes("UTF-8"));//这部分将转换后的字节数组写入到之前获取的输出流中,这意味着数据将被发送到客户端。} catch (IOException e) {e.printStackTrace();} finally {outputStream.flush();outputStream.close();}}
}

测试controller

import com.test.cn.springbootdemo.aspect.RateLimitAspect;
import com.test.cn.springbootdemo.util.ResultUtil;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;@Controller
public class TestController {@ResponseBody@RateLimitAspect@RequestMapping("/test")public String test(){return ResultUtil.success1(1001, "success").toString();}

2.2.2 漏桶算法

漏桶算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水,当水流入速度过大会直接溢出,可以看出漏桶算法能强行限制数据的传输速率。
在这里插入图片描述
对于很多应用场景来说,除了要求能够限制数据的平均传输速率外,还要求允许某种程度的突发传输。这时候漏桶算法可能就不合适了,令牌桶算法更为适合。
算法实现:

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;// 漏桶 限流
@Slf4j
public class LeakBucketLimiter {// 计算的起始时间private static long lastOutTime = System.currentTimeMillis();// 流出速率 每秒 2 次private static int leakRate = 2;// 桶的容量private static int capacity = 2;//剩余的水量private static AtomicInteger water = new AtomicInteger(0);//返回值说明:// false 没有被限制到// true 被限流public static synchronized boolean isLimit(long taskId, int turn) {// 如果是空桶,就当前时间作为漏出的时间if (water.get() == 0) {lastOutTime = System.currentTimeMillis();water.addAndGet(1);return false;}// 执行漏水int waterLeaked = ((int) ((System.currentTimeMillis() - lastOutTime) / 1000)) * leakRate;// 计算剩余水量int waterLeft = water.get() - waterLeaked;water.set(Math.max(0, waterLeft));// 重新更新leakTimeStamplastOutTime = System.currentTimeMillis();// 尝试加水,并且水还未满 ,放行if ((water.get()) < capacity) {water.addAndGet(1);return false;} else {// 水满,拒绝加水, 限流return true;}}//线程池,用于多线程模拟测试private ExecutorService pool = Executors.newFixedThreadPool(10);@Testpublic void testLimit() {// 被限制的次数AtomicInteger limited = new AtomicInteger(0);// 线程数final int threads = 2;// 每条线程的执行轮数final int turns = 20;// 线程同步器CountDownLatch countDownLatch = new CountDownLatch(threads);long start = System.currentTimeMillis();for (int i = 0; i < threads; i++) {pool.submit(() -> {try {for (int j = 0; j < turns; j++) {long taskId = Thread.currentThread().getId();boolean intercepted = isLimit(taskId, j);if (intercepted) {// 被限制的次数累积limited.getAndIncrement();}Thread.sleep(200);}} catch (Exception e) {e.printStackTrace();}//等待所有线程结束countDownLatch.countDown();});}try {countDownLatch.await();} catch (InterruptedException e) {e.printStackTrace();}float time = (System.currentTimeMillis() - start) / 1000F;//输出统计结果log.info("限制的次数为:" + limited.get() + ",通过的次数为:" + (threads * turns - limited.get()));log.info("限制的比例为:" + (float) limited.get() / (float) (threads * turns));log.info("运行的时长为:" + time);}
}

3.分布式限流

分布式限流
自定义注解+拦截器+Redis实现限流 (单体和分布式均适用,全局限流)
自定义注解:

@Inherited
@Documented
@Target({ElementType.FIELD,ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessLimit {int limit() default 5;  int sec() default 5;
}

拦截器:

public class AccessLimitInterceptor implements HandlerInterceptor {@Autowiredprivate RedisTemplate<String, Integer> redisTemplate;  //使用RedisTemplate操作redis@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {if (handler instanceof HandlerMethod) {HandlerMethod handlerMethod = (HandlerMethod) handler;Method method = handlerMethod.getMethod();if (!method.isAnnotationPresent(AccessLimit.class)) {return true;}AccessLimit accessLimit = method.getAnnotation(AccessLimit.class);if (accessLimit == null) {return true;}int limit = accessLimit.limit();int sec = accessLimit.sec();String key = IPUtil.getIpAddr(request) + request.getRequestURI();Integer maxLimit = redisTemplate.opsForValue().get(key);if (maxLimit == null) {redisTemplate.opsForValue().set(key, 1, sec, TimeUnit.SECONDS);  //set时一定要加过期时间} else if (maxLimit < limit) {redisTemplate.opsForValue().set(key, maxLimit + 1, sec, TimeUnit.SECONDS);} else {output(response, "请求太频繁!");return false;}}return true;}public void output(HttpServletResponse response, String msg) throws IOException {response.setContentType("application/json;charset=UTF-8");ServletOutputStream outputStream = null;try {outputStream = response.getOutputStream();outputStream.write(msg.getBytes("UTF-8"));} catch (IOException e) {e.printStackTrace();} finally {outputStream.flush();outputStream.close();}}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {}
}

controller:

@Controller
@RequestMapping("/activity")
public class AopController {@ResponseBody@RequestMapping("/seckill")@AccessLimit(limit = 4,sec = 10)  //加上自定义注解即可public String test (HttpServletRequest request,@RequestParam(value = "username",required = false) String userName){//TODO somethings……return   "hello world !";}
}

配置文件:

/*springmvc的配置文件中加入自定义拦截器*/
<mvc:interceptors><mvc:interceptor><mvc:mapping path="/**"/><bean class="com.pptv.activityapi.controller.pointsmall.AccessLimitInterceptor"/></mvc:interceptor>
</mvc:interceptors>

4.交流群

后端专属技术群
我建了一个后端专属技术群,欢迎从事编程开发、技术招聘HR进群,也欢迎大家分享自己公司的内推信息,相互帮助,一起进步!
文明发言,以交流技术、职位内推、行业探讨为主

图片

资源共享,共同进步

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/577148.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

漏洞复现-红帆OA iorepsavexml.aspx文件上传漏洞(附漏洞检测脚本)

免责声明 文章中涉及的漏洞均已修复&#xff0c;敏感信息均已做打码处理&#xff0c;文章仅做经验分享用途&#xff0c;切勿当真&#xff0c;未授权的攻击属于非法行为&#xff01;文章中敏感信息均已做多层打马处理。传播、利用本文章所提供的信息而造成的任何直接或者间接的…

浅谈师范双非普本工科专业的秋招历程

本人普通师范院校通信工程专业&#xff0c;于秋招历程之中四处碰壁&#xff0c;迫于家庭等各种因素考虑&#xff0c;最终选择移动的偏远县城岗位的OFFER&#xff01;本人秋招历程之中&#xff0c;屡屡碰壁&#xff0c;也算得上“收获满满”&#xff01;我简单给各位浅谈一下我的…

气动冷凝水回收泵机械浮球泵的特点工作原理介绍 不需要电源

​ 1&#xff1a;气动凝水回收泵机械式介绍 气动冷凝水回收泵是一种设计用于不使用电力来泵送冷凝液、油和其他高温液体等的设备。它无需维护&#xff0c;能将大量凝结水和其它液体从低位、低压或真空场所泵送到高处及高压区域。与传统电泵相比&#xff0c;气动冷凝水回收泵可…

八股文打卡day10——计算机网络(10)

面试题&#xff1a;HTTP1.1和HTTP2.0的区别&#xff1f; 我的回答&#xff1a; 1.多路复用&#xff1a;HTTP1.1每次请求响应一次都得建立一次连接&#xff0c;HTTP1.1引入了持久连接Connection&#xff1a;Keep-Alive&#xff0c;可以建立一次连接&#xff0c;进行多次请求响…

cmake常见操作记录(持续更新)

cmake常见操作记录 通用1. 指定生成目录2. 指定宏3. 设置编译类型&#xff08;Debug&#xff0c;Release&#xff09; linuxwindwos1. windows下设置编译位数 通用 1. 指定生成目录 cmake -B build .2. 指定宏 SET(_COMPACT_ false CACHE BOOL "whether to compact.&qu…

放大电路的静态分析和动态分析例题

(一) &#xff08;二&#xff09; (三) &#xff08;四&#xff09; &#xff08;五&#xff09;

QT中,socket通讯要考虑哪些问题?(面试题)

在Qt中进行Socket通信时&#xff0c;需要考虑以下几个问题&#xff1a; 连接建立和断开&#xff1a;确保在客户端和服务器端之间能够正确地建立和断开连接&#xff0c;以便进行正常的数据传输。 数据传输格式&#xff1a;确定数据传输的格式&#xff0c;包括数据的编码方式、数…

Spring Boot学习随笔- 第一个Thymeleaf应用(基础语法th:,request、session作用域取值)

学习视频&#xff1a;【编程不良人】2021年SpringBoot最新最全教程 第十五章、Thymeleaf Thymeleaf是一种现代化的服务器端Java模板引擎&#xff0c;专门用于Web和独立环境。Thymeleaf在有网络和无网络的环境下皆可运行&#xff0c;即可以让美工在浏览器查看页面的静态效果&am…

34. 在排序数组中查找元素的第一个和最后一个位置

34. 在排序数组中查找元素的第一个和最后一个位置 题目链接&#xff1a;34. 在排序数组中查找元素的第一个和最后一个位置 代码如下&#xff1a; class Solution { public:vector<int> searchRange(vector<int>& nums, int target) {vector<int> range…

下载MySQL Connector/C++

MySQL :: Download Connector/C

MySQL 一个线程(或事务)在更新表时,另一个线程能否读取这个表

在MySQL中&#xff0c;一个线程&#xff08;或事务&#xff09;在更新表时&#xff0c;另一个线程能否读取这个表主要取决于两个因素&#xff1a;使用的事务隔离级别和锁的类型。MySQL默认使用行级锁&#xff0c;这意味着锁定的范围相对较小&#xff0c;通常不会锁定整个表。下…

智能优化算法应用:基于向量加权平均算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于向量加权平均算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于向量加权平均算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.向量加权平均算法4.实验参数设定…

《C++避坑神器·二十五》简单搞懂json文件的读写之遍历json文件读写

json.hpp库放在文章末尾 1、遍历json文件读写 &#xff08;1&#xff09;插入新键值对到json之情形1 原来json文件如下所示&#xff1a; {"Connection": {"IpAddress": "192.168.20.1","Rock": 0,"Solt": 1}, "Data…

Springboot的测试类出现java.lang.Exception: No runnable methods的解决方法(万能)

目录 1. 问题所示2. 原理分析3. 解决方法4. 补充1. 问题所示 使用Springboot测试的时候出现如下问题: 十二月 24, 2023 6:33:12 下午 org.junit.vintage.engine.discovery.TestCl

opencv入门到精通——形态学转换

目录 目标 理论 1. 侵蚀 2. 扩张 3. 开运算 4. 闭运算 5. 形态学梯度 6. 顶帽 7. 黑帽 结构元素 目标 在这一章当中&#xff0c; 我们将学习不同的形态学操作&#xff0c;例如侵蚀&#xff0c;膨胀&#xff0c;开运算&#xff0c;闭运算等。我们将看到不同的功能&…

前端---html 的介绍

1. 网页效果图 --CSDN 2. html的定义 HTML 的全称为&#xff1a;HyperText Mark-up Language, 指的是超文本标记语言。 标记&#xff1a;就是标签, <标签名称> </标签名称>, 比如: <html></html>、<h1></h1> 等&#xff0c;标签大多数都是…

【数据结构】查找与排序

要查询信息&#xff0c;涉及两个问题&#xff1a; 在哪里查&#xff1f;——查找表 怎么查&#xff1f;——查找方法 一.查找 1.查找表的定义&#xff1a; 查找表是由同类型的数据元素构成的集合 2.对查找表的基本操作&#xff1a; 1&#xff09;查询某个数据元素是否在查…

MY FILE SERVER: 1

下载地址 https://download.vulnhub.com/myfileserver/My_file_server_1.ova 首先我们需要发现ip 我的kali是59.162所以167就是靶机的 然后我们拿nmap扫一下端口 nmap -sV -p- 192.168.59.167 扫完发现有七个端口开放 按照习惯先看80 没看到有啥有用信息,用nikto扫一下 nik…

20231225使用荣耀HONOR70的手机通过BLE调试助手接收BLE广播

20231225使用荣耀HONOR70的手机通过BLE调试助手接收BLE广播 2023/12/25 16:33 结论&#xff1a;我买的 荣耀HONOR70的手机&#xff08;CPU型号&#xff1a;骁龙778G&#xff09; 的蓝牙BLE的连接速度明显弱于 Redmi Note12Pro 5G&#xff08;CPU型号&#xff1a;天玑1080&#…

【网安 | 网络协议】ARP协议(地址解析协议)

前言 在使用nmap时可以使用-PR指令&#xff0c;通过发送ARP请求包进行主机存活探测。 那么这一过程的原理是什么呢&#xff1f; 在了解什么是ARP协议后&#xff0c;问题就迎刃而解了。 概念 地址解析协议&#xff0c;即ARP&#xff08;Address Resolution Protocol&#xf…