Netty源码解析—客户端启动

Bootstrap示例

public final class EchoClient {

    static final boolean SSL = System.getProperty("ssl") != null;
static final String HOST = System.getProperty("host", "127.0.0.1");
static final int PORT = Integer.parseInt(System.getProperty("port", "8007"));
static final int SIZE = Integer.parseInt(System.getProperty("size", "256")); public static void main(String[] args) throws Exception {
// Configure SSL.git
// 配置 SSL
final SslContext sslCtx;
if (SSL) {
sslCtx = SslContextBuilder.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE).build();
} else {
sslCtx = null;
} // Configure the client.
// 创建一个 EventLoopGroup 对象
EventLoopGroup group = new NioEventLoopGroup();
try {
// 创建 Bootstrap 对象
Bootstrap b = new Bootstrap();
b.group(group) // 设置使用的 EventLoopGroup
.channel(NioSocketChannel.class) // 设置要被实例化的为 NioSocketChannel 类
.option(ChannelOption.TCP_NODELAY, true) // 设置 NioSocketChannel 的可选项
.handler(new ChannelInitializer<SocketChannel>() { // 设置 NioSocketChannel 的处理器
@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();
} finally {
// Shut down the event loop to terminate all threads.
// 优雅关闭一个 EventLoopGroup 对象
group.shutdownGracefully();
}
}
}

我们进入到启动客户端的地方connect(...)

	public ChannelFuture connect(SocketAddress remoteAddress) {
if (remoteAddress == null) {
throw new NullPointerException("remoteAddress");
}
// 校验必要参数
validate();
// 解析远程地址,并进行连接
return doResolveAndConnect(remoteAddress, config.localAddress());
}

我们进入到doResolveAndConnect()方法

    /**
* @see #connect()
*/
private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
// 初始化并注册一个 Channel 对象,因为注册是异步的过程,所以返回一个 ChannelFuture 对象。
final ChannelFuture regFuture = initAndRegister();
//...
if (regFuture.isDone()) {
//...
// 解析远程地址,并进行连接
return doResolveAndConnect0(channel, remoteAddress, localAddress, channel.newPromise());
} else {
//...
//如果异步注册对应的 ChanelFuture 未完成,则调用下面方法,添加监听器
regFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
//...
// 解析远程地址,并进行连接
doResolveAndConnect0(channel, remoteAddress, localAddress, promise);
//...
}
});
return promise;
}
//...
}

省略掉部分代码,我们可以知道这个方法主要做了两件事

  1. 注册一个Channel对象
  2. 解析地址并连接

我们直接开始撸initAndRegister()

    final ChannelFuture initAndRegister() {
//...
//反射初始化一个NioSocketChannel
channel = channelFactory.newChannel();
//设置参数
init(channel);
//...
//注册channel
ChannelFuture regFuture = config().group().register(channel);
//...
return regFuture;
}

我们到NioSocketChannel

    public NioSocketChannel(SelectorProvider provider) {
this(newSocket(provider));
} private static SocketChannel newSocket(SelectorProvider provider) {
try {
//相当于java NIO的SocketChannel.open();
return provider.openSocketChannel();
} catch (IOException e) {
throw new ChannelException("Failed to open a socket.", e);
}
} public NioSocketChannel(Channel parent, SocketChannel socket) {
//调用父类构造方法
super(parent, socket);
//初始化config属性,创建NioSocketChannelConfig对象
config = new NioSocketChannelConfig(this, socket.socket());
}

我们再回到init(Channel channel)方法,初始化Channel配置

@Override
void init(Channel channel) throws Exception {
ChannelPipeline p = channel.pipeline(); // 添加处理器到 pipeline 中
p.addLast(config.handler()); // 初始化 Channel 的可选项集合
final Map<ChannelOption<?>, Object> options = options0();
synchronized (options) {
setChannelOptions(channel, options, logger);
} // 初始化 Channel 的属性集合
final Map<AttributeKey<?>, Object> attrs = attrs0();
synchronized (attrs) {
for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
channel.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
}
}
}

至此initAndRegister() 方法已经走完,回到我们的doResolveAndConnect()方法中,继续调用doResolveAndConnect0(),解析远程地址,并进行连接

    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()) {
// 解析远程地址失败,关闭 Channel ,并回调通知 promise 异常
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 {
// 解析远程地址失败,关闭 Channel ,并回调通知 promise 异常
if (future.cause() != null) {
channel.close();
promise.setFailure(future.cause());
// 解析远程地址成功,连接远程地址
} else {
doConnect(future.getNow(), localAddress, promise);
}
}
});
} catch (Throwable cause) {
// 发生异常,并回调通知 promise 异常
promise.tryFailure(cause);
}
return promise;
}

我们走到doConnect()方法,执行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);
}
});
}

然后调用connect()方法一直断点到AbstractNioUnsafe#connect

       AbstractNioUnsafe.java
