JDK动态代理原理

本文主要讲解下JDK动态代理的实现原理,其基本使用如下:

// 实例化自定义调用处理器实例
InvocationHandler handler = new MyInvocationHandler(...);
// 获取代理对象方式一 
Class<?> proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class);
Foo f = (Foo) proxyClass.getConstructor(InvocationHandler.class).newInstance(handler);
// 获取代理对象方式二
Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),new Class<?>[] { Foo.class },handler);

可以看出,只要给出要实现类的加载器、接口列表就能够在运行时创建出代理类。代理类的实例化需要传入自定义的调用处理器实例,具体代理的内容均在处理器实例中体现。
JDK动态代理在使用上十分简单,但内部具体实现逻辑有很多细节指的我们探索。下文将先从目标代理类入手,分析其结构和调用关系。然后分析Proxy中探索生成代理类的过程。

一、代理类

1.1 获取代理类

代理类是运行时生成的,一般不会产生具体的类文件。如果想要获取代理类,可设置如下系统参数:

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

不同JDK版本可能参数会有所不同,参考ProxyGenerator#saveGeneratedFiles指定的Key值。
该值设置为true后,程序生成代理类是就会将对应的类文件路径为:{项目根目录}/com/sun/proxy/$proxy{index}.class,其中index为代理类的唯一序号。,
下面是为简单的Person接口生成的代理类:

public interface Person {void ageNum();
}
public final class $Proxy0 extends Proxy implements Person {private static Method m1;private static Method m2;private static Method m3;private static Method m0;public $Proxy0(InvocationHandler var1) throws  {super(var1);}public final boolean equals(Object var1) throws  {try {return (Boolean)super.h.invoke(this, m1, new Object[]{var1});} catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);}}public final String toString() throws  {try {return (String)super.h.invoke(this, m2, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}public final void ageNum() throws  {try {super.h.invoke(this, m3, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}public final int hashCode() throws  {try {return (Integer)super.h.invoke(this, m0, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}static {try {m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));m2 = Class.forName("java.lang.Object").getMethod("toString");m3 = Class.forName("com.design.代理模式.jdkDynamic.Person").getMethod("ageNum");m0 = Class.forName("java.lang.Object").getMethod("hashCode");} catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());} catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(var3.getMessage());}}
}

可以看到代理类使用final修饰不允许被继承,访问权限是否为public由其继承的接口Person的权限决定。
代理类继承了Proxy类,并实现了指定接口的所有方法。是否正是因为代理类需继承Proxy类,且Java不允许多继承,所以代理类就只能传入对应的接口呢?为什么代理类必须得继承Proxy类?

1.2 构造方法

代理类仅提供了一个构造方法,并且需传入InvocationHandler实例。InvocationHandler就是具体方法调用时的代理处理器。这个后续会说到,先有个印象。【InvocationHandler实例必传】

1.3 类成员变量

代理类中的成员变量均为静态的方法句柄(java.lang.reflect.Method),代理类在加载时,会通过反射的方法初始化各个Method。

Calss.forName(String className).getMethod(String methodName, ...parameterTypes)

1.4 类方法

代理类会实现接口的多个方法,并且都使用final关键字修饰,不允许被重写(本来代理类也不能被继承),访问权限依据接口中方法的访问权限决定。
我们从上面例子可以看到,代理类不仅实现了接口的方法,同时也实现了Object类hashCode()、equals()、toString()。这就说明了代理类不仅会代理指定接口的方法,默认也会代理Object类中公开的、非本地的方法。

try {// super.h 就是 代理类构造方法传入的InvocationHandler实例return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {throw var3;
} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);
}

在方法内部执行逻辑,我们可以发现,代理类执行某个方法时,会调用InvocationHandler实例的invoke方法。

二、调用处理器

我们在前面代理类的分析中发现,代理类执行任何一个方法,实际上都会通过InvocationHandler实例的invoke方法进行处理。每一个代理实例都必须关联一个InvocationHandler实例。

