1 ChannelFuture
io.netty.channel.ChannelFuture
是 Netty 框架中一个非常重要的接口,它代表了一个异步 Channel
I/O 操作的结果。由于 Netty 中的所有 I/O 操作都是异步的,因此任何 I/O 调用都会立即返回,而不会保证在调用结束时请求的 I/O 操作已经完成。相反,你会得到一个 ChannelFuture
实例,它可以让你了解 I/O 操作的结果或状态。以下是对 ChannelFuture
类的详细解析:
接口定义
public interface ChannelFuture extends Future<Void> {
// ...
}
ChannelFuture
接口继承自 io.netty.util.concurrent.Future<Void>
,这意味着它具备了通用异步操作结果的基本特性,同时专门针对 Channel
的 I/O 操作进行了扩展。
状态模型
ChannelFuture
有两种主要状态:未完成(uncompleted)和已完成(completed)。
- 未完成:当一个 I/O 操作开始时,会创建一个新的
ChannelFuture
对象,此时它处于未完成状态。这意味着 I/O 操作尚未结束,它既没有成功、失败,也没有被取消。 - 已完成:当 I/O 操作成功完成、失败或被取消时,
ChannelFuture
会被标记为已完成,并包含更具体的信息,如失败的原因。
状态转换图如下:
+---------------------------+
| Completed successfully |
+---------------------------+
+----> isDone() = true |
+--------------------------+ | | isSuccess() = true |
| Uncompleted | | +===========================+
+--------------------------+ | | Completed with failure |
| isDone() = false | | +---------------------------+
| isSuccess() = false |----+----> isDone() = true |
| isCancelled() = false | | | cause() = non-null |
| cause() = null | | +===========================+
+--------------------------+ | | Completed by cancellation |
| +---------------------------+
+----> isDone() = true |
| isCancelled() = true |
+---------------------------+
核心方法
1. Channel channel()
返回与该 ChannelFuture
关联的 Channel
对象,即 I/O 操作发生的通道。
Channel channel = future.channel();
2. 监听相关方法
这些方法用于添加和移除 GenericFutureListener
,当 ChannelFuture
完成时,监听器会被通知。
@Override
ChannelFuture addListener(GenericFutureListener<? extends Future<? super Void>> listener);
@Override
ChannelFuture addListeners(GenericFutureListener<? extends Future<? super Void>>... listeners);
@Override
ChannelFuture removeListener(GenericFutureListener<? extends Future<? super Void>> listener);
@Override
ChannelFuture removeListeners(GenericFutureListener<? extends Future<? super Void>>... listeners);
示例:
future.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
// 操作成功处理逻辑
} else {
// 操作失败处理逻辑
Throwable cause = future.cause();
cause.printStackTrace();
}
}
});
3. 同步相关方法
这些方法用于等待 ChannelFuture
完成,并处理可能的异常。
@Override
ChannelFuture sync() throws InterruptedException;
@Override
ChannelFuture syncUninterruptibly();
sync()
:等待ChannelFuture
完成,如果操作失败则抛出异常,该方法会响应线程中断。syncUninterruptibly()
:与sync()
类似,但不会响应线程中断。
示例:
try {
future.sync();
// 操作成功处理逻辑
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
// 线程被中断处理逻辑
} catch (Exception e) {
// 操作失败处理逻辑
}
4. 等待相关方法
这些方法用于等待 ChannelFuture
完成,可以指定超时时间。
@Override
ChannelFuture await() throws InterruptedException;
@Override
ChannelFuture awaitUninterruptibly();
boolean await(long timeout, TimeUnit unit) throws InterruptedException;
boolean await(long timeoutMillis) throws InterruptedException;
boolean awaitUninterruptibly(long timeout, TimeUnit unit);
boolean awaitUninterruptibly(long timeoutMillis);
await()
:等待ChannelFuture
完成,该方法会响应线程中断。awaitUninterruptibly()
:与await()
类似,但不会响应线程中断。await(long timeout, TimeUnit unit)
和await(long timeoutMillis)
:等待ChannelFuture
在指定时间内完成,返回操作是否在超时时间内完成,该方法会响应线程中断。awaitUninterruptibly(long timeout, TimeUnit unit)
和awaitUninterruptibly(long timeoutMillis)
:与上述方法类似,但不会响应线程中断。
示例:
try {
if (future.await(5, TimeUnit.SECONDS)) {
if (future.isSuccess()) {
// 操作成功处理逻辑
} else {
// 操作失败处理逻辑
Throwable cause = future.cause();
cause.printStackTrace();
}
} else {
// 操作超时处理逻辑
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
// 线程被中断处理逻辑
}
5. boolean isVoid()
返回该 ChannelFuture
是否为无效的未来,如果是,则不允许调用一些特定的方法,如 addListener
、await
等。
最佳实践建议
1. 优先使用 addListener
而非 await
addListener
是非阻塞的,它只是将指定的监听器添加到 ChannelFuture
中,当与该未来关联的 I/O 操作完成时,I/O 线程会通知监听器。这种方式性能和资源利用率最佳,因为它不会阻塞任何线程。而 await
是阻塞操作,调用者线程会一直阻塞直到操作完成,可能会导致不必要的线程阻塞和较高的线程间通知成本,甚至在特定情况下可能会导致死锁。
示例:
// 推荐方式
future.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
// 操作成功处理逻辑
} else {
// 操作失败处理逻辑
Throwable cause = future.cause();
cause.printStackTrace();
}
}
});
// 不推荐方式
try {
future.await();
if (future.isSuccess()) {
// 操作成功处理逻辑
} else {
// 操作失败处理逻辑
Throwable cause = future.cause();
cause.printStackTrace();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
// 线程被中断处理逻辑
}
2. 避免在 ChannelHandler
中调用 await
ChannelHandler
中的事件处理方法通常由 I/O 线程调用,如果在这些方法中调用 await
,可能会导致死锁,因为 await
会阻塞 I/O 操作。
示例:
// 错误示例
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ChannelFuture future = ctx.channel().close();
future.awaitUninterruptibly();
// 执行关闭后的操作
}
// 正确示例
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ChannelFuture future = ctx.channel().close();
future.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
// 执行关闭后的操作
}
});
}
实现类
Netty 提供了多个 ChannelFuture
的实现类,如 CompleteChannelFuture
、FailedChannelFuture
、SucceededChannelFuture
等,用于表示不同状态的 ChannelFuture
。这些实现类在内部维护了 ChannelFuture
的状态和结果,并实现了接口定义的各种方法。
2 io.netty.channel.EventLoop
它主要负责处理 Channel
的所有 I/O 操作。下面将从接口定义、主要功能、核心方法、与相关类的关系以及使用示例几个方面对 EventLoop
进行详细解析。
接口定义
public interface EventLoop extends OrderedEventExecutor, EventLoopGroup {
@Override
EventLoopGroup parent();
}
从接口定义可以看出,EventLoop
继承自 OrderedEventExecutor
和 EventLoopGroup
。OrderedEventExecutor
确保任务按顺序执行,而 EventLoopGroup
表示一组 EventLoop
。parent()
方法用于返回该 EventLoop
所属的 EventLoopGroup
。
主要功能
- I/O 操作处理:
EventLoop
的核心功能是处理Channel
的 I/O 操作,包括连接建立、数据读写、连接关闭等。它负责监控Channel
的状态变化,并在有 I/O 事件发生时执行相应的处理逻辑。 - 任务调度:除了处理 I/O 操作,
EventLoop
还可以调度和执行普通的任务。这些任务可以是用户自定义的定时任务或异步任务。 - 线程管理:每个
EventLoop
通常与一个线程关联,确保Channel
的操作在同一个线程中执行,避免了多线程并发带来的复杂性和同步问题。
核心方法
虽然 EventLoop
接口中只定义了 parent()
方法,但它继承了 OrderedEventExecutor
和 EventLoopGroup
的许多重要方法:
继承自 OrderedEventExecutor
的方法
execute(Runnable task)
:将一个任务提交到EventLoop
中执行。该任务会在EventLoop
关联的线程中被调度执行。
eventLoop.execute(() -> {
// 执行具体的任务逻辑
System.out.println("Task is being executed in the EventLoop.");
});
submit(Callable<T> task)
:提交一个带有返回值的任务到EventLoop
中执行,并返回一个Future
对象,用于获取任务的执行结果。
Future<Integer> future = eventLoop.submit(() -> {
// 执行具体的任务逻辑并返回结果
return 42;
});
继承自 EventLoopGroup
的方法
next()
:返回EventLoopGroup
中的下一个EventLoop
。通常用于在多个EventLoop
之间进行负载均衡。
EventLoop nextEventLoop = eventLoopGroup.next();
register(Channel channel)
:将一个Channel
注册到EventLoop
中,以便EventLoop
可以处理该Channel
的 I/O 操作。
ChannelFuture registerFuture = eventLoop.register(channel);
与相关类的关系
- EventLoopGroup:
EventLoop
是EventLoopGroup
的成员,一个EventLoopGroup
可以包含多个EventLoop
。EventLoopGroup
负责管理和分配EventLoop
,并提供了统一的接口来操作这些EventLoop
。 - Channel:
EventLoop
与Channel
紧密相关,每个Channel
都需要注册到一个EventLoop
中,以便EventLoop
可以处理该Channel
的 I/O 操作。一个EventLoop
可以同时处理多个Channel
的 I/O 操作。
使用示例
以下是一个简单的使用 EventLoop
的示例,展示了如何创建 EventLoopGroup
、获取 EventLoop
并提交任务:
import io.netty.channel.EventLoop;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import java.util.concurrent.TimeUnit;
public class EventLoopExample {
public static void main(String[] args) {
// 创建一个 NioEventLoopGroup,包含 2 个 EventLoop
EventLoopGroup group = new NioEventLoopGroup(2);
try {
// 获取一个 EventLoop
EventLoop eventLoop = group.next();
// 提交一个任务到 EventLoop 中执行
eventLoop.execute(() -> {
System.out.println("Task is being executed in the EventLoop.");
});
// 提交一个定时任务到 EventLoop 中执行
eventLoop.schedule(() -> {
System.out.println("Scheduled task is being executed after 2 seconds.");
}, 2, TimeUnit.SECONDS);
// 保持主线程存活一段时间,以便观察任务的执行结果
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 优雅地关闭 EventLoopGroup
group.shutdownGracefully();
}
}
}
在这个示例中,我们首先创建了一个 NioEventLoopGroup
,它包含 2 个 EventLoop
。然后,我们通过 next()
方法获取一个 EventLoop
,并向该 EventLoop
提交了一个普通任务和一个定时任务。最后,我们优雅地关闭了 EventLoopGroup
。
综上所述,EventLoop
是 Netty 异步 I/O 模型的核心组件之一,它负责处理 Channel
的 I/O 操作和任务调度,为 Netty 应用程序提供了高效、可靠的并发处理能力。
3 io.netty.channel.EventLoopGroup
它在管理和调度 EventLoop
实例时发挥着核心作用,这些 EventLoop
实例负责处理 Channel
的所有 I/O 操作。下面我们将从多个方面对 EventLoopGroup
进行详细解析。
接口定义
public interface EventLoopGroup extends EventExecutorGroup {
/**
* Return the next {@link EventLoop} to use
*/
@Override
EventLoop next();
/**
* Register a {@link Channel} with this {@link EventLoop}. The returned {@link ChannelFuture}
* will get notified once the registration was complete.
*/
ChannelFuture register(Channel channel);
/**
* Register a {@link Channel} with this {@link EventLoop} using a {@link ChannelFuture}. The passed
* {@link ChannelFuture} will get notified once the registration was complete and also will get returned.
*/
ChannelFuture register(ChannelPromise promise);
/**
* Register a {@link Channel} with this {@link EventLoop}. The passed {@link ChannelFuture}
* will get notified once the registration was complete and also will get returned.
*
* @deprecated Use {@link #register(ChannelPromise)} instead.
*/
@Deprecated
ChannelFuture register(Channel channel, ChannelPromise promise);
}
主要功能
- 继承自
EventExecutorGroup
:EventLoopGroup
继承自EventExecutorGroup
,这意味着它拥有EventExecutorGroup
的所有功能,如任务执行、任务调度、生命周期管理等。 - 获取下一个
EventLoop
:next()
方法用于返回下一个可用的EventLoop
实例。在多线程环境下,EventLoopGroup
会负责管理多个EventLoop
实例,并根据一定的策略(如轮询)选择一个EventLoop
来处理新的Channel
。 Channel
注册:register()
方法用于将Channel
注册到EventLoop
中。注册完成后,Channel
的所有 I/O 操作都将由该EventLoop
负责处理。注册操作是异步的,返回的ChannelFuture
可以用于监听注册操作的完成状态。
主要实现类
-
MultithreadEventLoopGroup
这是一个抽象类,它是多个EventLoop实现的基础。它使用多个线程来处理Channel的 I/O 操作,默认线程数通常为 CPU 核心数的两倍。
public abstract class MultithreadEventLoopGroup extends MultithreadEventExecutorGroup implements EventLoopGroup {
// …
}
2. `NioEventLoopGroup`
:基于 Java NIO(Non-blocking I/O)实现的EventLoopGroup,用于处理基于Selector的Channel。它使用多个线程来处理Channel的 I/O 操作,适用于大多数网络编程场景
```java
@Deprecated
public class NioEventLoopGroup extends MultiThreadIoEventLoopGroup implements IoEventLoopGroup {
// ...
}
-
EpollEventLoopGroup
基于 Linux 的epoll机制实现的EventLoopGroup,只能在 Linux 系统上使用。它提供了比 NIO 更高的性能,特别适用于高并发的网络编程场景。
@Deprecated public final class EpollEventLoopGroup extends MultiThreadIoEventLoopGroup { // ... }
-
OioEventLoopGroup
:基于 Java OIO(Blocking I/O)实现的EventLoopGroup,每个Channel
由一个独立的线程处理。由于 OIO 是阻塞式 I/O,这种实现方式在高并发场景下性能较差,已被弃用。
@Deprecated public class OioEventLoopGroup extends ThreadPerChannelEventLoopGroup { // ... }
使用示例
以下是一个简单的 Netty 服务器示例,展示了如何使用 NioEventLoopGroup
:
public class NettyServer {
private final int port;
public NettyServer(int port) {
this.port = port;
}
public void run() throws Exception {
// 创建两个 EventLoopGroup 实例
EventLoopGroup bossGroup = new NioEventLoopGroup(); // 用于处理客户端连接
EventLoopGroup workerGroup = new NioEventLoopGroup(); // 用于处理网络读写
try {
// 创建 ServerBootstrap 实例
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
// 添加 ChannelHandler 到 ChannelPipeline
// ch.pipeline().addLast(new YourServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
// 绑定端口并开始接收连接
ChannelFuture f = b.bind(port).sync();
// 等待服务器关闭
f.channel().closeFuture().sync();
} finally {
// 优雅地关闭 EventLoopGroup
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port = 8080;
new NettyServer(port).run();
}
}
4 io.netty.channel.ChannelOutboundInvoker
io.netty.channel.ChannelOutboundInvoker
是 Netty 框架中一个关键的接口,操作,这些操作通常涉及将数据从应用程序发送到网络。以下是对该接口的详细解析:
接口概述
ChannelOutboundInvoker
接口定义了一系列方法,用于发起各种出站操作,如绑定地址、连接远程服务器、断开连接、关闭通道等。这些操作会在 ChannelPipeline
中触发相应的 ChannelOutboundHandler
处理。
主要方法及功能
1. 绑定操作
default ChannelFuture bind(SocketAddress localAddress) {
return bind(localAddress, newPromise());
}
ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise);
- 功能:请求将
Channel
绑定到指定的本地地址。bind
方法会触发ChannelPipeline
中下一个ChannelOutboundHandler
的bind
方法。 - 参数:
localAddress
:要绑定的本地地址。promise
:用于通知操作完成的ChannelPromise
对象。
- 返回值:一个
ChannelFuture
对象,用于监听绑定操作的完成状态。
2. 连接操作
default ChannelFuture connect(SocketAddress remoteAddress) {
return connect(remoteAddress, newPromise());
}
default ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) {
return connect(remoteAddress, localAddress, newPromise());
}
ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise);
ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise);
- 功能:请求将
Channel
连接到指定的远程地址。如果指定了本地地址,则在连接时绑定到该本地地址。连接操作会触发ChannelPipeline
中下一个ChannelOutboundHandler
的connect
方法。 - 参数:
remoteAddress
:要连接的远程地址。localAddress
:可选的本地地址。promise
:用于通知操作完成的ChannelPromise
对象。
- 返回值:一个
ChannelFuture
对象,用于监听连接操作的完成状态。
3. 断开连接操作
default ChannelFuture disconnect() {
return disconnect(newPromise());
}
ChannelFuture disconnect(ChannelPromise promise);
- 功能:请求断开
Channel
与远程对等方的连接。断开连接操作会触发ChannelPipeline
中下一个ChannelOutboundHandler
的disconnect
方法。 - 参数:
promise
:用于通知操作完成的ChannelPromise
对象。
- 返回值:一个
ChannelFuture
对象,用于监听断开连接操作的完成状态。
4. 关闭操作
default ChannelFuture close() {
return close(newPromise());
}
ChannelFuture close(ChannelPromise promise);
- 功能:请求关闭
Channel
。关闭后,Channel
不能再被重用。关闭操作会触发ChannelPipeline
中下一个ChannelOutboundHandler
的close
方法。 - 参数:
promise
:用于通知操作完成的ChannelPromise
对象。
- 返回值:一个
ChannelFuture
对象,用于监听关闭操作的完成状态。
5. 取消注册操作
default ChannelFuture deregister() {
return deregister(newPromise());
}
ChannelFuture deregister(ChannelPromise promise);
- 功能:请求将
Channel
从之前分配的EventExecutor
中取消注册。取消注册操作会触发ChannelPipeline
中下一个ChannelOutboundHandler
的deregister
方法。 - 参数:
promise
:用于通知操作完成的ChannelPromise
对象。
- 返回值:一个
ChannelFuture
对象,用于监听取消注册操作的完成状态。
6. 读取操作
ChannelOutboundInvoker read();
- 功能:请求从
Channel
读取数据到第一个入站缓冲区。如果读取到数据,会触发ChannelInboundHandler
的channelRead
事件;读取完成后,会触发channelReadComplete
事件。读取操作会触发ChannelPipeline
中下一个ChannelOutboundHandler
的read
方法。 - 返回值:返回
ChannelOutboundInvoker
本身,以便进行链式调用。
使用示例
以下是一个简单的示例,展示了如何使用 ChannelOutboundInvoker
进行连接和关闭操作:
public class ChannelOutboundInvokerExample {
public static void main(String[] args) throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// 添加 ChannelHandler
}
});
// 连接到远程服务器
ChannelFuture connectFuture = bootstrap.connect(new InetSocketAddress("example.com", 80));
connectFuture.sync();
Channel channel = connectFuture.channel();
// 关闭 Channel
ChannelFuture closeFuture = channel.close();
closeFuture.sync();
} finally {
group.shutdownGracefully();
}
}
}
5 io.netty.channel.ChannelPromise
它继承自 ChannelFuture
和 Promise<Void>
,代表一个可写的 ChannelFuture
,主要用于管理和跟踪 Channel
操作的异步结果。以下是对其详细解析:
作用概述
ChannelPromise
的核心作用是提供一种机制,让开发者可以异步地处理 Channel
相关操作的结果。在 Netty 中,许多 Channel
操作(如绑定、连接、读写等)都是异步执行的,这意味着调用这些操作的方法会立即返回,而实际的操作结果会在未来的某个时间点完成。ChannelPromise
允许开发者注册监听器,在操作完成时得到通知,还可以阻塞当前线程直到操作完成。
主要方法及功能
1. 状态设置方法
ChannelPromise setSuccess(Void result);
ChannelPromise setSuccess();
boolean trySuccess();
ChannelPromise setFailure(Throwable cause);
- 功能:这些方法用于设置
ChannelPromise
的状态。setSuccess
方法将Promise
的状态设置为成功,setFailure
方法将其设置为失败。trySuccess
方法尝试将Promise
的状态设置为成功,如果已经被设置过,则返回false
。 - 示例:
ChannelPromise promise = channel.newPromise();
if (someCondition) {
promise.setSuccess();
} else {
promise.setFailure(new RuntimeException("Operation failed"));
}
2. 监听器管理方法
ChannelPromise addListener(GenericFutureListener<? extends Future<? super Void>> listener);
ChannelPromise addListeners(GenericFutureListener<? extends Future<? super Void>>... listeners);
ChannelPromise removeListener(GenericFutureListener<? extends Future<? super Void>> listener);
ChannelPromise removeListeners(GenericFutureListener<? extends Future<? super Void>>... listeners);
- 功能:这些方法用于添加和移除
ChannelPromise
的监听器。当Promise
的状态变为成功或失败时,所有注册的监听器都会被通知。 - 示例:
ChannelPromise promise = channel.newPromise();
promise.addListener(future -> {
if (future.isSuccess()) {
System.out.println("Operation succeeded");
} else {
System.err.println("Operation failed: " + future.cause());
}
});
3. 同步方法
ChannelPromise sync() throws InterruptedException;
ChannelPromise syncUninterruptibly();
ChannelPromise await() throws InterruptedException;
ChannelPromise awaitUninterruptibly();
- 功能:这些方法用于阻塞当前线程,直到
ChannelPromise
的状态变为成功或失败。sync
和await
方法会抛出InterruptedException
,如果线程在等待过程中被中断;syncUninterruptibly
和awaitUninterruptibly
方法则不会抛出该异常。 - 示例:
ChannelPromise promise = channel.newPromise();
try {
promise.sync();
System.out.println("Operation completed");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
4. 其他方法
Channel channel();
ChannelPromise unvoid();
- 功能:
channel
方法返回与该ChannelPromise
关联的Channel
。unvoid
方法用于将一个void
类型的ChannelPromise
转换为普通的ChannelPromise
。
#### 主要实现类
1. DefaultChannelPromise
public class DefaultChannelPromise extends DefaultPromise<Void> implements ChannelPromise, FlushCheckpoint {
// ...
}
- 功能:
DefaultChannelPromise
是ChannelPromise
的默认实现类。它继承自DefaultPromise<Void>
,并实现了FlushCheckpoint
接口。通常建议使用Channel.newPromise()
方法来创建DefaultChannelPromise
实例。
2. VoidChannelPromise
@UnstableApi
public final class VoidChannelPromise extends AbstractFuture<Void> implements ChannelPromise {
// ...
}
- 功能:
VoidChannelPromise
是一个特殊的ChannelPromise
实现,它表示一个不可用的Promise
。当调用其方法时,通常会抛出IllegalStateException
异常。
使用示例
以下是一个简单的示例,展示了如何使用 ChannelPromise
来管理 Channel
的连接操作:
public class ChannelPromiseExample {
public static void main(String[] args) throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new SimpleChannelInboundHandler<Object>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
// 处理接收到的数据
}
});
}
});
// 创建一个 ChannelPromise
ChannelPromise promise = bootstrap.config().group().next().newPromise();
// 发起连接操作
ChannelFuture connectFuture = bootstrap.connect(new InetSocketAddress("example.com", 80));
connectFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
promise.setSuccess();
} else {
promise.setFailure(future.cause());
}
}
});
// 等待连接操作完成
promise.sync();
if (promise.isSuccess()) {
Channel channel = connectFuture.channel();
// 连接成功,进行后续操作
} else {
System.err.println("Connection failed: " + promise.cause());
}
} finally {
group.shutdownGracefully();
}
}
}
6 io.netty.channel.Channel
它代表了一个与网络套接字或能够执行 I/O 操作(如读、写、连接和绑定)的组件的连接点。以下是对 Channel
接口的详细解析:
接口概述
Channel
接口为用户提供了对通道的各种操作和状态的访问,包括通道的当前状态(如是否打开、是否连接)、通道的配置参数、通道支持的 I/O 操作以及处理与通道相关的所有 I/O 事件和请求的 ChannelPipeline
。
主要功能
1. 提供通道状态信息
ChannelId id()
:返回该通道的全局唯一标识符。boolean isOpen()
:判断通道是否打开,若打开则可能在后续变得活跃。boolean isRegistered()
:判断通道是否已注册到EventLoop
。boolean isActive()
:判断通道是否活跃且已连接。
2. 获取通道相关对象
EventLoop eventLoop()
:返回该通道注册到的EventLoop
。Channel parent()
:返回该通道的父通道,若没有则返回null
。ChannelConfig config()
:返回该通道的配置信息。Unsafe unsafe()
:返回一个供内部使用的对象,提供不安全的操作。ChannelPipeline pipeline()
:返回分配给该通道的ChannelPipeline
。ByteBufAllocator alloc()
:返回分配ByteBuf
时使用的ByteBufAllocator
。
3. 获取通道地址信息
SocketAddress localAddress()
:返回该通道绑定的本地地址,若未绑定则返回null
。SocketAddress remoteAddress()
:返回该通道连接的远程地址,若未连接则返回null
。对于能接收任意远程地址消息的通道(如DatagramChannel
),需使用DatagramPacket#recipient()
来确定接收消息的来源。
4. 处理通道关闭
ChannelFuture closeFuture()
:返回一个ChannelFuture
,当该通道关闭时会得到通知,此方法总是返回相同的未来实例。ChannelFuture close(ChannelPromise promise)
:关闭通道,并使用ChannelPromise
来通知操作结果。
5. 判断通道可写性
boolean isWritable()
:判断 I/O 线程是否会立即执行请求的写操作。若返回false
,则任何写请求都会被排队,直到 I/O 线程准备好处理这些请求。long bytesBeforeUnwritable()
:返回在isWritable()
返回false
之前还能写入的字节数,该值始终为非负,若isWritable()
为false
则返回 0。long bytesBeforeWritable()
:返回在isWritable()
返回true
之前,底层缓冲区必须释放的字节数,该值始终为非负,若isWritable()
为true
则返回 0。
6. 执行 I/O 操作
Channel
接口继承自 ChannelOutboundInvoker
,因此提供了一系列发起出站操作的方法,如绑定、连接、断开连接、关闭通道、读取数据等。以下是一些常用的方法:
ChannelFuture bind(SocketAddress localAddress)
:请求将通道绑定到指定的本地地址。ChannelFuture connect(SocketAddress remoteAddress)
:请求将通道连接到指定的远程地址。ChannelFuture disconnect()
:请求断开通道与远程对等方的连接。ChannelFuture close()
:请求关闭通道。Channel read()
:请求从通道读取数据到第一个入站缓冲区。Channel flush()
:将通道的出站缓冲区中的数据刷新到网络。ChannelFuture writeAndFlush(Object msg)
:将消息写入通道的出站缓冲区并刷新到网络。
7 io.netty.channel.ChannelHandler
ChannelHandler
接口用于处理 I/O 事件或拦截 I/O 操作,并将其转发到 ChannelPipeline
中的下一个处理器。它本身定义的方法不多,但通常需要实现其某个子类型来处理具体的 I/O 事件或操作。
public interface ChannelHandler {
void handlerAdded(ChannelHandlerContext ctx) throws Exception;
void handlerRemoved(ChannelHandlerContext ctx) throws Exception;
@Deprecated
void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
@Inherited
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Sharable {
// no value
}
}
主要功能
1. 处理器添加与移除回调
void handlerAdded(ChannelHandlerContext ctx) throws Exception
:当ChannelHandler
被添加到实际的上下文并且准备好处理事件时调用。void handlerRemoved(ChannelHandlerContext ctx) throws Exception
:当ChannelHandler
从实际的上下文移除并且不再处理事件时调用。
2. 异常捕获(已弃用)
void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception
:如果抛出了Throwable
异常,会调用此方法。不过现在建议实现ChannelInboundHandler
并在其中实现该方法。
3. @Sharable
注解
- 这是一个自定义注解,用于标记
ChannelHandler
实例是否可以被多次添加到一个或多个ChannelPipeline
中而不会产生竞态条件。如果没有指定该注解,每次将处理器添加到管道时都必须创建一个新的处理器实例,因为它可能包含非共享状态(如成员变量)。
子类型
ChannelHandler
本身提供的方法有限,通常需要实现以下子类型来处理具体的 I/O 事件或操作:
ChannelInboundHandler
:用于处理入站 I/O 事件,如通道注册、激活、读取数据等。
public interface ChannelInboundHandler extends ChannelHandler {
void channelRegistered(ChannelHandlerContext ctx) throws Exception;
void channelUnregistered(ChannelHandlerContext ctx) throws Exception;
void channelActive(ChannelHandlerContext ctx) throws Exception;
void channelInactive(ChannelHandlerContext ctx) throws Exception;
void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception;
void channelReadComplete(ChannelHandlerContext ctx) throws Exception;
void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception;
void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception;
@Override
@SuppressWarnings("deprecation")
void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
}
-
ChannelOutboundHandler
:用于处理出站 I/O 操作,如绑定、连接、写入数据等。 -
适配器类
:为了方便使用,Netty 还提供了以下适配器类:
ChannelInboundHandlerAdapter
:处理入站 I/O 事件。ChannelOutboundHandlerAdapter
:处理出站 I/O 操作。ChannelDuplexHandler
:处理入站和出站事件。
上下文对象(ChannelHandlerContext
)
ChannelHandler
通过 ChannelHandlerContext
对象与所属的 ChannelPipeline
进行交互。使用上下文对象,ChannelHandler
可以将事件向上游或下游传递、动态修改管道,或者存储特定于处理器的信息(使用 AttributeKey
)。
public interface ChannelHandlerContext extends AttributeMap, ChannelInboundInvoker, ChannelOutboundInvoker {
Channel channel();
EventExecutor executor();
String name();
ChannelHandler handler();
boolean isRemoved();
// 其他方法...
}
状态管理
ChannelHandler
通常需要存储一些有状态的信息,有两种常见的方法:
1. 使用成员变量
推荐使用成员变量来存储处理器的状态。由于处理器实例的状态变量是专用于一个连接的,因此必须为每个新通道创建一个新的处理器实例,以避免竞态条件。
public class DataServerHandler extends SimpleChannelInboundHandler<Message> {
private boolean loggedIn;
@Override
public void channelRead0(ChannelHandlerContext ctx, Message message) {
if (message instanceof LoginMessage) {
authenticate((LoginMessage) message);
loggedIn = true;
} else if (message instanceof GetDataMessage) {
if (loggedIn) {
ctx.writeAndFlush(fetchSecret((GetDataMessage) message));
} else {
fail();
}
}
}
// 其他方法...
}
2. 使用 AttributeKey
如果不想创建多个处理器实例,可以使用 ChannelHandlerContext
提供的 AttributeKey
来存储状态。这样,处理器的状态就与 ChannelHandlerContext
关联,可以将同一个处理器实例添加到不同的管道中。
@Sharable
public class DataServerHandler extends SimpleChannelInboundHandler<Message> {
private final AttributeKey<Boolean> auth = AttributeKey.valueOf("auth");
@Override
public void channelRead(ChannelHandlerContext ctx, Message message) {
Attribute<Boolean> attr = ctx.attr(auth);
if (message instanceof LoginMessage) {
authenticate((LoginMessage) message);
attr.set(true);
} else if (message instanceof GetDataMessage) {
if (Boolean.TRUE.equals(attr.get())) {
ctx.writeAndFlush(fetchSecret((GetDataMessage) message));
} else {
fail();
}
}
}
// 其他方法...
}
8 io.netty.channel.ChannelInitializer
这个类的主要作用是提供一种便捷的方式,在 Channel
注册到其 EventLoop
之后,对该 Channel
的 ChannelPipeline
进行初始化设置。ChannelInitializer
被标记为 @Sharable
,这意味着它的实现类实例可以安全地被添加到多个 ChannelPipeline
中,并且可以被多个 Channel
共享使用。
使用场景
在 Netty 应用程序开发中,我们经常需要为不同类型的 Channel
配置不同的 ChannelHandler
链,以实现各种功能,如数据编解码、业务逻辑处理等。ChannelInitializer
主要用于以下几种场景:
- 服务器端:在
ServerBootstrap
的childHandler
方法中使用,为每个新连接的客户端Channel
初始化ChannelPipeline
。 - 客户端:在
Bootstrap
的handler
方法中使用,为客户端连接的Channel
初始化ChannelPipeline
。
工作原理
ChannelInitializer
实现了 ChannelInboundHandler
接口中的一些方法,主要是 channelRegistered
和 handlerAdded
方法。当 Channel
注册到 EventLoop
时,会触发这些方法,进而调用抽象方法 initChannel
来初始化 ChannelPipeline
。
initChannel
方法:这是一个抽象方法,需要开发者在子类中实现。在该方法中,可以向ChannelPipeline
中添加各种ChannelHandler
。- 初始化流程:当
Channel
注册到EventLoop
后,ChannelInitializer
的handlerAdded
方法会被调用。如果此时Channel
已经注册,会调用initChannel
方法进行初始化,初始化完成后会将ChannelInitializer
从ChannelPipeline
中移除,避免重复初始化。
代码示例
以下是一个简单的使用 ChannelInitializer
的示例,展示了如何在服务器端和客户端初始化 ChannelPipeline
:
服务器端代码
public class NettyServer {
private final int port;
public NettyServer(int port) {
this.port = port;
}
public void run() throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(); // 用于接受客户端连接
EventLoopGroup workerGroup = new NioEventLoopGroup(); // 用于处理客户端读写操作
try {
ServerBootstrap b = new ServerBootstrap(); // 启动服务器的辅助类
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class) // 指定使用 NIO 的服务器套接字通道
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringDecoder()); // 添加字符串解码器
ch.pipeline().addLast(new StringEncoder()); // 添加字符串编码器
ch.pipeline().addLast(new ServerHandler()); // 添加自定义业务处理处理器
}
})
.option(ChannelOption.SO_BACKLOG, 128) // 设置 TCP 缓冲区
.childOption(ChannelOption.SO_KEEPALIVE, true); // 保持长连接
// 绑定端口,开始接收进来的连接
ChannelFuture f = b.bind(port).sync();
// 等待服务器 socket 关闭 。
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
new NettyServer(8080).run();
}
}
9 io.netty.channel.ChannelPipeline
它用于管理 ChannelHandler
链,负责处理和拦截 Channel
的入站事件和出站操作。
1. 基本概念
ChannelPipeline
实现了拦截过滤器模式(Intercepting Filter Pattern),允许用户完全控制事件的处理方式以及管道中各个 ChannelHandler
之间的交互。每个 Channel
都有自己的 ChannelPipeline
,并且在新 Channel
创建时会自动创建。
2. 事件流处理
入站事件(Inbound Events)
入站事件通常由 I/O 线程生成,例如读取远程对等方的数据。入站事件会按照 ChannelPipeline
中入站处理器的顺序从下往上处理。如果入站事件超出了最顶层的入站处理器,它将被静默丢弃,或者在需要关注时进行日志记录。
出站事件(Outbound Events)
出站事件通常由应用程序发起,例如写请求。出站事件会按照 ChannelPipeline
中出站处理器的顺序从上往下处理。如果出站事件超出了最底层的出站处理器,它将由与 Channel
关联的 I/O 线程处理,该线程通常会执行实际的输出操作。
3. 代码示例中的事件流图示
I/O Request
via {@link Channel} or
{@link ChannelHandlerContext}
|
+---------------------------------------------------+---------------+
| ChannelPipeline | |
| \|/ |
| +---------------------+ +-----------+----------+ |
| | Inbound Handler N | | Outbound Handler 1 | |
| +----------+----------+ +-----------+----------+ |
| /|\ | |
| | \|/ |
| +----------+----------+ +-----------+----------+ |
| | Inbound Handler N-1 | | Outbound Handler 2 | |
| +----------+----------+ +-----------+----------+ |
| /|\ . |
| . . |
| ChannelHandlerContext.fireIN_EVT() ChannelHandlerContext.OUT_EVT()|
| [ method call] [method call] |
| . . |
| . \|/ |
| +----------+----------+ +-----------+----------+ |
| | Inbound Handler 2 | | Outbound Handler M-1 | |
| +----------+----------+ +-----------+----------+ |
| /|\ | |
| | \|/ |
| +----------+----------+ +-----------+----------+ |
| | Inbound Handler 1 | | Outbound Handler M | |
| +----------+----------+ +-----------+----------+ |
| /|\ | |
+---------------+-----------------------------------+---------------+
| \|/
+---------------+-----------------------------------+---------------+
| | | |
| [ Socket.read() ] [ Socket.write() ] |
| |
| Netty Internal I/O Threads (Transport Implementation) |
+-------------------------------------------------------------------+
4. 主要方法
添加处理器
addFirst(String name, ChannelHandler handler)
:将ChannelHandler
插入到ChannelPipeline
的开头。addLast(String name, ChannelHandler handler)
:将ChannelHandler
追加到ChannelPipeline
的末尾。addBefore(String baseName, String name, ChannelHandler handler)
:在指定名称的现有处理器之前插入ChannelHandler
。addAfter(String baseName, String name, ChannelHandler handler)
:在指定名称的现有处理器之后插入ChannelHandler
。
移除处理器
remove(ChannelHandler handler)
:从ChannelPipeline
中移除指定的ChannelHandler
。remove(String name)
:根据名称从ChannelPipeline
中移除ChannelHandler
。remove(Class<T> handlerType)
:根据类型从ChannelPipeline
中移除ChannelHandler
。
替换处理器
replace(ChannelHandler oldHandler, String newName, ChannelHandler newHandler)
:用新的ChannelHandler
替换指定的旧处理器。
5. 线程安全
ChannelPipeline
是线程安全的,这意味着可以在任何时候添加或移除 ChannelHandler
。例如,在交换敏感信息时可以插入加密处理器,交换完成后再移除它。
6. 构建管道示例
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("decoder", new MyProtocolDecoder());
pipeline.addLast("encoder", new MyProtocolEncoder());
pipeline.addLast("handler", new MyBusinessLogicHandler());
在这个示例中,MyProtocolDecoder
用于将二进制数据转换为 Java 对象,MyProtocolEncoder
用于将 Java 对象转换为二进制数据,MyBusinessLogicHandler
用于执行实际的业务逻辑。
7. 代码实现
DefaultChannelPipeline
是 ChannelPipeline
的默认实现,它使用双向链表来管理 ChannelHandler
。以下是 DefaultChannelPipeline
的部分关键代码:
public class DefaultChannelPipeline implements ChannelPipeline {
final HeadContext head;
final TailContext tail;
protected DefaultChannelPipeline(Channel channel) {
this.channel = ObjectUtil.checkNotNull(channel, "channel");
tail = new TailContext(this);
head = new HeadContext(this);
head.next = tail;
tail.prev = head;
}
// 添加处理器的方法实现
@Override
public final ChannelPipeline addFirst(String name, ChannelHandler handler) {
return addFirst(null, name, handler);
}
private ChannelPipeline internalAdd(EventExecutorGroup group, String name,
ChannelHandler handler, String baseName,
AddStrategy addStrategy) {
// ...
}
// 其他方法...
}