阅读须知
- Netty版本:4.1.14.Final
- 文章中使用/* */注释的方法会做深入分析
正文
private void connect(int port, String host) throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true).handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new LineBasedFrameDecoder(1024));
socketChannel.pipeline().addLast(new StringDecoder());
// ...
}
});
ChannelFuture f = b.connect(host, port).sync();
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
这是一个简单的Netty客户端启动的方法,我们以这个方法作为入口来分析Netty的源码实现,首先我们来看Bootstrap的源码实现:
Bootstrap的作用是可以轻松引导用于客户端的Channel,b.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true).handler(...);
这段代码调用的方法都是在为Bootstrap的各个属性赋值,采用链式编程的方式,下面我们来看connect方法的实现:
Bootstrap:
public ChannelFuture connect(String inetHost, int inetPort) {
return connect(InetSocketAddress.createUnresolved(inetHost, inetPort));
}
Bootstrap:
public ChannelFuture connect(SocketAddress remoteAddress) {
if (remoteAddress == null) {
throw new NullPointerException("remoteAddress");
}
validate(); // 校验group、channelFactory等属性不能为null
/* 建立连接 */
return doResolveAndConnect(remoteAddress, config.localAddress());
}
Bootstrap:
private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
/* 注册和初始化Channel */
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
// 判断任务是否完成,正常结束、抛异常、取消都算任务完成
if (regFuture.isDone()) {
// 如果未成功直接返回
if (!regFuture.isSuccess()) {
return regFuture;
}
/* 解析并建立连接 */
return doResolveAndConnect0(channel, remoteAddress, localAddress, channel.newPromise());
} else {
// 注册future几乎总是已经完成,但以防万一
final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
regFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
// 获取异常并进行非空判断,以便在失败时我们只需要一次volatile读取
Throwable cause = future.cause();
if (cause != null) {
// EventLoop上的注册失败会导致ChannelPromise直接失败,导致我们尝试访问Channel的EventLoop时不会导致IllegalStateException。
promise.setFailure(cause);
} else {
// 注册成功
promise.registered();
/* 解析并建立连接 */
doResolveAndConnect0(channel, remoteAddress, localAddress, promise);
}
}
});
return promise;
}
}
AbstractBootstrap:
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
// 这里的channelFactory为调用channel方法时初始化的ReflectiveChannelFactory
// 也就是根据传入的Class类型反射初始化Channel,示例中我们传入的是NioSocketChannel
channel = channelFactory.newChannel();
/* 初始化Channel */
init(channel);
} catch (Throwable t) {
// 如果newChannel崩溃,例如SocketException("too many open files"),channel可以为null
if (channel != null) {
// 立即关闭Channel,不会触发任何事件
channel.unsafe().closeForcibly();
}
// 由于Channel尚未注册,我们需要强制使用GlobalEventExecutor
return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
}
// 注册Channel
ChannelFuture regFuture = config().group().register(channel);
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
// 如果Channel已经使用EventLoop注册,请求在完成操作后关闭Channel的同时通知ChannelFuture
channel.close();
} else {
// 如果Channel未注册,立即关闭Channel,不会触发任何事件
channel.unsafe().closeForcibly();
}
}
return regFuture;
}
方法中会涉及到两个新的类EventLoopGroup和Promise,我们会用单独的文章进行分析。方法执行到最后并且promise没有失败,则为以下情况之一:
- 如果我们尝试从event loop注册,则此时注册已完成。即现在尝试bind()或connect()是安全的,因为channel已被注册。
- 如果我们尝试从另一个线程注册,则注册请求已成功添加到event loop的任务队列中以供稍后执行。
即尝试bind()或connect()现在是安全的:
因为在注册任务执行后,bind()或connect()将被执行
因为register(),bind()和connect()都绑定到同一个线程。
Bootstrap:
void init(Channel channel) throws Exception {
ChannelPipeline p = channel.pipeline();
// 这里为Channel popeline添加ChannelHandler的是用户指定的,示例中传入的是ChannelInitializer对象
p.addLast(config.handler());
final Map<ChannelOption<?>, Object> options = options0();
synchronized (options) {
// 将用户配置的option选项设置到Channel的ChannelConfig对象中
setChannelOptions(channel, options, logger);
}
final Map<AttributeKey<?>, Object> attrs = attrs0();
synchronized (attrs) {
for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
// 将用户配置的attr属性设置到Channel的Attribute属性中
channel.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
}
}
}
完成初始化Channel后,下面就是解析并建立连接:
Bootstrap:
private ChannelFuture doResolveAndConnect0(final Channel channel, SocketAddress remoteAddress,
final SocketAddress localAddress, final ChannelPromise promise) {
try {
final EventLoop eventLoop = channel.eventLoop();
/* 获取与此EventLoop相关联的地址解析器 */
final AddressResolver<SocketAddress> resolver = this.resolver.getResolver(eventLoop);
if (!resolver.isSupported(remoteAddress) || resolver.isResolved(remoteAddress)) {
/* 解析器不支持指定的远程地址的解析,或者已经解析了,直接建立连接 */
doConnect(remoteAddress, localAddress, promise);
return promise;
}
// 解析器解析远程地址
final Future<SocketAddress> resolveFuture = resolver.resolve(remoteAddress);
if (resolveFuture.isDone()) {
final Throwable resolveFailureCause = resolveFuture.cause();
// 判断任务完成后之后发生异常
if (resolveFailureCause != null) {
channel.close(); // 异常关闭channel
promise.setFailure(resolveFailureCause);
} else {
/* 没有异常则说明解析成功,直接建立连接 */
doConnect(resolveFuture.getNow(), localAddress, promise);
}
return promise;
}
// 添加监听器等待解析完成继续做与上面同样的处理
resolveFuture.addListener(new FutureListener<SocketAddress>() {
@Override
public void operationComplete(Future<SocketAddress> 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;
}
AddressResolverGroup:
public AddressResolver<T> getResolver(final EventExecutor executor) {
if (executor == null) {
throw new NullPointerException("executor");
}
if (executor.isShuttingDown()) {
throw new IllegalStateException("executor not accepting a task");
}
AddressResolver<T> r;
synchronized (resolvers) {
// 尝试从缓存中获取解析器,解析的初始化操作比较昂贵,放入缓存复用
r = resolvers.get(executor);
if (r == null) {
final AddressResolver<T> newResolver;
try {
// 缓存中不存在则新建解析器,最终会返回InetSocketAddressResolver示例,用于解析InetSocketAddress
newResolver = newResolver(executor);
} catch (Exception e) {
throw new IllegalStateException("failed to create a new resolver", e);
}
resolvers.put(executor, newResolver); // 放入缓存
// 添加EventExecutor终止监听器
executor.terminationFuture().addListener(new FutureListener<Object>() {
@Override
public void operationComplete(Future<Object> future) throws Exception {
synchronized (resolvers) {
// EventExecutor终止时移除缓存
resolvers.remove(executor);
}
newResolver.close(); // 关闭解析器
}
});
r = newResolver;
}
}
return r;
}
远程地址解析操作就是通过InetSocketAddress的getHostName()方法获取主机名解析为InetAddress,使用的是JDK原生的方法进行解析,不多赘述,下面我们来看连接操作:
Bootstrap:
private static void doConnect(
final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise connectPromise) {
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);
}
// 添加监听器,在失败时关闭Channel
connectPromise.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
}
});
}
Channel的连接我们会用单独的文章分析,到这里,Bootstrap引导启动的源码分析就完成了。