Netty 4ソース分析-Bootstrap
11179 ワード
BootstrapはNettyが提供する便利なツールクラスで、Bootstrapクラスを通じてクライアントを容易に起動してサーバ側と通信させることができます.
次のコードは、サーバから時間を取得するために使用します.
TimeClientHandlerコードは次のとおりです.
TimeClientには、IOイベントを処理するためのNioEventLoopGroupオブジェクトが作成されています.グループにTimeClientHandlerが追加され、チャネルの準備が完了するとchannelActiveメソッドが呼び出されてサーバに
登録が完了していない場合は、PendingRegistrationPromiseタイプのオブジェクトを作成し、regFutureオブジェクトにlistenerを追加し、登録が完了するとlistenerのoperationCompleteメソッドが呼び出されます.
次にdoResolveAndConnect 0メソッドを見てみましょう.
このメソッドは主にアドレスのチェックと解析を行い、doConnectメソッドを呼び出します.
ここでは具体的な接続作業をchannel対応のeventLoopに入れて実行しているが,この点は後で述べるが,現在はスレッドプールに入れて実行していると理解できる.
具体的な接続操作は、AbstractNioUnsafeクラスのconnectメソッドがpipelineによって呼び出され、後で分析されます.
AbstractBootstrapで実装されたdoResolveAndConnectメソッドで呼び出されたinitAndRegisterメソッドを振り返ってみましょう.
この方法は簡単で、チャネルオブジェクトを作成し、initメソッドを呼び出してオブジェクトを初期化します.
TimeClientで次のコードを確認します.
Initメソッドは,上のoptionとhandlerをchannelに追加することである.
これで接続操作全体が完了し、以降の通信作業はEventLoopに渡されて実行され、Bootstrapは起動作業を提供しただけで、主に初期化と接続の操作を行う.
次のコードは、サーバから時間を取得するために使用します.
public class TimeClient {
public static void main(String[] args) throws Exception {
int port = 8080;
if (args != null && args.length > 0) {
try {
port = Integer.valueOf(args[0]);
} catch (NumberFormatException e) {
}
}
new TimeClient().connect(port, "127.0.0.1");
}
public void connect(int port, String host) throws Exception {
// NIO
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer() {
@Override
public void initChannel(SocketChannel ch)
throws Exception {
ch.pipeline().addLast(new TimeClientHandler());
}
});
//
ChannelFuture f = b.connect(host, port).sync();
//
f.channel().closeFuture().sync();
} finally {
// , NIO
group.shutdownGracefully();
}
}
}
TimeClientHandlerコードは次のとおりです.
public class TimeClientHandler extends ChannelInboundHandlerAdapter {
private static final Logger logger = Logger
.getLogger(TimeClientHandler.class.getName());
private final ByteBuf firstMessage;
/**
* Creates a client-side handler.
*/
public TimeClientHandler() {
byte[] req = "QUERY TIME ORDER".getBytes();
firstMessage = Unpooled.buffer(req.length);
firstMessage.writeBytes(req);
}
@Override
public void channelActive(ChannelHandlerContext ctx) {
ctx.writeAndFlush(firstMessage);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
ByteBuf buf = (ByteBuf) msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = new String(req, "UTF-8");
System.out.println("Now is : " + body);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
//
logger.warning("Unexpected exception from downstream : "
+ cause.getMessage());
ctx.close();
}
}
TimeClientには、IOイベントを処理するためのNioEventLoopGroupオブジェクトが作成されています.グループにTimeClientHandlerが追加され、チャネルの準備が完了するとchannelActiveメソッドが呼び出されてサーバに
QUERY TIME ORDER
が送信されます.サーバの応答を受信すると、channelReadメソッドが呼び出され、サーバが返す時間が出力されます.ChannelFuture f = b.connect(host, port).sync();
は非同期接続サービスに使用され、doResolveAndConnectメソッドを呼び出すconnectメソッドに入ります.private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
//
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
//
if (regFuture.isDone()) {
// , ChannelFuture
if (!regFuture.isSuccess()) {
return regFuture;
}
// , channel Promise
return doResolveAndConnect0(channel, remoteAddress, localAddress, channel.newPromise());
} else {
// Registration future is almost always fulfilled already, but just in case it's not.
final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
regFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
// Directly obtain the cause and do a null check so we only need one volatile read in case of a
// failure.
Throwable cause = future.cause();
// , promise
if (cause != null) {
// Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an
// IllegalStateException once we try to access the EventLoop of the Channel.
promise.setFailure(cause);
} else {
// Registration was successful, so set the correct executor to use.
// See https://github.com/netty/netty/issues/2586
// promise
promise.registered();
doResolveAndConnect0(channel, remoteAddress, localAddress, promise);
}
}
});
return promise;
}
}
登録が完了していない場合は、PendingRegistrationPromiseタイプのオブジェクトを作成し、regFutureオブジェクトにlistenerを追加し、登録が完了するとlistenerのoperationCompleteメソッドが呼び出されます.
次にdoResolveAndConnect 0メソッドを見てみましょう.
private ChannelFuture doResolveAndConnect0(final Channel channel, SocketAddress remoteAddress,
final SocketAddress localAddress, final ChannelPromise promise) {
try {
final EventLoop eventLoop = channel.eventLoop();
final AddressResolver resolver = this.resolver.getResolver(eventLoop);
if (!resolver.isSupported(remoteAddress) || resolver.isResolved(remoteAddress)) {
// Resolver has no idea about what to do with the specified remote address or it's resolved already.
doConnect(remoteAddress, localAddress, promise);
return promise;
}
final Future resolveFuture = resolver.resolve(remoteAddress);
if (resolveFuture.isDone()) {
final Throwable resolveFailureCause = resolveFuture.cause();
if (resolveFailureCause != null) {
// Failed to resolve immediately
channel.close();
promise.setFailure(resolveFailureCause);
} else {
// Succeeded to resolve immediately; cached? (or did a blocking lookup)
doConnect(resolveFuture.getNow(), localAddress, promise);
}
return promise;
}
// Wait until the name resolution is finished.
resolveFuture.addListener(new FutureListener() {
@Override
public void operationComplete(Future future) throws Exception {
if (future.cause() != null) {
channel.close();
promise.setFailure(future.cause());
} else {
doConnect(future.getNow(), localAddress, promise);
}
}
});
} catch (Throwable cause) {
promise.tryFailure(cause);
}
return promise;
}
このメソッドは主にアドレスのチェックと解析を行い、doConnectメソッドを呼び出します.
private static void doConnect(
final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise connectPromise) {
// This method is invoked before channelRegistered() is triggered. Give user handlers a chance to set up
// the pipeline in its channelRegistered() implementation.
final Channel channel = connectPromise.channel();
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
if (localAddress == null) {
channel.connect(remoteAddress, connectPromise);
} else {
channel.connect(remoteAddress, localAddress, connectPromise);
}
connectPromise.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
}
});
}
ここでは具体的な接続作業をchannel対応のeventLoopに入れて実行しているが,この点は後で述べるが,現在はスレッドプールに入れて実行していると理解できる.
具体的な接続操作は、AbstractNioUnsafeクラスのconnectメソッドがpipelineによって呼び出され、後で分析されます.
AbstractBootstrapで実装されたdoResolveAndConnectメソッドで呼び出されたinitAndRegisterメソッドを振り返ってみましょう.
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
// Channel
channel = channelFactory.newChannel();
init(channel);
} catch (Throwable t) {
if (channel != null) {
// channel can be null if newChannel crashed (eg SocketException("too many open files"))
channel.unsafe().closeForcibly();
}
// as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
}
// group
ChannelFuture regFuture = config().group().register(channel);
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
// If we are here and the promise is not failed, it's one of the following cases:
// 1) If we attempted registration from the event loop, the registration has been completed at this point.
// i.e. It's safe to attempt bind() or connect() now because the channel has been registered.
// 2) If we attempted registration from the other thread, the registration request has been successfully
// added to the event loop's task queue for later execution.
// i.e. It's safe to attempt bind() or connect() now:
// because bind() or connect() will be executed *after* the scheduled registration task is executed
// because register(), bind(), and connect() are all bound to the same thread.
return regFuture;
}
この方法は簡単で、チャネルオブジェクトを作成し、initメソッドを呼び出してオブジェクトを初期化します.
@Override
@SuppressWarnings("unchecked")
void init(Channel channel) throws Exception {
ChannelPipeline p = channel.pipeline();
p.addLast(config.handler());
final Map, Object> options = options0();
synchronized (options) {
setChannelOptions(channel, options, logger);
}
final Map, Object> attrs = attrs0();
synchronized (attrs) {
for (Entry, Object> e: attrs.entrySet()) {
channel.attr((AttributeKey
TimeClientで次のコードを確認します.
b.group(group).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer() {
@Override
public void initChannel(SocketChannel ch)
throws Exception {
ch.pipeline().addLast(new TimeClientHandler());
}
});
Initメソッドは,上のoptionとhandlerをchannelに追加することである.
これで接続操作全体が完了し、以降の通信作業はEventLoopに渡されて実行され、Bootstrapは起動作業を提供しただけで、主に初期化と接続の操作を行う.