客户端启动流程
我们以下面的demo作为本次netty源码分析的入口
public static void main(String[] args) throws Exception {
// 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 EchoClientHandler());
}
});
// Start the client.
ChannelFuture f = b.connect(HOST, PORT).sync();
// Wait until the connection is closed.
f.channel().closeFuture().sync();
} finally {
// Shut down the event loop to terminate all threads.
group.shutdownGracefully();
}
}
}
客户端启动和服务端启动有很多类似的地方,首先我们看到相比于服务端,客户端只创建了一个NioEventLoopGroup,不太清楚创建过程的同学可以翻看一下前面的分析。与服务端引导类不同,客户端使用Bootstrap进行引导,同样通过group方法绑定eventLoop,通过channel方法设置为nio模式,同样也通过handler方法绑定了channel初始化器,以此向channelPipeline中添加channelHandler。完成上面的配置后,调用入口方法connect方法,我们跟踪方法执行会走到Bootstrap的doResolveAndConnect方法
public ChannelFuture connect(String inetHost, int inetPort) {
return connect(InetSocketAddress.createUnresolved(inetHost, inetPort));
}
private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
//1.创建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 {
final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
regFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
Throwable cause = future.cause();
if (cause != null) {
promise.setFailure(cause);
} else {
promise.registered();
doResolveAndConnect0(channel, remoteAddress, localAddress, promise);
}
}
});
return promise;
}
}
通过上面的代码我们可以看到,首先和服务端执行bind方法相同,先执行initAndRegister方法,先通过反射创建 channel然后完成初始化,之后完成注册。与服务端不同这里创建的是NioSocketChannel对象,我们先跟踪到这个类的无参构造方法中。
public NioSocketChannel() {
this(DEFAULT_SELECTOR_PROVIDER);
}
public NioSocketChannel(SelectorProvider provider) {
//通过SelectorProvider创建SocketChannel
this(newSocket(provider));
}
public NioSocketChannel(Channel parent, SocketChannel socket) {
//1.调用父类构造函数
//socket jdk底层Nio的channel
super(parent, socket);
//2.创建config对象
config = new NioSocketChannelConfig(this, socket.socket());
}
这里首先调用父类的构造函数,我们先跟踪到父类中
protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
//parent=null socketChannel 关注的IO事件
super(parent, ch, SelectionKey.OP_READ);
}
可以看到继续调用父类构造函数,设置了关注IO事件
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
super(parent);
this.ch = ch;//nio的ServerSocketChannel
this.readInterestOp = readInterestOp;//连接事件
try {
ch.configureBlocking(false);//设置非阻塞模式
} catch (IOException e) {
try {
ch.close();//发生异常就关闭channel
} catch (IOException e2) {
if (logger.isWarnEnabled()) {
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;//null
//1.channel唯一标识id
id = newId();
//2.unsafe是channel的内部类,封装了很多底层nio的操作
unsafe = newUnsafe();
//3.每创建一个channel就会创建一个channelPipe用于组织handler
pipeline = newChannelPipeline();
}
这段代码在服务端启动流程中我们也看到过,继续调用父类的构造函数,给每一个NioSocketChannel创建一个唯一的id,channel内部创建了一个unsafe对象,里面封装了一些IO的底层操作,还创建了一个pipeline对象,用来组织handler。然后将channel设置为非阻塞。
完成NioSocketChannel的初始化后,我们继续跟踪到init初始化方法中。这里客户端的引导类是BootStrap,所以会走到这个类的init方法中
void init(Channel channel) throws Exception {
//向pipeline中加入ChannelInitializer
ChannelPipeline p = channel.pipeline();
p.addLast(config.handler());
//配置
final Map<ChannelOption<?>, Object> options = options0();
synchronized (options) {
setChannelOptions(channel, options, logger);
}
//属性
final Map<AttributeKey<?>, Object> attrs = attrs0();
synchronized (attrs) {
for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
channel.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
}
}
}
这里方法很加单,首先向pipeline中添加了一个Channellnitializer,这个我们在上面也已经分析过 就是新加了
个PendingHandlerAddedTask任务,当channel完成注册的时候回调handlerAdd方法,调用Channellnitializer的 initChannel方法,向pipeline中添加handler,然后再把Channellnitializer初始化器从链表中删除。然后对channel的属性和配置进行解析。
channel初始化完成后开始执行register方法
public ChannelFuture register(Channel channel) {
//1.创建一个DefaultChannelPromise用于返回执行结果
return register(new DefaultChannelPromise(channel, this));
}
public ChannelFuture register(final ChannelPromise promise) {
ObjectUtil.checkNotNull(promise, "promise");//校验
//调用unsafe的register方法
promise.channel().unsafe().register(this, promise);
return promise;
}
最终会走到AbstractChannel的内部类AbstractUnsafe的register方法
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
if (eventLoop == null) {
throw new NullPointerException("eventLoop");
}
if (isRegistered()) {
promise.setFailure(new IllegalStateException("registered to an event loop already"));
return;
}
if (!isCompatible(eventLoop)) {
promise.setFailure(
new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
return;
}
//关联channel和eventLoop
AbstractChannel.this.eventLoop = eventLoop;
//1.当前运行的线程是eventLoopGroup线程,及内部线程
if (eventLoop.inEventLoop()) {
register0(promise);
} else {
try {
//2.调用execute方法将register0方法包装成一个任务放入NioEventLoop的
//任务队列中,由NioEventLoop线程执行
eventLoop.execute(new Runnable() {
@Override
public void run() {
register0(promise);
}
});
} catch (Throwable t) {
logger.warn(
"Force-closing a channel whose registration task was not accepted by an event loop: {}",
AbstractChannel.this, t);
closeForcibly();
closeFuture.setClosed();
safeSetFailure(promise, t);
}
}
}
private void register0(ChannelPromise promise) {
try {
// check if the channel is still open as it could be closed in the mean time when the register
// call was outside of the eventLoop
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return;
}
//1.设置第一此执行注册标志
boolean firstRegistration = neverRegistered;
//2.完成channel注册,返回连接selectionKey
doRegister();
neverRegistered = false;
registered = true;
// Ensure we call handlerAdded(...) before we actually notify the promise. This is needed as the
// user may already fire events through the pipeline in the ChannelFutureListener.
//3.检查是否触发HandlerAdded事件
pipeline.invokeHandlerAddedIfNeeded();
//4.设置promise注册成功
safeSetSuccess(promise);
//5.触发ChannelRegistered事件
pipeline.fireChannelRegistered();
// Only fire a channelActive if the channel has never been registered. This prevents firing
// multiple channel actives if the channel is deregistered and re-registered.
//6.检查channel是不是已经属于连接成功状态,这里channel还没有bind,所以返回false
if (isActive()) {
if (firstRegistration) {
pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
// This channel was registered before and autoRead() is set. This means we need to begin read
// again so that we process inbound data.
//
// See https://github.com/netty/netty/issues/4805
//7.给channel注册读事件
beginRead();
}
}
} catch (Throwable t) {
// Close the channel directly to avoid FD leak.
closeForcibly();
closeFuture.setClosed();
safeSetFailure(promise, t);
}
}
下面的注册流程和我们对服务端的分析相同,由于这里调用的线程是main线程,所以会执行else,将Runnable任务添加到NioEventLoop的task任务队列中,并且检查NioEventLoop线程是否启动,如果没有启动就将这个线程启动,并设置thread属性就是当前运行的NioEventLoop,然后执行其内部的run方法,循环执行,上面说的三个事
到这里完成了NioSocketChannel的初始化和注册了,下面开始正在执行connect方法
private ChannelFuture doResolveAndConnect0(final Channel channel, SocketAddress remoteAddress,
final SocketAddress localAddress, final ChannelPromise promise) {
try {
//获取channel关联的eventLoop
final EventLoop eventLoop = channel.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();
promise.setFailure(resolveFailureCause);
} else {
//如果成功开始执行doConnect方法
doConnect(resolveFuture.getNow(), localAddress, promise);
}
return promise;
}
//如果上面的解析还没有完成就添加一个回调事件,如果解析完成后还是会调用doConnect方法
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;
}
private static void doConnect(
final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise connectPromise) {
//获取channel
final Channel channel = connectPromise.channel();
//调用connect方法
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);
}
});
}
然后会走到AbstractChannelHandlerContext的connect方法
public ChannelFuture connect(
final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
if (remoteAddress == null) {
throw new NullPointerException("remoteAddress");
}
if (isNotValidPromise(promise, false)) {
// cancelled
return promise;
}
//查询outbound的ChannelHandlerContext,这里就是headContext
final AbstractChannelHandlerContext next = findContextOutbound();
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeConnect(remoteAddress, localAddress, promise);
} else {
safeExecute(executor, new Runnable() {
@Override
public void run() {
next.invokeConnect(remoteAddress, localAddress, promise);
}
}, promise, null);
}
return promise;
}
经过逐层跟踪调用跟踪会走到AbstractNioUnsafe#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();
}
//检查是不是channel是不是已经是活跃状态
boolean wasActive = isActive();
//连接远程服务端
if (doConnect(remoteAddress, localAddress)) {
//如果连接成功设置promise状态并通知,如果没有则关闭
fulfillConnectPromise(promise, wasActive);
} else {
//如果连接失败
connectPromise = promise;
requestedRemoteAddress = remoteAddress;
// Schedule connect timeout.
//默认30秒
int connectTimeoutMillis = config().getConnectTimeoutMillis();
if (connectTimeoutMillis > 0) {
connectTimeoutFuture = eventLoop().schedule(new Runnable() {
@Override
public void run() {
ChannelPromise connectPromise = AbstractNioChannel.this.connectPromise;
ConnectTimeoutException cause =
new ConnectTimeoutException("connection timed out: " + remoteAddress);
//定时30秒后,如果连接失败就报错并关闭
if (connectPromise != null && connectPromise.tryFailure(cause)) {
close(voidPromise());
}
}
}, connectTimeoutMillis, TimeUnit.MILLISECONDS);
}
//给promise添加一个Listener回调,如果取消了连接任务则将连接任务取消并关闭
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();
}
}
这里我们关注的还是doConnect方法方法我们继续跟进
protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
//这里传入的localAddress是null则不会执行
if (localAddress != null) {
doBind0(localAddress);
}
boolean success = false;
try {
//调用connect方法
boolean connected = SocketUtils.connect(javaChannel(), remoteAddress);
//如果连接还没有建立,或者在非阻塞模式下正在连接,则设置关心connect事件
if (!connected) {
selectionKey().interestOps(SelectionKey.OP_CONNECT);
}
success = true;
return connected;
} finally {
if (!success) {
doClose();
}
}
}
这里我们可以看到,完成了nio底层的SocketChannel的connect方法调用,并设置了关注事件OP_CONNECT,到这里客户端的启动连接就完成了。如果有分析错误的地方还请不吝指正。感谢!!!