NIO 基本概念
阻塞(Block)与非阻塞(Non-Block)
阻塞和非阻塞是进程在访问数据的时候,数据是否准备就绪的一种处理方式,当数据没有准备的时候。
阻塞:往往需要等待缓冲区中的数据准备好过后才处理其他的事情,否则一直等待在那里。
非阻塞:当我们的进程访问我们的数据缓冲区的时候,如果数据没有准备好则直接返回,不会等待。如果数据已经准备好,也直接返回。
阻塞 IO :
非阻塞 IO :
同步(Synchronous)与异步(Asynchronous)
同步和异步都是基于应用程序和操作系统处理 IO 事件所采用的方式。比如
**同步:**是应用程序要直接参与 IO 读写的操作。
**异步:**所有的 IO 读写交给操作系统去处理,应用程序只需要等待通知。
同步方式在处理 IO 事件的时候,必须阻塞在某个方法上面等待我们的 IO 事件完成(阻塞 IO 事件或者通过轮询 IO事件的方式),对于异步来说,所有的 IO 读写都交给了操作系统。这个时候,我们可以去做其他的事情,并不需要去完成真正的 IO 操作,当操作完成 IO 后,会给我们的应用程序一个通知。
所以异步相比较于同步带来的直接好处就是在我们处理IO数据的时候,异步的方式我们可以把这部分等待所消耗的资源用于处理其他事务,提升我们服务自身的性能。
**同步 IO **:
**异步 IO **:
Java BIO与NIO对比
BIO(传统IO):
BIO是一个同步并阻塞的IO模式,传统的 java.io 包,它基于流模型实现,提供了我们最熟知的一些 IO 功能,比如File抽象、输入输出流等。交互方式是同步、阻塞的方式,也就是说,在读取输入流或者写入输出流时,在读、写动作完成之前,线程会一直阻塞在那里,它们之间的调用是可靠的线性顺序。
NIO(Non-blocking/New I/O)
NIO 是一种同步非阻塞的 I/O 模型,于 Java 1.4 中引入,对应 java.nio 包,提供了 Channel , Selector,Buffer 等抽象。NIO 中的 N 可以理解为 Non-blocking,不单纯是 New。它支持面向缓冲的,基于通道的 I/O 操作方法。 NIO 提供了与传统 BIO 模型中的 Socket 和 ServerSocket 相对应的 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发
BIO与NIO的对比
IO模型 |
BIO |
NIO |
通信 |
面向流 |
面向缓冲 |
处理 |
阻塞 IO |
非阻塞 IO |
触发 |
无 |
选择器 |
NIO 的 Server 通信的简单模型:
BIO 的 Server 通信的简单模型:
NIO的特点:
- 一个线程可以处理多个通道,减少线程创建数量;
- 读写非阻塞,节约资源:没有可读/可写数据时,不会发生阻塞导致线程资源的浪费
Reactor 模型
单线程的 Reactor 模型
多线程的 Reactor 模型
多线程主从 Reactor 模型
Netty 基础概念
Netty 简介
Netty 是一个 NIO 客户端服务器框架,可快速轻松地开发网络应用程序,例如协议服务器和客户端。它极大地简化和简化了网络编程,例如 TCP 和 UDP 套接字服务器。
Netty 执行流程
Netty 核心组件
Channel
Channel是 Java NIO 的一个基本构造。可以看作是传入或传出数据的载体。因此,它可以被打开或关闭,连接或者断开连接。
EventLoop 与 EventLoopGroup
EventLoop 定义了Netty的核心抽象,用来处理连接的生命周期中所发生的事件,在内部,将会为每个Channel分配一个EventLoop。
EventLoopGroup 是一个 EventLoop 池,包含很多的 EventLoop。
Netty 为每个 Channel 分配了一个 EventLoop,用于处理用户连接请求、对用户请求的处理等所有事件。EventLoop 本身只是一个线程驱动,在其生命周期内只会绑定一个线程,让该线程处理一个 Channel 的所有 IO 事件。
一个 Channel 一旦与一个 EventLoop 相绑定,那么在 Channel 的整个生命周期内是不能改变的。一个 EventLoop 可以与多个 Channel 绑定。即 Channel 与 EventLoop 的关系是 n:1,而 EventLoop 与线程的关系是 1:1。
ServerBootstrap 与 Bootstrap
Bootstarp 和 ServerBootstrap 被称为引导类,指对应用程序进行配置,并使他运行起来的过程。Netty处理引导的方式是使你的应用程序和网络层相隔离。
Bootstrap 是客户端的引导类,Bootstrap 在调用 bind()(连接UDP)和 connect()(连接TCP)方法时,会新创建一个 Channel,仅创建一个单独的、没有父 Channel 的 Channel 来实现所有的网络交换。
ServerBootstrap 是服务端的引导类,ServerBootstarp 在调用 bind() 方法时会创建一个 ServerChannel 来接受来自客户端的连接,并且该 ServerChannel 管理了多个子 Channel 用于同客户端之间的通信。
ChannelHandler 与 ChannelPipeline
ChannelHandler 是对 Channel 中数据的处理器,这些处理器可以是系统本身定义好的编解码器,也可以是用户自定义的。这些处理器会被统一添加到一个 ChannelPipeline 的对象中,然后按照添加的顺序对 Channel 中的数据进行依次处理。
ChannelFuture
Netty 中所有的 I/O 操作都是异步的,即操作不会立即得到返回结果,所以 Netty 中定义了一个 ChannelFuture 对象作为这个异步操作的“代言人”,表示异步操作本身。如果想获取到该异步操作的返回值,可以通过该异步操作对象的addListener() 方法为该异步操作添加监 NIO 网络编程框架 Netty 听器,为其注册回调:当结果出来后马上调用执行。
Netty 的异步编程模型都是建立在 Future 与回调概念之上的。
启动Demo
以下是一个完整的 Netty 启动 Demo,包括一个简单的 服务端 和 客户端 实现。这个 Demo 展示了如何使用 Netty 构建一个基本的网络应用。
1. Netty 服务端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
public class NettyServer {
public static void main(String[] args) throws InterruptedException {
// 创建两个 EventLoopGroup
EventLoopGroup bossGroup = new NioEventLoopGroup(1); // 用于处理客户端连接
EventLoopGroup workerGroup = new NioEventLoopGroup(); // 用于处理 I/O 操作
try {
// 创建 ServerBootstrap
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class) // 使用 NIO 传输通道
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
// 添加字符串编解码器
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
// 添加自定义的处理器
pipeline.addLast(new ServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128) // 设置连接队列大小
.childOption(ChannelOption.SO_KEEPALIVE, true); // 保持长连接
// 绑定端口并启动服务端
ChannelFuture future = serverBootstrap.bind(8080).sync();
System.out.println("Netty 服务端启动成功,监听端口:8080");
// 等待服务端关闭
future.channel().closeFuture().sync();
} finally {
// 优雅关闭 EventLoopGroup
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
// 自定义的处理器
private static class ServerHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
// 处理接收到的消息
System.out.println("服务端收到消息: " + msg);
// 回复客户端
ctx.writeAndFlush("Hello, 客户端!");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
// 处理异常
cause.printStackTrace();
ctx.close();
}
}
}
|
2. Netty 客户端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
public class NettyClient {
public static void main(String[] args) throws InterruptedException {
// 创建 EventLoopGroup
EventLoopGroup group = new NioEventLoopGroup();
try {
// 创建 Bootstrap
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class) // 使用 NIO 传输通道
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
// 添加字符串编解码器
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
// 添加自定义的处理器
pipeline.addLast(new ClientHandler());
}
});
// 连接服务端
ChannelFuture future = bootstrap.connect("127.0.0.1", 8080).sync();
System.out.println("Netty 客户端启动成功,连接到服务端");
// 发送消息
future.channel().writeAndFlush("Hello, 服务端!");
// 等待客户端关闭
future.channel().closeFuture().sync();
} finally {
// 优雅关闭 EventLoopGroup
group.shutdownGracefully();
}
}
// 自定义的处理器
private static class ClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
// 处理接收到的消息
System.out.println("客户端收到消息: " + msg);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
// 处理异常
cause.printStackTrace();
ctx.close();
}
}
}
|
3. 运行步骤
- 启动服务端:
- 运行
NettyServer
类的 main
方法。
- 服务端会监听
8080
端口,等待客户端连接。
- 启动客户端:
- 运行
NettyClient
类的 main
方法。
- 客户端会连接到服务端,并发送一条消息。
- 查看输出:
- 服务端会打印:
服务端收到消息: Hello, 服务端!
。
- 客户端会打印:
客户端收到消息: Hello, 客户端!
。
4. 代码说明
- 服务端:
- 使用
ServerBootstrap
启动服务端。
- 监听
8080
端口,处理客户端连接和消息。
- 使用
StringDecoder
和 StringEncoder
处理字符串消息。
- 自定义
ServerHandler
处理业务逻辑。
- 客户端:
- 使用
Bootstrap
启动客户端。
- 连接到服务端的
8080
端口,发送消息。
- 使用
StringDecoder
和 StringEncoder
处理字符串消息。
- 自定义
ClientHandler
处理业务逻辑。