@Override
public final void connect(
final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return;
} try {
// 目前有正在连接远程地址的 ChannelPromise ,则直接抛出异常,禁止同时发起多个连接。
if (connectPromise != null) {
// Already a connect in process.
throw new ConnectionPendingException();
}
// 记录 Channel 是否激活
boolean wasActive = isActive();
// 执行连接远程地址
if (doConnect(remoteAddress, localAddress)) {
fulfillConnectPromise(promise, wasActive);
} else {
// 记录 connectPromise
connectPromise = promise;
// 记录 requestedRemoteAddress
requestedRemoteAddress = remoteAddress; // 使用 EventLoop 发起定时任务,监听连接远程地址超时。若连接超时,则回调通知 connectPromise 超时异常。
// Schedule connect timeout.
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);
if (connectPromise != null && connectPromise.tryFailure(cause)) {
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
connectPromise = null;
close(voidPromise());
}
}
});
}
} catch (Throwable t) {
// 回调通知 promise 发生异常
promise.tryFailure(annotateConnectException(t, remoteAddress));
closeIfClosed();
}
}

调用isActive()方法,获得Channel是否激活,判断SocketChannel是否处于打开,并且连接的状态,此时,一般返回false

    @Override
public boolean isActive() {
SocketChannel ch = javaChannel();
return ch.isOpen() && ch.isConnected();
}

调用doConnect()方法

   NioSocketChannel.java
@Override
protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
//绑定本地地址
if (localAddress != null) {
doBind0(localAddress);
}
boolean success = false;
try {
//连接远程地址
boolean connected = SocketUtils.connect(javaChannel(), remoteAddress);
// 若未连接完成,则关注连接( OP_CONNECT )事件
if (!connected) {
selectionKey().interestOps(SelectionKey.OP_CONNECT);
}
// 标记执行是否成功
success = true;
return connected;
} finally {
// 执行失败,则关闭 Channel
if (!success) {
doClose();
}
}
}

一般情况下NIO client是不需要绑定本地地址的. 所以我们跟踪调用链AbstractChannelHandlerContext可知,localAddress传进来的是null

    AbstractChannelHandlerContext.java
@Override
public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {
return connect(remoteAddress, null, promise);
}

所以走到boolean connected = SocketUtils.connect(javaChannel(), remoteAddress);

若连接没有完成时,我们调用selectionKey().interestOps(SelectionKey.OP_CONNECT);添加事件SelectionKey.OP_CONNECT,也就是说连接远程地址成功时,Channel对应的Selector将会轮训到该事件,可以进一步处理

if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
// remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking
// See https://github.com/netty/netty/issues/924
int ops = k.interestOps();
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops); unsafe.finishConnect();
}

我们进入到AbstractNioChannel#finishConnect方法中

        @Override
public final void finishConnect() {
// Note this method is invoked by the event loop only if the connection attempt was
// neither cancelled nor timed out.
//判断是否在EventLoop的线程中
assert eventLoop().inEventLoop(); try {
//获得Channel是否激活
boolean wasActive = isActive();
//执行完成连接
doFinishConnect();
//通知connectPromice连接完成
fulfillConnectPromise(connectPromise, wasActive);
} catch (Throwable t) {
//通知connectPromice连接异常
fulfillConnectPromise(connectPromise, annotateConnectException(t, requestedRemoteAddress));
} finally {
// 取消 connectTimeoutFuture 任务
// Check for null as the connectTimeoutFuture is only created if a connectTimeoutMillis > 0 is used
// See https://github.com/netty/netty/issues/1770
if (connectTimeoutFuture != null) {
connectTimeoutFuture.cancel(false);
}
// 置空 connectPromise
connectPromise = null;
}
}

在调试的过程中我们可以知道isActive();返回false,说明连接还没完成,然后调用doFinishConnect();

    @Override
protected void doFinishConnect() throws Exception {
if (!javaChannel().finishConnect()) {
throw new Error();
}
}

在这里调用java NIO的方法进行连接,然后调用fulfillConnectPromise(ChannelPromise promise, boolean wasActive)

        private void fulfillConnectPromise(ChannelPromise promise, boolean wasActive) {
if (promise == null) {
// Closed via cancellation and the promise has been notified already.
return;
}
// 获得 Channel 是否激活
// Get the state as trySuccess() may trigger an ChannelFutureListener that will close the Channel.
// We still need to ensure we call fireChannelActive() in this case.
boolean active = isActive();
// 回调通知 promise 执行成功
// trySuccess() will return false if a user cancelled the connection attempt.
boolean promiseSet = promise.trySuccess();
// 若 Channel 是新激活的,触发通知 Channel 已激活的事件。
// Regardless if the connection attempt was cancelled, channelActive() event should be triggered,
// because what happened is what happened.
if (!wasActive && active) {
pipeline().fireChannelActive();
} // If a user cancelled the connection attempt, close the channel, which is followed by channelInactive().
if (!promiseSet) {
close(voidPromise());
}
}

在这里isActive();已返回true,说明已成功连接.

然后回调通知promise执行成功,如果你在connect(...)方法返回的ChannelFuture 的 ChannelFutureListener 的监听器,那么会执行里面的通知.

最后调用pipeline().fireChannelActive();触发Channel激活的事件.调用channelActive()doBeginRead()走server端一样的代码,设置的 readInterestOp = SelectionKey.OP_READ 添加为感兴趣的事件

