Commit 4a43c04f by ClassmateWang

2021-11-11 15:59 /Netty/黑马/4)NettyApi.md

parent 224f0c00
# 1)类加载器
# 1)类加载器
## 一、定义
`什么是类加载器?`
**Java 编译器**会将Java **代码**转化为**Java 虚拟机的源指令**,并将源指令**存储到.class文件**中,这里每个类文件都会包含某个类或者接口的定义和实现代码。
**Java 类加载器**会处理这些**虚拟机指令**,并将其翻译为**目标机器的机器语言**
## 二、类加载过程
类加载器的分类:
- 引导类加载器
- 加载系统类(往往从rt.jar 文件中进行加载)
- 通常使用C语言来编写,是虚拟机不可分割的一部分
- 没有ClassLoader 对象
- 扩展类加载器
- 从jre/lib/ext 加载标准的扩展jar
- 系统类加载器
- 用于加载应用类
- 从系统环境变量或者-classpath 命令行参数中指定的目录中查找类
在Oracle 的Java 语言实现中,扩展类加载器和系统类加载器都是通过Java来实现的,他们都是URLClassLoader 类的实例
`过程:`
> 注意:虚拟机只加载程序执行时需要的类文件
1. 虚拟机通过文件读取机制从磁盘或者网络加载MyProgram 类文件中的内容
2. 类的解析(加载某个类依赖的所有类):如果MyProgram 类拥有类型为另一个类的域,或者是拥有超类,则这些类文件也会被加载
3. 接着 虚拟机执行MyProgram 中的main 方法
4. 如果mai 方法或者main 调用的方法要用到更多的类,那么接下来就会加载这些类
## 三、类加载器的层次结构
类加载器都有一种父——子的层次结构,**除了引导类加载器外,每个类都有一个父类加载器**。且类加载器会为它的父类加载器提供给一个机会,以便加载任何给定的类,**并且只有其父类加载器加载失败时,它才会执行并加载该类**
> 比如说:
>
> ​ 如果要求系统类加载器加载一个系统类(java.util.ArrayList),它首先会要求扩展类加载器去加载,而扩展类加载器会要求引导类加载器去加载。
>
> 此时引导类加载器会找到并加载rt.jar 中的这个类,而不需要其他类加载器做更多的处理。
如果说对应的类被达成了jar包,那么可以直接的通过URLClassLoader 类的实例去加载这些类:
```java
URL url = new URL("file://path/to/plugin.jar");
URLClassLoader pluginLoader = new URLClassLoader(new URL[]{url});
Class<?> cl = pluginLoader.loadClass("mypackage.MyClass");
```
因为URLClassLoader 中并没有指定父类加载器,所以pluginLoader的父类加载器就是系统类加载器
每个线程都有一个类加载器的引用,称为上下文类加载器,主线程的上下文类加载器是系统类加载器,当新的线程创建时,这些新线程的类加载器就会被设置为创建他们的线程的类加载器,所以如果不做任何操作的话**所有线程的类加载器都将是系统类加载器**
`设置类加载器:`
```java
Thread t = Thread.currentThread();
t.setContextClassLoader(loader);
```
`获取类加载器:`
```java
Thread t = Thread.currentThread();
ClassLoader loader = t.getContextClassLoader();
/*通过获取的类加载器加载类*/
Class cl = loader.loadClass(className);
```
## 四、类加载器作为命名空间
在同一个虚拟机中其实是允许两个包名和类名都相同的类存在,因为类是由它的类全名和类加载器确定的,这项技术在加载来自多出的代码时非常有用。
可以通过不同的类加载器的实例去加载,就可以避免因为类名重复的情况引发的问题。**也就是说即使包名类名相同若使用不同的类加载器也能保证不冲突。**
# Java 虚拟机结构
# Java 虚拟机结构
\ No newline at end of file
# 4)Netty-api
# 4)Netty-api
......@@ -38,13 +38,15 @@ Netty 在Java 网络应用框架的地位就好比:Spring在JavaEE中的地位
## 二、Hello World
### 2.1 目标需求
### 目标需求:
开发一个简单的服务器端和客户端
- 客户端向服务器发送HelloWorld
- 服务器仅接收不返回,实现单向通信
### 实现:
导入依赖:
```xml
......@@ -170,9 +172,302 @@ public class HelloClient {
> - handler 理解为数据的处理工序
> - 工序有多道,合在一起就是pipline,pipline 负责发布事件(读、读取完成....),然后传播给每个handler,handler 只对自己感兴趣的事件进行处理(重写了相应事件的处理方法)
> - handler分为
> - Inbound
> - Outbound
> - Inbound 入站(输入时通过入站处理器处理)
> - Outbound 出战
> - pipline 流水线,对数据的处理有的时候一道工序不够有的时候需要多道工序,而这个就像流水线多道工序处理数据
> - 把eventLoop 理解为处理数据的工人
> - 工人可以管理多个Channel 的IO操作,并且一旦一个工人负责了某个channel,就要负责到底
> -
> - 工人可以管理多个Channel 的IO操作,并且一旦一个工人负责了某个channel,就要负责到底(目的是为了线程安全)
> - 工人可以执行IO操作,也可以进行普通的任务处理,每位工人有任务队列,队列里可以堆放多个channel 的待处理任务,任务分为普通任务和定时任务
> - 工人按照pipline 的顺序,依次按照handler 的规划(代码)处理数据,可以为每道工序指定不同的工人
## 三、组件
### 3.1EventLoop
`事件循环对象&事件循环组`
EventLoop 本质上是一个**单线程执行器**(同时维护了一个selector),里面的run方法**处理channel 上源源不断的IO事件**
它的继承关系比较复杂:
- 一条线是继承自j.u.c.ScheduledExecutorService 可以用于执行定时任务,同时也包含了线程池中的所有方法
- 另一条线是继承自netty自己的OrderedEventExecutor,有序的执行任务
- 提供了boolean isEventLoop(Thread thread)判断一个线程是否属于此EventLoop
- 提供了parent 方法看看自己属于哪个EventLoopGroup
`事件循环组:`
EventLoopGroup 就是一组EventLoop,channel 一般会调用EventLoopGroup 的register 方法来绑定其中的一个EventLoop,**后续这个Channel 上的IO事件都由此EventLoop 来处理**(保证了IO事件处理时的线程安全)
#### 处理普通事件
> 执行普通任务的意义在于:
>
> - 异步处理
> - 事件分发的时候可以通过这种方式,将接下来一段代码的执行从一个线程传递给另一个线程
```java
@Slf4j
public class TestEventLoop {
public static void main(String[] args) {
/**
* 1、创建事件循环组
* NioEventLoopGroup 可以用于处理IO事件,定时事件,普通任务
* DefaultEventLoopGroup 只能处理定时事件和普通任务,并不能处理IO事件
*
* 一个事件循环是一个线程来维护的
* 这里传入的参数是创建线程的个数,如果没有传入参数则线程默认数为1
*/
EventLoopGroup group = new NioEventLoopGroup(2);
/*2、group.next
* 循环获取下一个事件循环对象,这样就保证负载均衡
* 依次将不同的channel 注册到不同eventLoop 上
* 就类似于NIO对于两个worker 的处理
* */
group.next().submit(() -> {
/**
* 将事件提交给某个事件循环对象去处理(交给一个线程去处理)
* 实现一个异步处理
*/
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("ok");
});
log.debug("main");
}
}
```
![image-20211110152447645](https://wangnotes.oss-cn-beijing.aliyuncs.com/notesimage/image-20211110152447645.png)
- group.next
- 获取事件循环对象
- submit 向事件循环对象提交一个事件,一个事件循环对象通过一个线程来专门进行维护
- 也可以使用execute
#### 处理定时任务
> 使用意义:
>
> - 实现keep-live 的时候可以作为连接的保护而使用
> - 通过另一条线程规律执行某个线程
```java
/**
* 启动一个定时任务,并以一定的频率来执行
* 1、参数一是一个Runnable 接口类型的任务对象
* 2、参数二是初始延时事件
* 3、参数三是间隔时间
* 4、执行单位
*/
group.next().scheduleAtFixedRate(()->{
log.info("ok");
},0,1, TimeUnit.SECONDS);
```
![image-20211110153213782](https://wangnotes.oss-cn-beijing.aliyuncs.com/notesimage/image-20211110153213782.png)
#### 处理IO事件
```java
@Slf4j
public class EventLoopServer {
public static void main(String[] args) {
new ServerBootstrap()
.group(new NioEventLoopGroup())
.channel(NioServerSocketChannel.class)
/*初始化器*/
.childHandler(new ChannelInitializer<NioSocketChannel>() {
/**
* 这个方法会在connection 建立后调用
* @param nioSocketChannel 泛型对象
* @throws Exception
*/
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
/*向流水线中添加处理,这里添加一个自定义的处理器*/
nioSocketChannel.pipeline().addLast(new ChannelInboundHandlerAdapter(){
@Override
/*这里表示关注的是读操作
没有StringDecoder的时候
Object msg 是ByteBuf类型的数据*/
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
log.debug(buf.toString(StandardCharsets.UTF_8));
}
});
}
})
.bind(8080);
}
}
```
![image-20211111135551286](https://wangnotes.oss-cn-beijing.aliyuncs.com/notesimage/image-20211111135551286.png)
当服务器和客户端两个channel 建立连接之后,那么服务器会用同一个EventLoop 或者说同一个线程来处理这个客户端,建立一个绑定关系
![image-20211111135947864](https://wangnotes.oss-cn-beijing.aliyuncs.com/notesimage/image-20211111135947864.png)
但是并不是说有多少个channel 就会建立多少了eventLoop,eventLoop的数量是既定的,所以会将channel 循环的分给eventLoop,以达到负载均衡的效果,其次就是上面说的每一个eventLoop 对一个channel 都会负责到底
#### 分工细化一
- `创建两个eventLoopGroup 分别管理Boss 和Worker`
- BossEventLoop 中只会有一个线程因为它唯一去处理ServerSocketChannel,而只会有一个ServerSocketChannel
将eventLoop 的职责划分可以划的更细一些,划分为boss 和worker
.channel 会将ServerSocketChannel 绑定到一个EventLoop上
.childHandler 会将SocketChannel 绑定到参数二的EventLoopGroup 中的EventLoop 上,**初始化器针对的是已经绑定后的SocketChannel**
```java
new ServerBootstrap()
/**
* 划分为boss 和 worker
* 第一个boss 只负责ServerSocketChannel accept 事件
* 第二个worker 只负责socketChannel 上的读写事件 线程数默认是CPU核心数*2
* 这里并不需要将第一个EventLoopGroup 的线程数设为一
* 因为这里的serverSocketChannel 只会和第一个EventLoopGroup 中的一个进行绑定
* 而并没有多个ServerSocketChannel
* 也只会占用一个线程
*/
.group(new NioEventLoopGroup(),new NioEventLoopGroup())
.channel(NioServerSocketChannel.class)
```
#### 分工细化二
- 再创建一个EventLoopGroup 去处理耗时较长的操作,而不是用NioEventLoop (worker Thread) 去处理耗时较长的操作
- 避免繁杂逻辑操作影响io线程
- 当需要将两个自定义的handler 建立连接时需要在第一个handler 中调用context.fireChannelRead(msg) 将数据传递给下一个handler,这样才能将两个handler 串起来
- 这里在第一个handler 可以**实现条件判断看看是不是要走第二个handler**,调用了那句代码才会走到对应的handler 中
- 用指定的group 处理特殊情况的操作,两个用的线程是不同的,因为eventLoop 中线程的数量是有限的,而worker 线程要服务很多channel ,所以如果一个线程做比较消耗资源的操作就会降低其他channel 对资源的利用率,所以这里才会创建一个新的group 其实就是新的线程去处理对应的特殊操作
- eventLoop 就是**一个线程且带着一个selector**
```java
public static void main(String[] args) {
/*创建处理特殊事件的Group*/
EventLoopGroup group = new DefaultEventLoop();
new ServerBootstrap()
.group(new NioEventLoopGroup(),new NioEventLoopGroup())
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
nioSocketChannel.pipeline().addLast(new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
log.debug(buf.toString(StandardCharsets.UTF_8));
/*将消息传递给下一个handler*/
ctx.fireChannelRead(msg);
}
}).addLast(group,"handler2",new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
super.channelRead(ctx, msg);
}
});
}
})
.bind(8080);
}
```
#### 切换线程
`在流水线执行过程中,handler 执行是如何切换EventLoop的,例如:如何从NioEventLoop 切换到DefaultEventLoop 上执行的(换人)`
![image-20211111144854012](https://wangnotes.oss-cn-beijing.aliyuncs.com/notesimage/image-20211111144854012.png)
> 如果两个handler 绑定的是同一个线程(EventLoop)就可以直接调用,**如果绑定的是不同线程,而是会将要调用的代码封装为一个Runnable 对象中然后传递给下一个handler的线程去处理**
### 3.2Channel
channel 的主要作用:
- close()
- 用于关闭channel
- closeFuture()
- 用于处理channel 的关闭,close 之后的善后处理
- sync方法 是同步等待channel 的关闭
- 用addListener 方法是异步等待channel 的关闭
- pipline()
- 添加处理器
- write()
- 将数据写入
- 由于netty 的缓冲机制所以写入channel 后不会立马发出
- 需要达到一定数据量
- **需要执行flush 方法**
- writeAndFlush()
- 将数据写入并刷出
- **保证数据写入channel 后立马发出**
#### channelFuture
`用于处理建立连接后的结果(同步等待还是异步执行)`
带future 或者promise 的类型都是和异步方法进行使用的
> 客户端中 connect 方法的返回值是一个ChannelFuture 对象,如果不调用channelFuture.sync()的话后面的代码就无法正常执行:
分析:
```java
/*connect 是异步非阻塞方法,main方法只是调用,真正执行connect的是NIO线程*/
.connect(new InetSocketAddress("localhost", 8080));
// future.sync();
Channel channel = future.channel();
channel.writeAndFlush("Hello,world");
```
connect **是异步非阻塞**,main线程调用之后会立即返回,如果此时没有调用sync 方法会继续向下执行,下面的channel 对象会建立成功,但是实际上因为连接**并没有建立成功所以没有爆空指针错误**,而是代码执行无效。
主线程其实并不直到channel 下的连接是否建立好,因为建立连接的是NIO线程而不是主线程
这里connection 的建立需要一些事件,**必须阻塞直到连接建立成功,再去继续执行才能执行成功。**
**<u>正确处理:</u>**
- channelFuture.sync()
- 谁发起的调用谁来对建立好的连接进行后续操作
- 可以通过使用sync方法来同步结果
- 阻塞住当前线程(调用sync方法的线程),直到nio线程连接建立完毕
- 相当于主线程主动的去等待,实际执行针对连接建立后对象操作的还是主线程
- channelFuture.addListener(回调对象) 异步处理结果
- 通过回调对象处理,将连接建立后的对象操作扔给nio 线程,由NIO线程去处理
- NIO线程建立好了,就会主动去调用operationComplete 方法,
```java
//// future.sync();
// Channel channel = future.channel();
// channel.writeAndFlush("Hello,world");
future.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
Channel channel = channelFuture.channel();
channel.writeAndFlush("Hello,world");
}
});
```
处理建立连接完成之后操作的是NIO线程而不是主线程(处理连接建立的是netty 自己配置完成的NIO线程)
### 3.3Future&Promise
### 3.4Handler&Pipeline
### 3.5ByteBuf
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment