引导客户端和无连接协议
Bootstrap 类被用于客户端或者使用了无连接协议的应用程序中。
表8-1 提供了该类的一个概览,其中许多方法都继承自AbstractBootstrap 类。
方法概览
Bootstrap group(EventLoopGroup) 设置用于处理Channel 所有事件的EventLoopGroup
Bootstrap channel(Class<? extends C>)/Bootstrap channelFactory(ChannelFactory<? extends C>) channel()方法指定了Channel的实现类。如果该实现类没提供默认的构造函数,可以通过调用channelFactory()方法来指定一个工厂类,它将会被bind()方法调用
Bootstrap localAddress(SocketAddress) 指定Channel 应该绑定到的本地地址。如果没有指定,则将由操作系统创建一个随机的地址。或者,也可以通过
bind()或者connect()方法指定localAddress
<T> Bootstrap option(ChannelOption<T> option,T value) 设置ChannelOption,其将被应用到每个新创建的Channel 的ChannelConfig。这些选项将会通过
bind()或者connect()方法设置到Channel,不管哪个先被调用。这个方法在Channel 已经被创建后再调用将不会有任何的效果。支持的ChannelOption 取决于使用的Channel 类型。参见8.6 节以及ChannelConfig 的API 文档,了解所使用的Channel 类型
<T> Bootstrap attr(Attribute<T> key, T value) 指定新创建的Channel 的属性值。这些属性值是通过bind()或者connect()方法设置到Channel 的,具体
取决于谁最先被调用。这个方法在Channel 被创建后将不会有任何的效果。参见8.6 节
Bootstraphandler(ChannelHandler) 设置将被添加到ChannelPipeline 以接收事件通知的
ChannelHandlerBootstrap clone() 创建一个当前Bootstrap 的克隆,其具有和原始的Bootstrap 相同的设置信息
Bootstrap remoteAddress(SocketAddress) 设置远程地址。或者,也可以通过connect()方法来指定它
ChannelFuture connect() 连接到远程节点并返回一个ChannelFuture,其将会在连接操作完成后接收到通知
ChannelFuture bind() 绑定Channel 并返回一个ChannelFuture,其将会在绑定操作完成后接收到通知,在那之后必须调用Channel.connect()方法来建立连接
下一节将一步一步地讲解客户端的引导过程。我们也将讨论在选择可用的组件实现时保持兼容性的问题。
引导客户端
Bootstrap 类负责为客户端和使用无连接协议的应用程序创建Channel,如图8-2 所示。
Bootstrap 方法在 bind() 调用之后,创建新的 channel。在调用 connect() 之后,也创建新的 channel。
示例
- 8.1 引导一个客户端
package com.github.houbb.netty.inaction.bootstrap;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.net.InetSocketAddress;
/**
* @author binbin.hou
* @date 2019/4/30
* @since 1.0.0
*/
public class BoostrapDemo {
public static void main(String[] args) {
// 创建一个引导
Bootstrap bootstrap = new Bootstrap();
// 新建一个 group
EventLoopGroup eventExecutors = new NioEventLoopGroup();
bootstrap.group(eventExecutors)
//指定 channel 类实现
.channel(NioSocketChannel.class)
//指定 channelHandler
.handler(new SimpleChannelInboundHandler<ByteBuf>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
System.out.println("Received data...");
}
});
//连接到远程,并返回 future
ChannelFuture channelFuture = bootstrap
.connect(new InetSocketAddress("www.manning.com", 80));
// 添加 future 的listener
channelFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if(future.isSuccess()) {
System.out.println("channelFuture success");
} else {
System.out.println("channelFuture fail");
future.cause().printStackTrace();
}
}
});
}
}
直接运行,日志如下:
channelFuture success
Channel 和 EventLoopGroup 的兼容性
代码清单8-2 所示的目录清单来自io.netty.channel 包。
你可以从包名以及与其相对应的类名的前缀看到,对于NIO 以及OIO 传输两者来说,都有相关的EventLoopGroup 和Channel 实现。
- 代码清单
channel
├───nio
│ NioEventLoopGroup
├───oio
│ OioEventLoopGroup
└───socket
├───nio
│ NioDatagramChannel
│ NioServerSocketChannel
│ NioSocketChannel
└───oio
│ OioDatagramChannel
│ OioServerSocketChannel
│ OioSocketChannel
必须保持这种兼容性,不能混用具有不同前缀的组件,如NioEventLoopGroup 和OioSocketChannel。
不匹配的例子
代码清单8-3 展示了试图这样做的一个例子。
package com.github.houbb.netty.inaction.bootstrap;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.oio.OioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.net.InetSocketAddress;
/**
* @author binbin.hou
* @date 2019/4/30
* @since 1.0.0
*/
public class DismatchBoostrapDemo {
public static void main(String[] args) {
// 创建一个引导
Bootstrap bootstrap = new Bootstrap();
// 新建一个 group
EventLoopGroup eventExecutors = new OioEventLoopGroup();
bootstrap.group(eventExecutors)
//指定 channel 类实现
.channel(NioSocketChannel.class)
//指定 channelHandler
.handler(new SimpleChannelInboundHandler<ByteBuf>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
System.out.println("Received data...");
}
});
//连接到远程,并返回 future
ChannelFuture channelFuture = bootstrap
.connect(new InetSocketAddress("www.manning.com", 80));
// 添加 future 的listener
channelFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if(future.isSuccess()) {
System.out.println("channelFuture success");
} else {
System.out.println("channelFuture fail");
future.cause().printStackTrace();
}
}
});
}
}
运行的话就会报错
channelFuture fail
java.lang.IllegalStateException: incompatible event loop type: io.netty.channel.ThreadPerChannelEventLoop
at io.netty.channel.AbstractChannel$AbstractUnsafe.register(AbstractChannel.java:469)
at io.netty.channel.SingleThreadEventLoop.register(SingleThreadEventLoop.java:80)
at io.netty.channel.ThreadPerChannelEventLoop.register(ThreadPerChannelEventLoop.java:35)
at io.netty.channel.ThreadPerChannelEventLoopGroup.register(ThreadPerChannelEventLoopGroup.java:279)
at io.netty.bootstrap.AbstractBootstrap.initAndRegister(AbstractBootstrap.java:331)
at io.netty.bootstrap.Bootstrap.doResolveAndConnect(Bootstrap.java:163)
at io.netty.bootstrap.Bootstrap.connect(Bootstrap.java:145)
at com.github.houbb.netty.inaction.bootstrap.DismatchBoostrapDemo.main(DismatchBoostrapDemo.java:38)
关于IllegalStateException 的更多讨论
在引导的过程中,在调用bind()或者connect()方法之前,必须调用以下方法来设置所需的组件:
-
group();
-
channel()或者channelFactory();
-
handler()。
如果不这样做,则将会导致IllegalStateException。对handler()方法的调用尤其重要,因为它需要配置好ChannelPipeline。
思考
既然不能出现这种混乱的 EventLoopGroup 和 Channel 得匹配关系,那么是否应该把这种细节隐藏?
还是说 channel 是不确定的,所以无法确定 group?
那么能不能根据 channel 来确定 EventLoopGroup?
参考资料
《Netty in Action》 P113