走完doConnect(remoteAddress, localAddress)我们断点可知返回了false,所以是不会走fulfillConnectPromise(promise, wasActive);,而是执行else分支.

int connectTimeoutMillis = config().getConnectTimeoutMillis();这里可以知道,设置了一个30s的时间

    @Override
public int getConnectTimeoutMillis() {
return connectTimeoutMillis;
}
private volatile int connectTimeoutMillis = DEFAULT_CONNECT_TIMEOUT; private static final int DEFAULT_CONNECT_TIMEOUT = 30000;

调用 EventLoop#schedule(Runnable command, long delay, TimeUnit unit) 方法,发起定时任务connectTimeoutFuture,监听连接远程地址是否超时,并回调connectPromise超时异常

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);
if (connectPromise != null && connectPromise.tryFailure(cause)) {
close(voidPromise());
}
}
}, connectTimeoutMillis, TimeUnit.MILLISECONDS);
}

调用ChannelPromise#addListener(ChannelFutureListener)方法,添加监听器,监听连接远程地址是否取消.若取消,则取消 connectTimeoutFuture 任务,并置空 connectPromise 。这样,客户端 Channel 可以发起下一次连接。

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());
}
}
});

至此为止,doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress)已经全部分析完毕.

Netty源码解析—客户端启动的更多相关文章

  1. Netty源码解析---服务端启动

    Netty源码解析---服务端启动 一个简单的服务端代码: public class SimpleServer { public static void main(String[] args) { N ...

  2. Netty 源码解析(八): 回到 Channel 的 register 操作

    原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第八篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源 ...

  3. Netty 源码解析(五): Netty 的线程池分析

    今天是猿灯塔“365篇原创计划”第五篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源码解析(二): Netty 的 Channel Netty ...

  4. Netty 源码解析(二):Netty 的 Channel

    本文首发于微信公众号[猿灯塔],转载引用请说明出处 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty源码解析(一):开始 当前:Netty 源码解析(二): Netty 的 Channel ...

  5. Netty 源码学习——客户端流程分析

    Netty 源码学习--客户端流程分析 友情提醒: 需要观看者具备一些 NIO 的知识,否则看起来有的地方可能会不明白. 使用版本依赖 <dependency> <groupId&g ...

  6. Netty 源码解析(九): connect 过程和 bind 过程分析

    原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第九篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源 ...

  7. Netty 源码解析(七): NioEventLoop 工作流程

    原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365篇原创计划”第七篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源 ...

  8. Netty 源码解析(四): Netty 的 ChannelPipeline

    今天是猿灯塔“365篇原创计划”第四篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源码解析(二): Netty 的 Channel Netty ...

  9. Netty 源码解析(三): Netty 的 Future 和 Promise

    今天是猿灯塔“365篇原创计划”第三篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源码解析(二): Netty 的 Channel 当前:Ne ...

随机推荐

  1. if语句简单练习

    input练习 # -*-coding:utf-8 -*- # import getpass 隐藏只能在cmd中使用 user=input("请输入账号:") password=i ...

  2. CSS3常用转换总结

    一.2D转换 translate(npx,npx) 相对当前元素位置移动 /* 实现div向左移动50个像素,并向下移动100个像素 */ div { transform: translate(50p ...

  3. ML.NET技术研究系列-2聚类算法KMeans

    上一篇博文我们介绍了ML.NET 的入门: ML.NET技术研究系列1-入门篇 本文我们继续,研究分享一下聚类算法k-means. 一.k-means算法简介 k-means算法是一种聚类算法,所谓聚 ...

  4. Java 垃圾收集总结

    概述 垃圾收集(Garbage Collection,GC),它不是Java语言的伴生产物,它的历史比Java还要久远. 人们主要思考GC需要完成的3件事情: 哪些内存需要回收? 什么时候回收? 如何 ...

  5. Java基础之回味finally

    平时大家try…catch…finally语句用的不少,知道finally块一定会在try…catch..执行结束时执行,但是具体是在什么时候执行呢,今天我们一起来看下. public static ...

  6. EPG开发《异常排查以及解决方案》

    [框架]

  7. 修改Windows10的host文件。

    一.Windows10中host地址. c:\windows\system32\drivers\etc\hosts 其他系统中的位置. Windows操作系统(Windows XP/7/8/10): ...

  8. 微信小程序之楼层效果

    今天做了一个小程序实现一个楼层效果  带大家分享下经验和api的使用吧 如图 将左边和右边各分了一个组件  目录如下 其中list页面是这个楼层效果的页面 components是组成这个页面的两个组件 ...

  9. pip install xxxx报错(一大堆红色exception)【解决】

    安装个distribute或nose或lpthw.web或virtualenv 都可能出现下面问题   root@kali:~# pip install distribute Collecting d ...

  10. TCP、UDP和HTTP简述整理

    http:是用于www浏览的一个协议.tcp:是机器之间建立连接用的到的一个协议. 1.TCP/IP是个协议组,可分为三个层次:网络层.传输层和应用层.在网络层有IP协议.ICMP协议.ARP协议.R ...