2.1 InvocationHandler接口

InvocationHandler是java.lang.reflect包下提供的接口,该接口仅声明了一个方法:

public Object invoke(Object proxy, Method method, Object[] args)throws Throwable;

任何代理类调用方法时,都会调用其关联的InvocationHandler实例的invoke方法获取方法返回结果。对于其方法参数说明如下:

  • Object proxy:被调用的代理类实例。
  • Method method:代理类实例分派的对应方法句柄。
  • Object[] args:方法调用的参数列表
    因此,代理类调用的任何方法最终都会通过这个方法实际执行。调用方可以根据自己的需要通过实现此方法达到自己的目的。

三、核心类-Proxy

Proxy是JDK动态代理的核心类,主要负责代理类的生成、实例化任务。

public class Proxy implements java.io.Serializable {
}

从类声明上看,Proxy实现Serializable接口表明代理类(继承Proxy)是允许被序列化&反序列化的。
Proxy类的访问权限为public且没有声明final关键字。即便如此,一般情况下也没有人手动继承Proxy类,因为Proxy要么是静态方法,要么是私有方法,意即,单独继承Proxy几乎没有意义。

3.1 构造方法

Proxy提供了两个构造方法。

private Proxy() {
}protected Proxy(InvocationHandler h) {Objects.requireNonNull(h);this.h = h;
}

Proxy的无参构造的访问权限为私有,这就意味着Proxy是不允许用户直接实例化的。实际上,Proxy直接实例化也没有意义,公开方法均为静态方法。
Proxy的有参构造访问权限为protected,是提供给子类用的,比如生成的代理类是Proxy的子类,当代理类实例化时,就会通过这个构造方法将调用处理器实例存储在Proxy#属性中。

3.2 属性

Proxy类中有三个属性,其中2个私有类型,1个受保护类型。

  • Class<?>[] constructorParams:私有属性,在实例化代理类时,会调用参数列表为constructorParams的构造器进行初始化。默认情况下,constructorParams为仅包含InvocationHandler.class的数组。
  • WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache:私有属性,在生成代理类时,会先通过WeakCache去生产代理类并缓存起来。如何生成代理类与WeakCache息息相关,此处建议先看下WeakCache相关文章。【WeakCache二级缓存】
    WeakCache是个二级缓存,一级Key由类加载器生成,二级Key由接口列表生成,Value值就是代理类class。proxyClassCache会在Proxy加载时就初始化,初始化传入的KeyFactory用于结合接口列表产生二级Key,ProxyClassFactory会根据类加载器、接口列表来产生具体的代理类class对象。因此,ProxyClassFactory将会是Proxy的重头戏。
  • InvocationHandler h:protected权限,在代理类实例化时赋值InvocationHandler 实例,代理类执行任何方法都会分派给InvocationHandler实例的invoke方法执行。

3.4 内部类

内部类的内容是Proxy原理的重点,阅读本节之前必须先看懂【WeakCache二级缓存】

Proxy类中定义了5个私有静态内部类,并且均使用final关键字修饰。

  1. KeyFactory类实现了函数式接口,根据代理接口列表生成二级缓存的sub-Key。
  2. Key1、Key2、KeyX:这三个类是KeyFactory中根据接口个数选择的不同Sub-Key类型,目的是优化性能。
  3. ProxyClassFactory类也实现了函数式接口,根据类加载器、接口列表生成代理类class。

3.4.1 KeyFactory、KeyX

KeyFactory实例作为WeakCache实例化时的第一个工厂函数对象subKeyFactory,主要用于代理类缓存的二级Key,即sub-Key的生成

