本章主要内容:
前面说过,每一个Channel都需要注册到一个EventLoop上来处理I/O事件。这是在启动器初始化过程中配置好后自动执行的。如下图列出的关系。
不过还有一半没有说到,当关闭Channel的时候它就会从EventLoop上注销掉并释放资源。
前面说过,很多时候我们需要重构遗留项目,也就是要处理java.nio.channels.SocketChannel或者Java.nio.channels.Channel的实现。
好消息就是用Netty也可以使用它们,通过包装Java.nio.channels.Channel就可以将它注册到EventLoop上。也就是说你可以在遗留的项目中使用Netty的功能。而且集成方式很简单,如下面的代码。
java.nio.channels.SocketChannel mySocket = ...;
mySocket.open();
//使用Netty的SocketChannel包装JDK的SocketChannel
SocketChannel ch = new NioSocketChannel(mySocket);
EventLoopGroup group = ...;
//注册到EventLoop
ChannelFuture registerFuture = group.register(ch);
//注销Channel
ChannelFuture deregisterFuture = ch.deregister();
当然Netty的功能不仅能用在
java.nio.channels.Channel的实现上,还可以用在JDK的Socket上。用法也基本上一样,有一个区别就是包装JDK的Socket要使用OioSocketChannel。这个道理很简单,因为JDK的Socket是阻塞型的,所以要使用Netty的阻塞型OioSocketChannel进行包装。
Socket mySocket = new Socket("www.manning.com", 80);
//使用OioSocketChannel包装
SocketChannel ch = new OioSocketChannel(mySocket);
EventLoopGroup group = ...;
//注册到EventLoop
ChannelFuture registerFuture = group.register(ch);
//从EventLoop上注销
ChannelFuture deregisterFuture = ch.deregister();
JDK的Channel或Socket使用Netty时有两个很重要的事情要记得。
EventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group).channel(NioSocketChannel.class)
.handler(new SimpleChannelInboundHandler() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf byteBuf) throws Exception {
ctx.pipeline().remove(this);
ctx.deregister();
}
});
ChannelFuture future = bootstrap.connect(new InetSocketAddress("www.manning.com", 80)).sync();
//这里做一些耗时的操作
...
Channel channel = future.channel();
//重新注册到EventLoop上
group.register(channel).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
System.out.println("Channel registered");
} else {
System.err.println("Register Channel on EventLoop failed");
future.cause().printStackTrace();
}
}
});
在
SimpleChannelInboundHandler中我们注销了Channel,然后在后面通过EventLoopGroup.register(…)方法重新注册了Channel,不过是异步的方式。
//创建两个EventLoopGroup实例
EventLoopGroup group = new NioEventLoopGroup();
final EventLoopGroup group2 = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group).channel(NioSocketChannel.class)
.handler(new SimpleChannelInboundHandler() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf byteBuf) throws Exception {
ctx.pipeline().remove(this);
//注销掉
ChannelFuture cf = ctx.deregister();
cf.addListener((ChannelFutureListener) future -> {
//注销成功后注册到另一个EventLoopGroup
group2.register(future.channel());
});
}
});
ChannelFuture future = bootstrap.connect(new InetSocketAddress("www.manning.com", 80));
future.addListener((ChannelFutureListener) channelFuture -> {
if (channelFuture.isSuccess()) {
System.out.println("Connection established");
} else {
System.err.println("Connection attempt failed");
channelFuture.cause().printStackTrace();
}
});
可以看到修改Channel的EventLoop也是很简单的。最需要注意的地方就是
deregister(…)和
register(…)方法是异步的,要通过检查ChannelFuture或添加ChannelFutureListener来确定你的注册或注销操作已经完成。如果不检查可能就会出现重复注册的情况,重复注册就会触发IllegalStateException异常。
这一章我们其实就学习了Channel的注销和注册操作。利用这些操作可以暂停Channel的IO处理或者修改Channel的EventLoop。而且可以将JDK的Channel和Socket的项目集成进Netty的功能。对于保持系统的稳定性很有用的,通过先注销掉Channel,然后清理系统资源,再重新注册,不过要保证数据丢失不会对业务造成很大影响。对于旧的项目,通过Netty提供的包装功能,可以以最小的风险,逐步在旧项目中集成Netty的功能。