首先记录下结果,都是寻找DNS的锅。
测试代码:
public void testFlow(){
EventLoopGroup group = new NioEventLoopGroup();
// try {
Bootstrap bootstrap = new Bootstrap()
.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) {
System.out.println(ch.toString());
}
});
ChannelFuture channelFuture = bootstrap.connect("1226.baidu.com", 9529);
channelFuture.addListener(new GenericFutureListener<Future<? super Void>>() {
@Override
public void operationComplete(Future<? super Void> future) throws Exception {
System.out.println(future.isSuccess());
}
});
// }finally {
// group.shutdownGracefully();
// }
}
同样的连接,在放开注释与不放开注释,是否有Internet网(路由器是否接入Internet)是各种不同的情况。
在放开shutdownGracefully的情况下,netty会强行处理所有异步回调结果,使得回调结果可以快速得出;
在注释finally代码块的情况下,是否有Internet网的情况就会有很大影响了—有Internet网的情况,异步回调可以快速回调;在无Internet网的情况下,会阻塞10s至40s左右。
所以问题在于:为什么在无Internet网的情况下会阻塞?
经过阅读源码,确定这个阻塞延时为Resolve所用时间=。
private ChannelFuture doResolveAndConnect0(final Channel channel, SocketAddress remoteAddress,
final SocketAddress localAddress, final ChannelPromise promise) {
try {
final EventLoop eventLoop = channel.eventLoop();
final AddressResolver<SocketAddress> 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<SocketAddress> 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<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;
}
根源在于:
final Future<SocketAddress> resolveFuture = resolver.resolve(remoteAddress);
调用流程大概是这样:
最终会追到JDK中的InetAddress类中,然后由这个类来解析host,解析host就会寻找路由,也就是这个方法:
private static InetAddress[] getAddressesFromNameService(String host, InetAddress reqAddr) throws UnknownHostException
{
InetAddress[] addresses = null;
boolean success = false;
UnknownHostException ex = null;
if ((addresses = checkLookupTable(host)) == null) {
try {
for (NameService nameService : nameServices) {
try {
addresses = nameService.lookupAllHostAddr(host);
success = true;
break;
} catch (UnknownHostException uhe) {
if (host.equalsIgnoreCase("localhost")) {
InetAddress[] local = new InetAddress[] { impl.loopbackAddress() };
addresses = local;
success = true;
break;
}
else {
addresses = unknown_array;
success = false;
ex = uhe;
}
}
}
// More to do?
if (reqAddr != null && addresses.length > 1 && !addresses[0].equals(reqAddr)) {
// Find it?
int i = 1;
for (; i < addresses.length; i++) {
if (addresses[i].equals(reqAddr)) {
break;
}
}
// Rotate
if (i < addresses.length) {
InetAddress tmp, tmp2 = reqAddr;
for (int j = 0; j < i; j++) {
tmp = addresses[j];
addresses[j] = tmp2;
tmp2 = tmp;
}
addresses[i] = tmp2;
}
}
// Cache the address.
cacheAddresses(host, addresses, success);
if (!success && ex != null)
throw ex;
} finally {
// Delete host from the lookupTable and notify
// all threads waiting on the lookupTable monitor.
updateLookupTable(host);
}
}
return addresses;
}
不用怀疑,当没有外网的情况,使用InetAddress.getByName去解析地址,都会阻塞。
所以这并非Netty才会发生,几乎所有连接在此前提条件下都会产生这种情况,除非有特殊的处理,如netty通过shutdownGracefully来加速这个过程。
当然,一般情况下shutdownGracefully是和sync配合使用的,否则就会直接被shutdownGracefully强行关闭线程池,无论连接的域名是否可用,都会被强行返回一个失败的结果。