首先来看下官方的example中echo的例子
// Configure the client.
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
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
if (sslCtx != null) {
p.addLast(sslCtx.newHandler(ch.alloc(), HOST, PORT));
}
//p.addLast(new LoggingHandler(LogLevel.INFO));
p.addLast(new EchoClientHandler());
}
});
// Start the client.
ChannelFuture f = b.connect(HOST, PORT).sync();
// Wait until the connection is closed.
f.channel().closeFuture().sync();
- 配置客户端的EventLoopGroup.
- 设置channel类型为NioSocketChannel.
- 设置channelOption.
- 设置ChannelHandler,进行初始化Channel,往pipeline中添加EchoClientHandler处理器.
- 调用connect建立socket的连接.
- 等待客户端关闭.
NioSocketChannel的初始化
1.主要初始化三个重要变量,第一个是Channel唯一标识ChannelId, 第二个是UNSA否Unsafe实现类NioByteUnsafe, 第三个ChannelPipeline实现类DefaultChannelPipeline
public NioSocketChannel(SocketChannel socket) {
this(null, socket);
}
public NioSocketChannel(Channel parent, SocketChannel socket) {
super(parent, socket);
config = new NioSocketChannelConfig(this, socket.socket());
}
protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
super(parent, ch, SelectionKey.OP_READ);
}
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
super(parent);
this.ch = ch;
this.readInterestOp = readInterestOp;
try {
ch.configureBlocking(false);
} catch (IOException e) {
try {
ch.close();
} catch (IOException e2) {
logger.warn(
"Failed to close a partially initialized socket.", e2);
}
throw new ChannelException("Failed to enter non-blocking mode.", e);
}
}
protected AbstractChannel(Channel parent) {
this.parent = parent;
id = newId();
unsafe = newUnsafe();
pipeline = newChannelPipeline();
}
客户端connect远程服务地址
BootStrap#connect
- 校验handler不能为空。
- 调用doResolveAndConnect进行连接。
public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) {
ObjectUtil.checkNotNull(remoteAddress, "remoteAddress");
validate();
return doResolveAndConnect(remoteAddress, localAddress);
}
BootStrap#doResolveAndConnect
- 调用initAndRegister进行Channle的初始化和注册(这在Server启动是一样的流程,再这里就不赘述)
- 获取ChannelFuture的Channel。\
- 判断是否完成注册,然后判断是否成功,不成功则直接返回,成功则直接调用doResolveAndConnect0进行连接操作\
- 如果没有完成注册, 则将Channel包装成PendingRegistrationPromise(继承DefaultChannelPromise)对象,并在ChannelFuture添加监听器,一旦绑定完成,设置PendingRegistrationPromise的registed属性值为true,接着调用doResolveAndConnect0.
private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
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 {
// 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();
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.registered();
doResolveAndConnect0(channel, remoteAddress, localAddress, promise);
}
}
});
return promise;
}
}
BootStrap#doResolveAndConnect0
- 创建一个地址解析器AddressResolver。
- 如果接卸器不支持解析远程网络地址或则远程网络地址已经解析过,则直接调用doConnect进行远程网络连接。
- 直接解析远程网路地址,如果解析完成,则有异常,则关闭Channel, 成功则调用 doConnect进行连接。
private ChannelFuture doResolveAndConnect0(final Channel channel, SocketAddress remoteAddress,
final SocketAddress localAddress, final ChannelPromise promise) {
try {
final EventLoop eventLoop = channel.eventLoop();
AddressResolver<SocketAddress> resolver;
try {
resolver = this.resolver.getResolver(eventLoop);
} catch (Throwable cause) {
channel.close();
return promise.setFailure(cause);
}
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;
}
BootStrap#doConnect
- 获取ChannelPromise中Channel对象.
- 获取Channel中的eventloop执行封装Runnalbe匿名内部类,里面逻辑会判断localAddress是否为空,为空,则调用channel的connect,否则调用Channel的connect相比上一步会多传入一个localAddress参数。
- 在ChannelPromise中添加监听器,监听出现异常关系channel.
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的connect过程,这里Channel就是例子设置的NioSocketChannel。 NioSocketChannel#connect
- 可以看到Channel中connect调用实际是委托给ChannelPipeline的connect执行connect进行建立连接。
@Override
public ChannelFuture connect(SocketAddress remoteAddress) {
return pipeline.connect(remoteAddress);
}
DefaultChannelPipeline#connect
- 从ChannelPipeline中是从ChannelHandlerContext中tail开启传播,一直会找到head, 由于tail是找下一个ChannelContext, 由于例子没有添加handler,所以最终会调用head节点ChanelHanderContext的connect进行连接。
@Override
public final ChannelFuture connect(SocketAddress remoteAddress) {
return tail.connect(remoteAddress);
}
HeadContext#connect
- HeadContext里面实际是用unsafe(实现类是NioByteUnsafe)的connect进行远程连接
@Override
public void connect(
ChannelHandlerContext ctx,
SocketAddress remoteAddress, SocketAddress localAddress,
ChannelPromise promise) {
unsafe.connect(remoteAddress, localAddress, promise);
}
AbstractNioUnsafe#connect
- 校验异常.
- 调用doConnect进行远程地址,如果成功,则调用fulfillConnectPromise进行实则ChannelPromise为success, 进行传播channelActive事件,如果connect失败,则进行超时或异常的检验.
public final void connect(
final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return;
}
try {
if (connectPromise != null) {
// Already a connect in process.
throw new ConnectionPendingException();
}
boolean wasActive = isActive();
if (doConnect(remoteAddress, localAddress)) {
fulfillConnectPromise(promise, wasActive);
} else {
connectPromise = promise;
requestedRemoteAddress = remoteAddress;
// Schedule connect timeout.
int connectTimeoutMillis = config().getConnectTimeoutMillis();
if (connectTimeoutMillis > 0) {
connectTimeoutFuture = eventLoop().schedule(new Runnable() {
@Override
public void run() {
ChannelPromise connectPromise = AbstractNioChannel.this.connectPromise;
if (connectPromise != null && !connectPromise.isDone()
&& connectPromise.tryFailure(new ConnectTimeoutException(
"connection timed out: " + remoteAddress))) {
close(voidPromise());
}
}
}, connectTimeoutMillis, TimeUnit.MILLISECONDS);
}
promise.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isCancelled()) {
if (connectTimeoutFuture != null) {
connectTimeoutFuture.cancel(false);
}
connectPromise = null;
close(voidPromise());
}
}
});
}
} catch (Throwable t) {
promise.tryFailure(annotateConnectException(t, remoteAddress));
closeIfClosed();
}
}
NioSocketChannel#doConnect
- 判断localAddress不为空,则绑定本地地址
- 嗲用SOcketUtils的connect方法进行远程连接,
- 设置Selectionkey关注事件增加SelectKey.OP_READ.
protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
if (localAddress != null) {
doBind0(localAddress);
}
boolean success = false;
try {
boolean connected = SocketUtils.connect(javaChannel(), remoteAddress);
if (!connected) {
selectionKey().interestOps(SelectionKey.OP_CONNECT);
}
success = true;
return connected;
} finally {
if (!success) {
doClose();
}
}
}
SOcketUtils#connect 1,实际就是调用JDK的SocketChannel的connect进行连接远程。
public static boolean connect(final SocketChannel socketChannel, final SocketAddress remoteAddress)
throws IOException {
try {
return AccessController.doPrivileged(new PrivilegedExceptionAction<Boolean>() {
@Override
public Boolean run() throws IOException {
return socketChannel.connect(remoteAddress);
}
});
} catch (PrivilegedActionException e) {
throw (IOException) e.getCause();
}
}
总结
本文主要是对客户端连接远程服务端地址流程,首先第一步还是跟服务端一样,开始记性初始化Channel,只是客户端的Channel类型是NioSocketChnnel,并并注册SocketChannel到多路复用器Selector中,开始设置感兴趣事件为0,这样就防止channel建立连接完成禁止读写操作,然后是HandlerContext中进行connect事件传播,最有找到HeadContext的connect,由HeadContext利用netty中unsafe中connect,其底层是调用Jdk的SocketChannel的connect进行连接远程连接,并设置Seleckey的增加感兴趣事件SelectionKey.OP_READ,至此Channel建立完成。