private static final class KeyFactory implements BiFunction<ClassLoader, Class<?>[], Object> {@Overridepublic Object apply(ClassLoader classLoader, Class<?>[] interfaces) {switch (interfaces.length) {case 1: return new Key1(interfaces[0]); // the most frequentcase 2: return new Key2(interfaces[0], interfaces[1]);case 0: return key0;default: return new KeyX(interfaces);}}
}

KeyFactory类实现了BiFunction的apply方法。BiFunction(Binary Function)相比于Function接口,BiFunction会根据两个参数返回结果的函数式接口。KeyFactory其实就对应着WeakCache的subKeyFactory,因此通过KeyFactory的泛型参数可以看出代理类缓存的一级Key就是ClassLoader类型的类加载器,生成二级Key的另外一个参数就是Class<?>[]类型的类信息数组(实际上,这里其实是接口类信息数组,后面会说明)。
KeyFactory#apply方法生成subKey需要传入两个参数:类加载器及接口信息列表。而在实际逻辑中,可以看出KeyFactory在生成subKey的时候并没有考虑类加载器。因此代理类缓存的结构可记为<类加载器,接口信息,代理类>,也就意味着代理类在同一类加载器下同样的接口信息下是唯一的
那具体是如何根据接口类信息数组生成二级Key呢?从KeyFactory#apply方法可以看到,二级Key实际上就是Proxy提供内部类Key[1,2,X]对象。这里是根据接口的长度来返回不同类型的对象。

这里有两个问题暂时还没搞懂:

  1. Key1、Key2、KeyX的区别在哪里?内存占用和性能上似乎没有区别,都是用KeyX不行吗?
  2. KeyX为什么不继承弱引用呢?虽然subKey即便不是弱引用也无所谓,WeakCache都没要求。
    KeyX对象只会被用于WeakCache

3.4.2 ProxyClassFactory

ProxyClassFactory实例作为WeakCache实例化时的第二个工厂函数对象valueFactory,主要用于生成代理类Class

private static final class ProxyClassFactoryimplements BiFunction<ClassLoader, Class<?>[], Class<?>> {...
}

ProxyClassFactory同样也是实现了BiFunction接口,实现的apply方法会根据类加载器和类信息数组生成代理类Class并返回。
先来看ProxyClassFactory内部类的两个私有类成员变量

// 所有代理类名称前缀private static final String proxyClassNamePrefix = "$Proxy";// 下一个要生成的代理类序号private static final AtomicLong nextUniqueNumber = new AtomicLong();

这两个成员变量就是用于拼接生成代理类名的,即proxyClassNamePrefix + nextUniqueNumber,比如第一章节给出的“$Proxy0”就是生成的第一个代理类名。
ProxyClassFactory的核心还是用于生成代理类Class的apply方法,代码及注释:

public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {// 1. 第一部分是类信息数组的校验和验证部分Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);for (Class<?> intf : interfaces) {// 1.1 验证所有类信息都能够通过当前类加载器加载 Class<?> interfaceClass = null;try {interfaceClass = Class.forName(intf.getName(), false, loader);} catch (ClassNotFoundException e) {}if (interfaceClass != intf) {throw new IllegalArgumentException(intf + " is not visible from class loader");}// 1.2 验证所有类信息均为接口类型【JDK动态代理只能代理接口】 if (!interfaceClass.isInterface()) {throw new IllegalArgumentException(interfaceClass.getName() + " is not an interface");}// 1.3 验证接口信息数组不存在重复if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {throw new IllegalArgumentException("repeated interface: " + interfaceClass.getName());}}String proxyPkg = null;     // 定义代理类所在的包路径int accessFlags = Modifier.PUBLIC | Modifier.FINAL;		// 默认代理类为public final/** Record the package of a non-public proxy interface so that the* proxy class will be defined in the same package.  Verify that* all non-public proxy interfaces are in the same package.*/// 2. 如果接口数组中存在非public,那代理类也是非public权限。// 	并且,代理类所在的包路径为非public接口所在路径【多个非public接口需在同一个包路径下】for (Class<?> intf : interfaces) {int flags = intf.getModifiers();if (!Modifier.isPublic(flags)) {accessFlags = Modifier.FINAL;String name = intf.getName();int n = name.lastIndexOf('.');String pkg = ((n == -1) ? "" : name.substring(0, n + 1));if (proxyPkg == null) {proxyPkg = pkg;} else if (!pkg.equals(proxyPkg)) {throw new IllegalArgumentException("non-public interfaces from different packages");}}}if (proxyPkg == null) {// 若接口均为public权限,默认代理类包路径为com.sun.proxyproxyPkg = ReflectUtil.PROXY_PACKAGE + ".";}/** Choose a name for the proxy class to generate.*/// 3. 拼接出代理类的全限定名long num = nextUniqueNumber.getAndIncrement();String proxyName = proxyPkg + proxyClassNamePrefix + num;/** 4. 根据代理类名、实现的接口、代理类权限信息生成代理类字节数组*/byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);try {// 调用本地方法,使用指定的类加载器加载代理类Class对象return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);} catch (ClassFormatError e) {throw new IllegalArgumentException(e.toString());}
}

代码十分简单,几乎就是验证了一些代理类信息正确性,确定代理类包路径、类名、访问权限等信息,最重要的是通过ProxyGenerator的静态方法来生成代理类字节码数组,并调用本地方法加载并返回代理类Class对象。

3.3 公开方法

Proxy类提供了四个静态公开方法。

