Java定时任务大盘点:发工资也能“指日可待”
yuyutoo 2024-12-10 21:51 2 浏览 0 评论
作者:京东保险 孙昊宇
引子:“指日可待”
让我们先从一个成语开始,“指日可待”。没错,我说的就是定时任务。
“指日可待”: 为任务指定好日程,就可以安心等待任务执行。
在实际场景中,我们往往需要在特定时间做某件事情,或以某个时间间隔重复某件事情,如定期备份数据、定时取消超时订单等。所有和时间有关的事情,都需要借助定时任务来完成。
定时任务可分为两种:本地定时任务、 分布式定时任务。
本地定时任务,即单机定时任务,适合做那些需要每台机器都执行的任务,如刷新每台机器的本地缓存;分布式定时任务则以一个分布式集群为单位执行任务,适用于支持在分布式场景下任务的高可用。
今天让我们看看Java中的本地定时任务,本文将介绍如何使用Timer、ScheduledExecutorService和@Scheduled三种方式实现本地定时任务。
读完本文,你会发现:原来每月最后一个工作日发工资,也可以用定时任务实现!
一、Timer
Timer,即java.util.Timer,是来自Java 1.3的古老定时器。
要使用Timer,要先创建一个TimerTask,作为Timer要执行的任务:
// 创建Timer对象
Timer timer = new Timer();
// 创建TimerTask:task1
TimerTask task1 = new TimerTask() {
@Override
public void run() {
System.out.println("task1开始执行:" + new Date().getSeconds());
}
};
有了Timer和TimerTask,就可以安排任务执行。让我们简单了解下Timer的用法:
(1)单次执行
使用Timer.schedule方法,只需传入TimerTask和延迟时间,即可让任务在指定的延迟时间后执行一次。也可以传入Date,让任务在指定的时刻执行:
// 5s后执行
timer.schedule(task1, 5000);
// 指定时刻。如果传入当前时刻,立即执行
timer.schedule(task1, new Date());
如果直接运行以上代码,会出现“Task already scheduled or cancelled”异常。这是因为一个TimerTask只能被schedule方法调度一次。如果需要执行两个任务,我们需要创建两个TimerTask。我们让两个任务在执行时分别打印当前时刻的秒数,全部代码如下:
// 创建Timer对象
Timer timer = new Timer();
// 创建TimerTask:task1
TimerTask task1 = new TimerTask() {
@Override
public void run() {
System.out.println("task1开始执行:" + new Date().getSeconds());
}
};
// 创建TimerTask:task2
TimerTask task2 = new TimerTask() {
@Override
public void run() {
System.out.println("task2开始执行:" + new Date().getSeconds());
}
};
// 5s后执行
timer.schedule(task1, 5000);
// 指定时刻。如果传入当前时刻,立即执行
timer.schedule(task2, new Date());
运行结果如下,可以看到,task2传入当前时刻立即执行,而task1延迟了5秒执行。
task2开始执行:32 task1开始执行:37
(2)周期性执行
周期性任务可以以固定的周期反复地执行下去。要让Timer周期性执行,同样使用重载的schedule方法,传入第三个参数period——执行周期,就可以让task以固定频率执行。我们给task1传入period = 3000(ms),让它三秒执行一次:
// 5s后执行,每3s一次
timer.schedule(task1, 5000, 3000);
运行结果如下:
task1开始执行:33 task1开始执行:36 task1开始执行:39 task1开始执行:42 ... ...
(3)原理浅析
Timer是如何实现的?查看Timer的源码,发现Timer有两个成员变量,它们是Timer的核心实现:
TaskQueue:任务队列,其中定义了一个长度为128的TimerTask数组,根据TimerTask.nextExecutionTime(下次执行倒计时)维护成了一个最小堆,堆顶就是最近要执行的任务。
TimerThread:任务触发线程,是一个无限循环的线程,它不断从TaskQueue堆顶取出最近要执行的任务,判断剩余执行时间,等待指定时间后去执行任务。执行时,根据任务配置(单次执行 or 周期执行),决定是否向任务队列中放入下一次任务。
用堆来实现任务优先级队列是非常高效的办法,因为任务触发线程只关心下一个要执行的任务,即堆顶元素,剩下的任务的剩余时间一定更长,不必有序,只需取走堆顶元素后重新堆化即可,每次操作的时间复杂度是O(log n)。
(4)存在问题
Timer的实现方式,导致其存在如下问题:
(1)Timer只有一个执行任务的线程,即TimerThread。执行任务时其他任务会阻塞,如果一个任务执行很久,会导致后续任务无法按时执行;
(2)Timer内部只捕获了InterruptedException,未捕获运行时异常。如果任务执行过程中抛出运行时异常,线程将直接被杀死,其他任务也将无法执行。
Timer是Java早期的任务调度框架,其缺陷较多,请读者简单了解,非常不建议使用哦。
二、ScheduledExecutorService
ScheduledExecutorService,可以称为“计划线程池”或“调度线程池”,来自于Java 1.5的JUC包。作为Java升级版的任务调度框架,它解决了Timer的遗留问题,为多线程场景下的定时任务调度提供了稳定可靠的支持。有了它,再也不需要使用Timer了。
作为Executor框架的一部分,ScheduledExecutorService继承了ExecutorService接口,其实现类为ScheduledThreadPoolExecutor。构造方法有4个,如下:
ScheduledThreadPoolExecutor(int corePoolSize);
ScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory);
ScheduledThreadPoolExecutor(int corePoolSize, RejectedExecutionHandler handler);
ScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory, RejectedExecutionHandler handler);
其中3参数构造方法实现如下:
public ScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue(), threadFactory, handler);
}
根据构造方法,可以看到ScheduledThreadPoolExecutor的所有参数如下,其中3个参数可指定,4个参数固定:
① 核心线程数:必传参数,控制执行任务的线程数量;
② 最大线程数:固定为Integer.MAX_VALUE。在ScheduledThreadPoolExecutor中没有作用,实际起作用的是corePoolSize;
③、④ 空闲线程存活时间:固定为0,单位为纳秒。
⑤ 任务队列:固定DelayedWorkQueue延迟阻塞队列,同样是一个最小堆实现的优先级队列。
⑥ 线程工厂:可以手动设置,建议手动传参,方便设置线程名称。
⑦ 拒绝策略:可以手动设置,如果不指定,默认为AbortPolicy,拒绝任务并抛出异常。
让我们看看如何使用它。
(1)单次执行
要想让任务只执行一次,使用schedule方法即可,有3个参数,依次是:要执行的任务方法(实现Runnable或Callable)、延迟的时间、时间单位。注意,ScheduledExecutorService不支持在指定时刻执行,只能在指定的延迟后执行。示例如下:
// 创建线程池,核心线程数=5
System.out.println("创建线程池:" + new Date().getSeconds());
ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(5, new ThreadFactoryBuilder().setNameFormat("scheduler-thread-%d").build());
// 5s后执行任务
scheduler.schedule(() -> System.out.println("任务开始执行:" + new Date().getSeconds()), 5, TimeUnit.SECONDS);
(2)周期性执行
两种执行方式
周期性执行任务又可细分为:固定频率执行、固定延迟执行。
当以固定频率执行时,以上次任务执行的开始时间到本次任务的开始时间来计算任务周期,不考虑任务的执行时间;
当以固定延迟执行时,以上次任务执行的结束时间到本次任务的开始时间来计算任务周期,即任务的执行时间会影响下次任务的开始时间;
当任务执行时间可以忽略时,两种执行方式效果一样。如果考虑任务的执行时间,如任务周期为5s,任务执行需要1s,那么固定频率执行的效果是:0s(开始)、5s(开始)、10s(开始)... ,而固定延迟执行的效果是:0s(开始)、1s(结束)、6s(开始)、7s(结束)、12s(开始)... 。
固定频率执行
要让任务以固定频率执行,使用scheduleAtFixedRate方法。它有4个参数,依次是:要执行的任务方法(实现Runnable或Callable)、首次执行延迟的时间、执行周期、时间单位。
我们设置延迟时间为0,周期为5s,在任务执行中,让任务sleep 1s,模拟任务耗时,配置如下:
// 创建线程池,核心线程数=5
System.out.println("创建线程池:" + new Date().getSeconds());
ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(5, new ThreadFactoryBuilder().setNameFormat("scheduler-thread-%d").build());
scheduler.scheduleAtFixedRate(() -> {
System.out.println("任务开始执行:" + new Date().getSeconds());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务执行完成:" + new Date().getSeconds());
}, 0, 5, TimeUnit.SECONDS);
效果如下:
创建线程池:4 任务开始执行:4 任务执行完成:5 任务开始执行:9 任务执行完成:10 ... ...
需要注意的是,如果任务的执行耗时 > 任务周期,即下一个任务要开始时,上一个任务还没结束,则scheduleAtFixedRate并不会严格按照预期时间执行,而是会等待上一个任务执行结束后再执行。即:任何情况下,一个周期任务都不会同时存在两个执行中的任务实例。
固定延迟执行
要让任务以固定延迟执行,使用scheduleWithFixedDelay方法。其他参数都不变,仅修改方法名,让我们对比下效果:
// 创建线程池,核心线程数=5
System.out.println("创建线程池:" + new Date().getSeconds());
ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(5, new ThreadFactoryBuilder().setNameFormat("scheduler-thread-%d").build());
scheduler.scheduleWithFixedDelay(() -> {
System.out.println("任务开始执行:" + new Date().getSeconds());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务执行完成:" + new Date().getSeconds());
}, 0, 5, TimeUnit.SECONDS);
执行结果如下,可以看到上次任务结束时间和下次任务开始时间的间隔固定,符合我们的预期:
创建线程池:6 任务开始执行:6 任务执行完成:7 任务开始执行:12 任务执行完成:13 ... ...
(3)原理浅析
首先需要清楚,调度线程池和普通线程池最大的区别是:对普通线程池而言,只要线程池中有空闲工作线程,就只管从任务队列中取出任务执行,因此普通线程池的任务队列都是FIFO的。而调度线程池必须判断每个任务的剩余等待时间,没有“到点”的任务,是不可以执行的。如何合理地安排每个定时任务的执行时间?这就需要特殊的任务队列(优先级队列)了。
上文提到,ScheduledThreadPoolExecutor使用的任务队列固定为:DelayedWorkQueue延迟阻塞队列。这是一个专为ScheduledThreadPoolExecutor定制的、满足多线程定时任务设计的任务队列。具有如下特性:
① 【优先性】优先级队列,实现原理和Timer相同,同样是按照任务剩余时间构造的最小堆,每次从堆顶取得最近要执行的任务;
② 【无限大】初始大小为16,每次队列满后自动扩容,可无限扩容到 Integer.MAX_VALUE,因此在添加任务时(offer方法)不会阻塞;
③ 【并发性】队列操作有锁机制保证线程安全;同时,为了更好管理线程资源,队列采用了Leader-Follower的线程模型。
Leader/Follower模型
为了实现该模型,DelayedWorkQueue中定义了如下成员变量:
// 队列内的重入锁,保证线程安全
private final ReentrantLock lock = new ReentrantLock();
// leader线程
private Thread leader = null;
// lock创建的等待队列
private final Condition available = lock.newCondition();
首先,队列拥有一个重入锁lock,所有队列操作都需要先获取这把锁; 一个成员变量leader,指向下一个要处理队列头部任务的线程,其他空闲的工作线程被称作follower; 最后是lock创建的等待队列available,所有follower都在这里等待,等着成为新的leader。那么什么时候才会出现岗位空缺(available)呢?请看下文。
刚才提到,leader就是处理当前队列头任务的线程。leader首先会判断这个任务的剩余时间,然后等待这个时间。时间一到就取走任务,要去执行,就在leader要“卸任”的时候,它需要通知一下排队的继任者(follower)们,于是发出available.signal()信号,岗位有空缺啦!从而使一个follower线程获取锁成为leader,执行后边任务。最后,所有执行完任务的线程都会重新成为follower等着领新一轮的任务,如此循环。这部分实现,请感兴趣的读者阅读DelayedWorkQueue的take()/poll()方法。
如果在leader等待时,来了新任务怎么办?先别急,重新堆化,如果新任务没排到队首,说明剩余时间肯定大于队首任务,则不需要着急执行;如果新任务排到了队首,说明这个任务时间最紧急,执行时间已经早于了当前leader的苏醒时间了,来不及啦!那么直接把当前的leader踢掉,发送available.signal()信号,召唤新leader执行新任务。这部分实现,请感兴趣的读者阅读DelayedWorkQueue的offer()方法。
这种Leader/Follower模式最早被应用于多线程网络服务中,通过确保请求接收者和执行者是同一个线程来减少接收者另外创建执行者线程的开销,减少线程间数据交换。在DelayedWorkQueue中,这种模式巧妙地确保了在线程池中最多只有一个线程(leader)在等待执行最近的任务,而其他空闲线程可以无限等待直到被唤醒,从而避免多个工作线程同时等待一个任务带来的额外开销。
(4)使用注意
译自ScheduledThreadPoolExecutor的官方注释:
虽然这个类继承自ThreadPoolExecutor,但继承的一些调优方法对它没有用处。特别是,因为它使用corePoolSize线程和无界队列充当固定大小的池,所以对maximumPoolSize的调整没有任何有用的影响。此外,将corePoolSize设置为零或使用allowCoreThreadTimeout几乎从来都不是一个好主意,因为这可能会使池中没有线程来处理任务,一旦它们有资格运行。
使用ScheduledThreadPoolExecutor的时候,我们需要格外注意的是:线程池大小始终固定为corePoolSize不会变,而maximumPoolSize没有任何作用,它可不会自己添加工作线程!如果你需要执行多个定时任务,请尽量把corePoolSize设置大一些,避免工作线程不够导致任务没能按时执行。
三、@Scheduled
@Scheduled注解是Spring框架提供的通过注解方式实现定时调度的定时任务框架,来自org.springframework.scheduling包。该注解主要有三种配置定时的方式,分别支持我们以固定频率、固定延迟或cron表达式配置定时任务:
// 固定频率执行,5s一次
@Scheduled(fixedRate = 5000)
// 固定延迟执行,5s一次
@Scheduled(fixedDelay= 5000)
// cron表达式,1分钟执行一次
@Scheduled(cron = "0 0/1 * * * ?")
(1)环境配置
首先让我们看看使用@Scheduled注解需要的配置。要让该注解生效,有两种配置方法。
① 注解启动类:在Spring启动类添加@EnableScheduling注解,才能使@Scheduled生效,默认未开启。
② 配置文件:在配置文件中引入task的命名空间,并添加注解驱动“annotation-driven”:
<beans xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd">
<task:annotation-driven/>
(2)cron表达式
固定频率执行、固定延迟执行,这两个执行方式前文已经介绍过了,有读者可能要问了,第三种方式cron表达式是什么?又该如何使用呢?
cron始于linux下的定时执行工具,是linux系统的内置服务。在系统中,可以使用crontab命令来设定cron服务,cron会根据命令和执行时间来按时调度工作任务。cron表达式的功能强大,可以满足各种定制化的定时任务配置需求,远比fixedRate、fixedDelay等方式灵活多样,适合各种复杂的定时任务配置。
cron表达式一共包含7个域,每个域代表不同的含义,从左到右依次是:“秒 分 时 日 月 周 年”。这些域以空格隔开组成的字符串就是cron表达式。其中,“年”在大多数场景下用不上(极少有以年为周期的任务),因此不是必要的,前6个域即可组成一个cron表达式。cron表达式的规则是:
① 【数字】在每个域中,可以填入数字,代表在指定的时刻执行。具体到每个域的数字范围是:
分、秒:0-59; 时:0-23; 日:1-31(视每月情况); 月:1-12(JAN-DEC); 周:1-7(SUN-SAT); 年:1970-2099。
特别提醒:在【周】域中,1=周日、2=周一、... 7=周六。这块有点反常识,担心记错的话可以填星期的英文缩写(SUN-SAT)。
② 【*】如果想让这个域无论等于什么值都执行,请填入【*】(通配符)。
这时细心的读者会发现有个bug:如果配置了【日】为【*】,即每天都执行,且【周】配为【6】,即每周五执行,就会出现互斥:又想每天都执行,又想每周五执行,但并非每天都是周五啊! 为了解决这个问题,cron表达式设置了【?】符号。
③ 【?】如果这个域的值不关心,请填入【?】(不指定)。这个符号只能填在【日】或【周】域,用来解决两个日期和星期的互斥问题。
用规则①②③搭配就可以创建很多基本的cron表达式:
1 * * * * ? 每分钟的第1秒执行 0 0 0 * * ? 每天0点执行 0 15 10 ? * * 每天上午10:15执行
光有这些还不够,cron表达式还有更加丰富的符号以满足更多样的需求,请接着往下看:
④ 【-】指定取值范围。例:
0 0 9-17 * * ? 每天9点到17点,每整点执行一次
⑤ 【,】指定多个值。例:
0 0 0 1,15 * ? 每月1日、15日,0点执行
⑥ 【/】指定起始量/增量,以固定频率执行。例:
0/2 * * * * ? (从每分钟的0秒开始)每2秒执行一次 0 0 18/1 * * ? 从每天的18点开始,每整点执行一次
⑦ 【L】即“LAST”之意,只能填在【日】或【周】域,代表最后一天。
在【周】域,还可以写“数字 + L”,代表该取值的最后一个,即最后一个周几,例:
0 0 0 L * ? 每月最后一天0点执行 0 0 0 ? * L 每周最后一天0点执行 0 0 0 ? * WEDL 每月最后一个周三0点执行
⑧【W】表示自动匹配工作日,只能填在【日】域,且必须在数字后。如“5W”意为“本月距离5号最近的工作日”。
也可以将【L】【W】连用,意为“本月最后一个工作日”。
0 0 0 1W * ? 在本月距离1号最近的工作日0点执行 0 0 0 LW * ? 在本月最后一个工作日0点执行 看到这里,我想你已经明白怎么用定时任务发工资了。这个表达式,真像是为月末发工资准备的呢!
⑨【#】指定第几个周几,只能填在【周】域。【#】左边填周几,右边填第几个。例:
0 0 0 ? 5 SUN#2 每年母亲节(5月的第二个星期日)的0点执行
强大的cron表达式几乎可以满足所有定时任务的需求,如果你迫不及待想尝试一下cron表达式并验证效果,推荐这个网站:https://cron.qqe2.com,可以在线生成和验证cron表达式,并看到预期执行结果。
(3)单线程 or 多线程?
看完了cron表达式,那么问题来了:@Scheduled注解实现的定时任务是单线程还是多线程呢?如果是多线程,有几个线程?如何控制线程池?
很遗憾,答案是:在默认配置下,@Scheduled注解是单线程的。这是因为@Scheduled注解默认创建的线程池大小为1,这显然很可能导致阻塞问题。要想改成多线程,需要手动配置任务线程池,让我们一起看看。
方法①:使用.properties文件或.yml文件,直接配置线程池大小
spring.task.scheduling.pool.size = 10
spring:
task:
scheduling:
pool:
size: 10
方法②:手动配置线程池org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler,并设置线程池大小等参数。ThreadPoolTaskScheduler即为@Scheduled注解所创建的线程池:
@Configuration
@EnableScheduling
public class TaskSchedulerConfig {
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10);
scheduler.initialize();
return scheduler;
}
}
经过以上配置,我们可以让@Scheduled注解的定时任务以多线程方式执行,即使A任务阻塞,B任务也不会受影响。这和上文提到的ScheduledExecutorService的效果是完全一样的。事实上,ThreadPoolTaskScheduler就是基于ScheduledThreadPoolExecutor实现的,其部分源码:
public class ThreadPoolTaskScheduler extends ExecutorConfigurationSupport
implements AsyncListenableTaskExecutor, SchedulingTaskExecutor, TaskScheduler {
// ScheduledThreadPoolExecutor.setRemoveOnCancelPolicy(boolean) only available on JDK 7+
private static final boolean setRemoveOnCancelPolicyAvailable =
ClassUtils.hasMethod(ScheduledThreadPoolExecutor.class, "setRemoveOnCancelPolicy", boolean.class);
private volatile int poolSize = 1;
private volatile boolean removeOnCancelPolicy = false;
private volatile ErrorHandler errorHandler;
private volatile ScheduledExecutorService scheduledExecutor;
/**
* Set the ScheduledExecutorService's pool size.
* Default is 1.
* <p><b>This setting can be modified at runtime, for example through JMX.</b>
*/
public void setPoolSize(int poolSize) {
Assert.isTrue(poolSize > 0, "'poolSize' must be 1 or higher");
this.poolSize = poolSize;
if (this.scheduledExecutor instanceof ScheduledThreadPoolExecutor) {
((ScheduledThreadPoolExecutor) this.scheduledExecutor).setCorePoolSize(poolSize);
}
}
// ... ...
}
不难发现,Spring的ThreadPoolTaskScheduler就是在ScheduledThreadPoolExecutor基础上封装了一层,且默认的线程池大小为1。在原线程池的基础上增加了注解驱动、cron表达式解析等功能,更加方便了我们的使用。
(4)关于异步执行
以上提到的所有任务调度都有一个共同点:我们可以通过线程池让不同任务的执行互不干扰,但对于同一任务,当上一次执行未完成时,即使到了下一次执行时间,下一次执行还是会等待,即不会出现一个任务同时存在两个执行中的任务实例。如果想让一个任务的每次执行都互不影响呢?
@Async注解可以帮助我们,它可以支持异步地执行方法,每次执行都会另起线程。我们将它和@Scheduled注解一起加在方法上:
@Async
@Scheduled(cron = "0 0/1 * * * ?")
public void task() {
// do something
}
这样,就可以让该任务的每次执行都互不影响。我们还可以在注解中指定异步方法执行的线程池,如@Async("asyncExecutor"),且asyncExecutor应为ThreadPoolTaskScheduler类型。
? 特别提醒:原则上讲,不应该,也没有必要让一个周期性任务异步执行。一旦允许异步,如果该任务卡死,后续本类任务不再阻塞,还会继续起新线程,并不断卡死,很快把任务线程池打满,最后阻塞所有的定时任务,造成严重后果。如果一定要使用,请为异步定时任务手动指定一个单独的任务线程池,并配置好最大等待时长(setAwaitTerminationSeconds),避免无限阻塞。
相关推荐
- TCP协议原理,有这一篇就够了
-
先亮出这篇文章的思维导图:TCP作为传输层的协议,是一个软件工程师素养的体现,也是面试中经常被问到的知识点。在此,我将TCP核心的一些问题梳理了一下,希望能帮到各位。001.能不能说一说TC...
- Win10专业版无线网络老是掉线的问题
-
有一位电脑基地的用户,使用...
- 学习计算机网络需要掌握以下几方面基础知识
-
计算机基础知识操作系统:了解常见操作系统(如Windows、Linux)的基本操作和网络配置,例如如何设置IP地址、子网掩码、网关和DNS服务器等,以及如何通过命令行工具(如ping、tr...
- 网络工程师的圣经!世界级网工手绘268张图让TCP/IP直接通俗易懂
-
要把知识通俗地讲明白,真的不容易。——读者说TCP/IP从字面意义上讲,有人可能会认为TCP/IP是指TCP和IP两种协议。实际生活当中有时候也确实就是这两种协议。然而在很多情况下,它只是...
- 三分钟了解通信知识TCP与IP协议(含“通信技术”资料分享)
-
TCP/IPTCP/IP分层模型①应用层...
- 网闸与防火墙:网络安全设备的差异与应用
-
在网络安全领域,网闸(安全隔离网闸,GAP)和防火墙(Firewall)是两类重要的防护设备。尽管它们都服务于网络安全防护,但在设计理念、技术原理、安全效能及适用场景等方面存在显著差异,以下从五个维度...
- S7-300的TCP/IP通信
-
一、首先在项目中创建2个S7-300的站点;二、硬件组态中,设置合适的TCP/IP地址,在同一网段内;...
- 西门子S7-1500 PLC的 MODBUS TCP通信
-
MODBUSTCP使MODBUS_RTU协议运行于以太网,MODBUSTCP使用TCP/IP和以太网在站点间传送MODBUS报文,MODBUSTCP结合了以太网物理网络和网络标准TC...
- 系统规划与管理师新版备考必备:第7章考点思维导图解析
-
备考系统规划与管理师的小伙伴们,福利又来啦!今天为大家带来《系统规划与管理师(第2版)》第7章考点的思维导图,助你高效梳理重点,让备考更有方向!...
- TCP/IP、Http、Socket 有何区别与联系?
-
HTTP协议对应于应用层,Socket则是对TCP/IP协议的封装和应用(程序员层面上)。HTTP是应用层协议,主要解决如何包装数据。而我们平时说的最多的Socket是什么呢?实际上...
- 西门子PLC串口协议与以太网通信协议对比
-
西门子plc品牌众多,通信协议的类型就更多了,具体可分为串口协议和以太网通信协议两大类。...
- 网络编程懒人入门(十三):一泡尿的时间,快速搞懂TCP和UDP的区别
-
本文引用了作者Fundebug的“一文搞懂TCP与UDP的区别”一文的内容,感谢无私分享。1、引言...
- 程序员必备的学习笔记《TCP/IP详解(一)》
-
为什么会有TCP/IP协议在世界上各地,各种各样的电脑运行着各自不同的操作系统为大家服务,这些电脑在表达同一种信息的时候所使用的方法是千差万别。就好像圣经中上帝打乱了各地人的口音,让他们无法合作一样...
- 一文读懂TCP/IP协议工作原理和工作流程
-
简述本文主要介绍TCP/IP协议工作原理和工作流程。含义TCP/IP协议,英文全称TransmissionControlProtocol/InternetProtocol,包含了一系列构成互联网...
- 如何在 Windows 10 和 Windows 11 上重置 TCP/IP 堆栈
-
传输控制协议/Internet协议,通常称为TCP/IP,是您的WindowsPC如何与Internet上的其他设备进行通信的关键部分。但是当事情出错时会发生什么?你如何解决它?幸运的...
你 发表评论:
欢迎- 一周热门
-
-
前端面试:iframe 的优缺点? iframe有那些缺点
-
带斜线的表头制作好了,如何填充内容?这几种方法你更喜欢哪个?
-
漫学笔记之PHP.ini常用的配置信息
-
其实模版网站在开发工作中很重要,推荐几个参考站给大家
-
推荐7个模板代码和其他游戏源码下载的网址
-
[干货] JAVA - JVM - 2 内存两分 [干货]+java+-+jvm+-+2+内存两分吗
-
正在学习使用python搭建自动化测试框架?这个系统包你可能会用到
-
织梦(Dedecms)建站教程 织梦建站详细步骤
-
【开源分享】2024PHP在线客服系统源码(搭建教程+终身使用)
-
2024PHP在线客服系统源码+完全开源 带详细搭建教程
-
- 最近发表
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)