  1. Class<?> getProxyClass(ClassLoader loader, Class<?>… interfaces)
    根据类加载器、代理类需实现的接口数组返回代理类对象。内部逻辑很简单,几乎就是通过Proxy#proxyClassCache缓存来获取-可能是从已有缓存中获取也有可能需要通过ProxyClassFactory的apply方法生成代理对象。【逻辑简单,本文不再详细叙述,读者在之前的基础上阅读即可】
  2. boolean isProxyClass(Class<?> cl)
    判断传入的Class对象是否为为代理类对象。
    返回true的代理类条件:Proxy子类,且存在于proxyClassCache缓存中。【问:该缓存失效怎么办?代理类缓存一定是GC失效吗?】
  3. InvocationHandler getInvocationHandler(Object proxy)
    返回传入代理类对象的InvocationHandler对象(当前proxy对象的Class必须为代理类对象)。
  4. Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
    根据创建代理类对象所有必须参数,返回最终代理类对象实例。

四个方法内部逻辑均十分简单清晰,这里仅给出常用newProxyInstance方法代码及注释。

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException
{// 调用处理器必须不能为nullObjects.requireNonNull(h);final Class<?>[] intfs = interfaces.clone();final SecurityManager sm = System.getSecurityManager();if (sm != null) {checkProxyAccess(Reflection.getCallerClass(), loader, intfs);}/** 根据类加载器、要实现的接口列表获取或加载代理类Class对象*/Class<?> cl = getProxyClass0(loader, intfs);try {if (sm != null) {checkNewProxyPermission(Reflection.getCallerClass(), cl);}// 反射找到代理类的构造方法,包含调用处理器参数final Constructor<?> cons = cl.getConstructor(constructorParams);final InvocationHandler ih = h;if (!Modifier.isPublic(cl.getModifiers())) {	// 如果当前代理类非public【接口存在非public】AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {cons.setAccessible(true);	// 设置构造方法可见return null;}});}return cons.newInstance(new Object[]{h});	// 反射实例化代理类对象} catch (IllegalAccessException|InstantiationException e) {throw new InternalError(e.toString(), e);} catch (InvocationTargetException e) {Throwable t = e.getCause();if (t instanceof RuntimeException) {throw (RuntimeException) t;} else {throw new InternalError(t.toString(), t);}} catch (NoSuchMethodException e) {throw new InternalError(e.toString(), e);}
}

可以看到,如果前面一大部分能够理解的话,Proxy提供的四个公开静态方法实际上理解起来十分简单,这里也不做过多赘述【重点还是二级缓存&代理类的生成】。另外,还有个细节就是所有的公开方法均使用了@CallerSensitive注解,深入了解可参考@CallerSensitive一些理解。

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

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

相关文章

element vue2 动态添加 select+tree

难点在 1 添加一组一组的渲染 是往数组里push对象 循环的&#xff1b;但是要注意对象的结构! 因为这涉及到编辑完成后&#xff0c;表单提交时候的 校验&#xff01; 是校验每一个select tree里边 是否勾选 2 是在后期做编辑回显的时候 保证后端返回的值 是渲染到 select中的tr…

ChatGPT超详细介绍与功能与免费网页版(超全面!)

ChatGPT ChatGPT前言ChatGPT介绍ChatGPT的优点关于ChatGPT的一些问题1.chatgpt是什么意思?2.chatgpt国内能用吗? 国内可用的ChatGPT网页版&#xff1a;1.ChatGPT prompts2.这个网站收集了5000多个ChatGPT 应用&#xff0c;可以在线运行3.ChatGPT Box4.飞书chatgpt5.AI-Produc…

matplotlib库的用法——各种图的绘制

matplotlib是一个流行的Python绘图库&#xff0c;用于创建各种静态、动态、交互式可视化。以下是一些基本的用法&#xff1a; 线图 plt.plot([1, 2, 3, 4]) plt.ylabel(Some Numbers) plt.show()散点图 x [1,2,3,4,5] y [2,3,4,5,6] plt.scatter(x, y) plt.show() 条形图 …

sklearn中使用决策树

1.示例 criterion可以是信息熵&#xff0c;entropy&#xff0c;可以是基尼系数gini # -*-coding:utf-8-*- from sklearn import tree from sklearn.datasets import load_wine from sklearn.model_selection import train_test_split wineload_wine()# print ( wine.feature_…

【2.3】Java微服务:sentinel服务哨兵

✅作者简介&#xff1a;大家好&#xff0c;我是 Meteors., 向往着更加简洁高效的代码写法与编程方式&#xff0c;持续分享Java技术内容。 &#x1f34e;个人主页&#xff1a;Meteors.的博客 &#x1f49e;当前专栏&#xff1a;Java微服务 ✨特色专栏&#xff1a; 知识分享 &…

uniapp 微信小程序 判断数据返回的是jpg还是pdf,以及pdf预览

<template> <view class"approval-notice"><block v-for"(imgItem, idx) in drivingLicense" :key"idx">//如果是非图片&#xff0c;那就走pdf预览<view class"pdf-item" v-if"Object.keys(thumbnail).incl…

css-3:什么是响应式设计?响应式的原理是什么?如何做?

1、响应式设计是什么&#xff1f; 响应式网站设计&#xff08;Responsive WEB desgin&#xff09;是一个网络页面设计布局&#xff0c;页面的设计与开发应当根据用户行为以及设备环境&#xff08;系统平台、屏幕尺寸、屏幕定向等&#xff09;进行相应的相应和调整。 描述响应式…

ensp与虚拟机搭建测试环境

1.虚拟机配置 ①首先确定VMnet8 IP地址&#xff0c;若要修改IP地址&#xff0c;保证在启动Ensp前操作 ②尽量保证NAT模式 2.ensp配置 (1)拓扑结构 (2)Cloud配置 ①首先点击 绑定信息 UDP → 增加 ②然后点击 绑定信息 VMware ... → 增加 ③最后在 端口映射设置上点击双向通…

Hive创建外部表详细步骤

① 在hive中执行HDFS命令&#xff1a;创建/data目录 hive命令终端输入&#xff1a; hive> dfs -mkdir -p /data; 或者在linux命令终端输入&#xff1a; hdfs dfs -mkdir -p /data; ② 在hive中执行HDFS命令&#xff1a;上传/emp.txt至HDFS的data目录下&#xff0c;并命名为…

vue3+vue-i18n 监听语言的切换

最近在用 vue3 做一个后台管理系统&#xff0c;之前是只考虑中文&#xff0c;现在加了个需求是多语言。 本来也不是太难的需求&#xff0c;但是我用的并不熟悉&#xff0c;并且除了页面展示不同的语言&#xff0c;需求是在切换语言的时候在几个页面中需要做出一些自定义的行为&…

Openharmony重要特性之一浅析分布式软总线

OH分布式软总线 概述 简介分布式软总线的特征官方说明目录结构与代码说明分布式软总线使用 使用说明接口说明发现 发现的接口C++示例说明组网 组网的接口C++示例说明传输 软总线传输关键流程传输的接口注意事项一、概述 1.1 简介 ​ 分布式软总线能做什么? ​ 1.1 通过共享一…

50.两数之和(力扣)

目录 问题描述 核心代码解决 代码思想 时间复杂度和空间复杂度 问题描述 给定一个整数数组 和一个整数目标值 &#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组下标。numstarget 你可以假设每种输入只会对应一个答案。但是&am…

jmeter工具测试和压测websocket协议【杭州多测师_王sir】

一、安装JDK配置好环境变量&#xff0c;安装好jmeter 二、下载WebSocketSampler发送请求用的&#xff0c;地址&#xff1a;https://bitbucket.org/pjtr/jmeter-websocket-samplers/downloads/?spma2c4g.11186623.2.15.363f211bH03KeI 下载解压后的jar包放到D:\JMeter\apache-j…

2.Flink应用

2.1 数据流 DataStream&#xff1a;DataStream是Flink数据流的核心抽象&#xff0c;其上定义了对数据流的一系列操作DataStreamSource&#xff1a;DataStreamSource 是 DataStream 的 起 点 &#xff0c; DataStreamSource 在StreamExecutionEnvironment 中 创 建 &#xff0c;…

计算机网络各层的功能以及常用协议

目录 1. 物理层&#xff08;Physical Layer&#xff09;2. 数据链路层&#xff08;Data Link Layer&#xff09;3. 网络层&#xff08;Network Layer&#xff09;4. 传输层&#xff08;Transport Layer&#xff09;5. 应用层&#xff08;Application Layer&#xff09; 计算机网…

init_pg_dir 的大小及作用

init_pg_dir 的大小 vmlinux.lds.S 中 在vmlinux.lds.S 中&#xff0c;有 init_pg_dir .; . INIT_DIR_SIZE; init_pg_end .;/*include/asm/kernel-pgtable.h*/ #define EARLY_ENTRIES(vstart, vend, shift) \ ((((vend) - 1) >&g…

基于 CentOS 7 构建 LVS-DR 群集

文章目录 前言1、LVS集群2、DR模式的工作流程图 一、LVS DR模式的配置二、配置步骤总结 前言 什么是LVS集群&#xff1f;DR模式&#xff1f; 1、LVS集群 LVS采用的是合入内核模块&#xff0c;先把对于nginx来说要稳定很多&#xff0c;性能和稳定都在一定层度上占据优势&…

【ChatGPT 指令大全】怎么使用ChatGPT写履历和通过面试

目录 怎么使用ChatGPT写履历 寻求履历的反馈 为履历加上量化数据 把经历修精简 为不同公司客制化撰写履历 怎么使用ChatGPT通过面试 汇整面试题目 给予回馈 提供追问的问题 用 STAR 原则回答面试问题 感谢面试官的 email 总结 在职场竞争激烈的今天&#xff0c;写一…

mysql从每个分组中取特定条件行的全部内容

取每个task_id分组中更新日期最新的一行 采用join的方式完成 select a.task_id,a.theme_id,a.time_unix from table_xxx a join (select task_id,max(time_unix) as max_timefrom table_xxxwhere date < ${date}and date > ${date} - 3and theme_i…

linux网络编程--线程池UDP

目录 学习目标 1线程池 2.UDP通信 3本地socket通信 学习目标 了解线程池模型的设计思想能看懂线程池实现源码掌握tcp和udp的优缺点和使用场景说出udp服务器通信流程说出udp客户端通信流程独立实现udp服务器代码独立实现udp客户端代码熟练掌握本地套接字进行本地进程通信 1…