每日一诗

余晖洒下一缕缕碎波潺潺
  晚风里灯影柔曼
  荡漾着湖水清清倩影婉转
  青底白花油纸伞
  采一片莲叶清香飘在两岸
  长亭和垂柳作伴
  暗下来薄雾不散不见远山
  一方天地风轻水软

水绿天蓝蓝 白鹭一行路辗转
  飞过芦苇滩 停在相思畔
  听船歌声声慢
  长忆相见欢 多少柔情落江南
  一曲唱不完 此去几时还
  明月照两端
  落红时四野尽染你在对岸
  留不住彩云易散
  雾尽时月影微寒独上远山
  两处别离 良宵短

——毛不易《水乡》

一、客户端BootStrap

Bootstrap 是Netty 提供的一个便利的工厂类, 我们可以通过它来完成Netty 的客户端或服务器端的Netty 初始化。下面是我之前学习过程中根据老师的课程手写的rpc的一个demo代码, 从客户端和服务器端分别分析一下Netty 的程序是如何启动的。

......       EventLoopGroup group = new NioEventLoopGroup();Bootstrap client = new Bootstrap();client.group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer() {@Overrideprotected void initChannel(Channel ch) throws Exception {//接收课客户端请求的处理流程ChannelPipeline pipeline = ch.pipeline();int fieldLength = 4;//通用解码器设置pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,fieldLength,0,fieldLength));//通用编码器pipeline.addLast(new LengthFieldPrepender(fieldLength));//对象编码器pipeline.addLast("encoder",new ObjectEncoder());//对象解码器pipeline.addLast("decoder",new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));pipeline.addLast("handler",consumerHandler);}}).option(ChannelOption.TCP_NODELAY, true);ChannelFuture future = client.connect("localhost",8080).sync();future.channel().writeAndFlush(msg).sync();future.channel().closeFuture().sync();
......     

从上面的客户端代码虽然简单, 但是却展示了Netty 客户端初始化时所需的所有内容:

  1. EventLoopGroup:不论是服务器端还是客户端, 都必须指定EventLoopGroup。在这个例子中, 指定了NioEventLoopGroup, 表示一个NIO 的EventLoopGroup。
  2. ChannelType: 指定Channel 的类型。因为是客户端,因此使用了NioSocketChannel。
  3. Handler: 设置处理数据的Handler。

让我们来聚焦下面这段代码:

 client.group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer() {
......
}

group()方法、channel()方法、handler方法分别是干嘛的呢:

  • group:我们可以通过 group 方法指定对应的线程组
  • channel:设置channel的类型,它为用户提供了关于Socket 状态(是否是连接还是断开)以及对Socket的读写等操作
  • handler: ChannelHandler负责I/O事件或者I/O操作进行拦截和处理,用户可以通过ChannelHandlerAdapter来选择性的实现自己感兴趣的事件拦截和处理

二、NioSocketChannel

通过上面的代码,我们可以知道,客户端channel类型应该设置成NioSocketChannel.class类型。那么channel肯定还有其他类型,下面我们来看看channel其他类型。

2.1 Channel分类

在Netty 中,Channel 是一个Socket 的抽象,它为用户提供了关于Socket 状态(是否是连接还是断开)以及对Socket的读写等操作。每当Netty 建立了一个连接后, 都创建一个对应的Channel 实例。除了TCP 协议以外,Netty 还支持很多其他的连接协议, 并且每种协议还有NIO(非阻塞IO)和OIO(Old-IO, 即传统的阻塞IO)版本的区别。不同协议不同的阻塞类型的连接都有不同的Channel 类型与之对应下面是一些常用的Channel类型。

在Netty里,Channel是通讯的载体,而ChannelHandler负责Channel中的逻辑处理。它是Netty网络通信的主体,由它负责同对端进行网络通信、注册和数据操作等功能。

  • 状态主要包括:打开、关闭、连接
  • 主要的IO操作,读(read)、写(write)、连接(connect)、绑定(bind)。
  • 所有的IO操作都是异步的,调用诸如read,write方法后,并不保证IO操作完成,但会返回一个凭证,在IO操作成功,取消或失败后会记录在该凭证中。
类名 解释
NioSocketChannel 异步非阻塞的客户端TCP Socket连接。
NioServerSocketChannel 异步非阻塞的服务器端TCP Socket连接。
NioDatagramChannel 异步非阻塞的UDP连接。
NioSctpChannel 异步的客户端(Stream Control Transmission Protocol,流控制传输协议)连接。
NioSctpServerChannel 异步的Sctp服务器端连接。
OioSocketChannel 同步阻塞的客户端TCP Socket连接。
OioServerSocketChannel 同步阻塞的服务器端TCP Socket连接。
OioDatagramChannel 同步阻塞的UDP连接。
OioSctpChannel 同步的Sctp服务器端连接。
OioSctpServerChanner 同步的客户端TCP Socket连接。

个人认为:上表中NioSocketChannel、NioServerSocketChannel、NioDatagramChannel、NioSctpChannel、NioSctpServerChannel应该都是同步非阻塞,不可能是异步的。

查看一下Channel的总体类图:

2.2 NioSocketChannel 的创建

NioSocketChannel 的创建的过程调用图,根据这个图,我们可以对这个过程有总体的了解:

先看看NioSocketChannel类图:

下面我们将根据图中的方法开发代码追踪过程。

2.2.1 channe()[配置入口]

可以看到channel方法中传入了NioSocketChannel.class类,我们是否可以猜想,这个字节码文件是给后面反射用的?

点进channel方法:

可以看到进入了AbstractBootstrap#channel方法,我用红框和绿框区分开来了,现在我们分析红框:

new ReflectiveChannelFactory<C>(channelClass)

前面传的 NioSocketChannel.class参数,是直接作为ReflectiveChannelFactory的构造函数传进去了。
ReflectiveChannelFactory从名字可以看出来是一个工厂类。

继续往下看ReflectiveChannelFactory的构造函数:

可以看到我们传入的NioSocketChannel.class作为一个属性,放到了ReflectiveChannelFactory对象中。想必是后面有用处。

再来看点开绿框#channelFactory方法:

可以看到前面生成ReflectiveChannelFactory工厂对象是赋值到了AbstractBootstrap对象中的channelFactory属性中。那么问题来了,既然AbstractBootstrap中的channelFactory属性是在什么时候用到的呢?

别急,且听我接下来慢慢道来。

2.2.2 newChannel()[反射创建Channel对象]

我们把目光聚焦到前面第一节中的例子代码中的#connect方法:

点开#connect方法:

三个调用接连调用的方法都在Bootstrap类,巧了都在一块,方便我们截图了,可以看到最后是调用#doResolveAndConnect方法

继续追踪#doResolveAndConnect方法:

我红框部分标注了initAndRegister()方法,它是channel的初始化和注册事件到Selector上。

接下来我们继续追踪initAndRegister()方法:

可以看到走到了AbstractBootstrap#initAndRegister方法,聚焦红框1这行代码(绿框2和绿框3我们后面再分析),前面设置的AbstactBootstrap的channelFactory属性用上了,在这里调用了他的newChannel()方法,继续追踪channelFactory.newChannel()方法:

可以看到他有好几个实现,还记得吗,我们前面传的是ReflectvieChannelFactory对象,所以点开ReflectvieChannelFactory的实现:

看到没,是利用反射得到的对象!!!证明了我们的猜想。还记得我们前面传的clazz是什么类型吗?
——是NioSocketChannel.class!!!

2.2.3 反射调用的NioSocketChannel构造方法创建客户端

那下面我们是要干什么呢?既然是用NioSocketChannel.class的无参构造方法反射出来的对象,那么我们就跟进一下NioSocketChannel.class的无参构造方法:

特别说明:

可以看到,也是一连串的构造函数调用,调用顺序为上图标注的1->2->3->4红框部分,4中红框部分调用了父类的构造方法,继续追踪:

如上图,追踪到这里有一个细节需要我们注意一下:SelectionKey.OP_READ可见客户端的channel只会注册读事件到Selector上

先看上图绿框1部分:

这里就是将我们上面传的SelectionKey.OP_READ,赋值到readInterestOp属性上。

再看看上图绿框2部分:

ch.configureBlocking(false);

这里是不是特别熟悉,我们在Nio的原生demo中使用过它,作用是将阻塞改为非阻塞。

接下来再继续追踪红框部分:

可以看到走到了最顶层AbstractChannel的构造方法,它做的就是设置了四个属性:parent、id、unsafe、pipeline。前一篇文章对于它们已经介绍过了,现在我贴一下:

第一个属性是this.parent,类型为io.netty.channel.Channel,但此时值为null;
第二个属性id——每个channel都会生成。类型为io.netty.channel.ChannelId,就是一个id生成器,值为new DefaultChannelId();
第三个属性unsafe——在每个channel之上,要去完成数据读写的对象。类型为io.netty.channel.Channel.Unsafe,该属性很重要,封装了对事件的处理逻辑,最终调用的是AbstractNioMessageChannel中的newUnsafe方法,赋的值为new NioMessageUnsafe();
第四个属性pipeline——数据处理的流程全在这里。类型为io.netty.channel.DefaultChannelPipeline,该属性很重要,封装了handler处理器的逻辑,赋的值为 new DefaultChannelPipeline(this)  this即当前的NioServerSocketChannel对象。

其中DefaultChannelPipeline的构造器需要额外看一下,如下,将NioServerSocketChannel对象存入channel属性,然后初始化了tail、head两个成员变量,且对应的前后指针指向对方。TailContext和HeadContext都继承了AbstractChannelHandlerContext,在这个父类里面维护了next和prev两个双向指针,看到这里有经验的园友应该一下子就能看出来,DefaultChannelPipeline内部维护了一个双向链表。

这几个个对象不是jdk nio包提供的,是netty封装的。

闲话不多说,继续追踪上图的newId()方法、newUnsafe()方法、newChannelPipeline()方法:

newId():略。

2.2.3.1 newUnsafe():

我们简单地提到了,在实例化NioSocketChannel的过程中,会在父类Ab市actChannel的构造方法中调用 newUnsafe()来获取一个unsafe实例。那么unsafe是怎么初始化的呢?它的作用是什么?

其实unsafe特别关键,它封装了对Java底层Socket的操作,因此实际上是沟通Netty上层和Java底层的重要的 桥梁。那么我们下面看一下Unsafe接口所提供的方法吧:

从源码可以看出来,这些方法都是对Java底层的操作。

点击该方法,有多个实现,因为我们之前传的是NioSocketChannel,所以选择它:

可以看到接连调用了上图的两个方法,关于这部分其他的就不做阐述了。

2.2.3.2 newChannelPipeline():

点进去newChannelPipeline()——>new DefaultChannelPipeline(this)

可以看到,调用到了DefaultChannelPipeline的构造方法,红框部分是pipeline将nioSocketChannel保存在channel对象中。

tail和head:

绿框中tail和head两个特殊字段,这里简单介绍一下,后面章节再做讲解:

  • tail——TailContext对象,TailContext是定义在DefaultChannelPipeline的内部类
  • head——HeadContext对象,TailContext是定义在DefaultChannelPipeline的内部类

它们最上层是一个ChannelHandler。

先贴下head和tail类图:

再看下它们的源码:

从源码看出:他俩一个inbound为false,一个outbount为true。后续再做其他分析。

2.2.3.3 NioSocketChannel初始化总结:

至此,NioSocketChannel的初始化就完成了,我们可以稍微总结一下NioSocketChannel初始化所做的工作内容

  1. 调用NioSocketChannel.newSocket(DEFAULT_SELECTOR_PROVIDER)打开一个新的Java NIOSocketChannel。
  2. AbstractChannel中初始化的属性:id、parent、unsafe、pipeline
  3. AbstractNioChannel的属性:
    ch:赋值为 Java SocketChannel,即 NioSocketChannel 的 newSocketf) 方法返回的 Java NIO SocketChannel。
    readInterestOp:赋值为 SelectionKey.OP_READ
    ch:被配置为非阻塞,即调用ch.configureBlocking(felse)。
  4. NioSocketChannel中的属性:
    config=new NioSocketChannel(this,socket.socket())

2.2.4 init()[初始化客户端channel]

还记得2.2.2小节的这张图吗,绿框2部分就是初始化客户端channel的部分:

点开上图绿框2中#init方法:

上述框1,框2,框3单看代码也很容易懂:

  1. 取出pipeline,获取handler,添加到pipeline中
  2. 添加options到配置中
  3. 添加attr到配置中

这段代码跟前面demo代码这段代码是不是就呼应起来了,就是我们手动设置的:

三、EvenLoop

下面是EvenLoop的初始化过程,我们可以通过图对这个过程有总体的了解:

首先看看NioEventLoopGroup类图:

看看它的顶层接口是什么?是Executor!!!也就是说它就是线程池呀!并且是ScheduleExecutorService——定时任务的线程池。
——那么我是否可以推测什么呢?

3.1 NioEventLoopGroup()[构造方法]

我们先从demo代码中找到创建NioEventLoopGroup的代码:

点进去查看构造方法:

接连追踪多个NioEventLoopGroup构造方法,可以看到绿框中设置了:selectorProvider ,选择器策略工厂DefaultSelectStrategyFactory.INSTANCE 最终都是调用的父类MultithreadEventLoopGroup的构造器。其中 DefaultEventExecutorChooserFactory 就是事件执行器选择策略的创建工厂。
另外无参构造函数设置线程数是0,为什么会传0呢?且往下继续追踪。

3.3.1 无参构造函数设置线程数是0:

可以看到如果传入的线程数为0,Netty 首先会从系统属性中获取"io.netty.eventLoopThreads"的值,如果我们没有设置的话,那么会设置默认的线程数,。DEFAULT_EVENT_LOOP_THREADS值是多多少呢?

Runtime.getRuntime().availableProcessors() * 2是什么含义呢?

Runtime.getRuntime().availableProcessors()的含义是获取CPU核数,也就是线程数默认设置为CPU核数的2倍。这里就涉及到如何合理地设置线程池的核心线程数。
——一般来说,任务中IO多的就设置CPU核数的2倍,cpu计算多的就设置CPU核数+1

3.2 childen=new EventExecutor[nThreads]、 newChild()[初始化线程池]

继续往下追踪,因为代码太长,不好截图,所以贴了只关键代码如下:

protected MultithreadEventExecutorGroup(int nThreads, Executor executor,EventExecutorChooserFactory chooserFactory, Object... args) {。。。。。。children = new EventExecutor[nThreads];for (int i = 0; i < nThreads; i ++) {。。。。。。children[i] = newChild(executor, args);     }。。。。。。chooser = chooserFactory.newChooser(children);//去掉以下代码}

线程的元素就是一个个的NioEventLoop对象。

给事件线程池数组赋值。

newChild()[初始化线程池]:

这里其实就是创建了一个大小为 nThreads 的事件执行器数组,第二步是初始化该数组,紧接着通过选择策略工厂 EventExecutorChooserFactory 创建选择策略。首先看一下他的初始化,调用newChhild()方法初始化children 数组。根据上面的代码,我们也能知道:MultithreadEventExecutorGroup 内部维护了一个EventExecutor 数组,而Netty 的EventLoopGroup 的实现机制其实就建立在MultithreadEventExecutorGroup 之上。每当Netty 需要一个EventLoop 时,会调用next()方法获取一个可用的EventLoop。newChild()方法是一个抽象方法,它的任务是实例化EventLoop 对象。我们跟踪一下它的代码。可以发现。这个方法在NioEventLoopGroup 类中有实现,其内容很简单:

上图中红框的部分是什么呢?
——线程池的拒绝策略,是再NioEventLoopGroup构造方法中传进去的,如下:

红框中多出的任务丢弃并且抛出异常。

继续追踪:NioEventLoop构造方法

此处不再继续追踪了。再往后点,可以发现:

  1. 创建selector
  2. 关联NioEventLoop
  3. newTaskQueue()创建MPSC队列

3.2.1 MultithreadEventExecutorGroup处理逻辑总结:

  1. 创建一个大小为 nhreads 的SingleThreadEventExecutor
  2. 根据nThreads的大小,创建不同的Chooser,即如果nThreads是2的幂,则使用 PowerOfTwoEventExecutorChooser,反之使用 GenericEventExecutorChooser。不论使用哪个 Chooser,它们的功 能都是一样的,即从children数组中选出一个合适的EventExecutor实例。
  3. 调用newChhild()方法初始化children数组。

3.3 调用next()方法获取下一个EventLoop、newChooser()[线程池优化]

可以看到这是设置next()方法线程的算法:

  • powerOfTow:求与
  • Generic-普通,取模

取模与求与,结果是一样的。只是当数据量大的时候,求与比取模效率高。

3.5 NioEventLoopGroup初始化总结

EventLoopGroup的初始化过程:

  1. EventLoopGroup(其实是 MultithreadEventExecutorGroup)内部维护一个类型为 EventExecutor children 数组, 其大小是nThreads这样就构成了一个线程池。
  2. 如果我们在实例化NioEventLoopGroup时,如果指定线程池大小,则nThreads就是指定的值,反之是处理 器核心数* 2。
  3. MuItithreadEventExecutorGroup 中会调用 newChild()抽象方法来初始化 children 数组。
  4. 抽象方法newChild()是在NioEventLoopGroup中实现的,它返回一个NioEventLoop实例。
  5. NioEventLoop 属性赋值:
    provider:在 NioEventLoopGroup 构造器中通过 SelectorProvider.provide()获取一个SelectorProvider
    selector:在NioEventLoop构造器中通过调用通过provider.openSelector()方法获取一个selector对象

3.5 结论

  • NioEventLoopGroup实际上就是个线程池
  • NioEventLoopGroup在后台启动了n个NioEventLoop来处理Channel事件
  • 每一个NioEventLoop负责处理m个Channel
  • NioEventLoopGroup从NioEventLoop数组里挨个取出NioEventLoop来处理Channel

四、Channal注册到Selector

照惯例,上图:

4.1 AbstractBootstrap.initAndRegister()

在前面的分析中,我们提到Channel会在Bootstrap的initAndRegister()中进行初始化,但是这个方法还会将初始 化好的Channe注册到NioEventLoop的selector中。接下来我们来分析一下Channel注册的过程。 再回顾一下 AbstractBootstrap 的 initAndRegister() 方法:

4.2 MultithreadEventLoopGroup.register()

4.3 SingleThreadEventLoop.register()

4.4 channel.unsafe().register(this,promise)

4.5 AbstractNioChannel.doRegister()

把selector传进去,并且ops是0,表示未注册任何事件。

4.6 Channel的注册过程总结

  1. 首先在AbstractBootstrap的initAndRegister()方法中,通过 group().register(channel),调用 MuItithreadEventLoopGroup的register()方法。
  2. 在 MultithreadEventLoopGroup 的 register()中,调用 next()方法获取一个可用的 SingleThreadEventLoop,然 后调用它的register()方法。
  3. 在 SingleThreadEventLoop 的 register()方法中,调用 channel.unsafe().registe(this, promise)方法来获取 channel的unsafe()底层操作对象,然后调用unsafe的register()。
  4. 在 AbstractUnsafe 的 register()方法中,调用 register()方法注册 Channel 对象。
  5. 在 AbstractUnsafe 的 register()方法中,调用 AbstractNioChannel 的 doRegister()方法。
  6. AbstractNioChannel 的 doRegister()方法通过ljavaChannel().register(eventLoop().selector, 0, this)将 Channel 对应的 Java NIO 的 Socketchannel 注册到一个 eventLoop 的 selector 中,并且将当前 Channel 作为 attachment 与 SocketChannel 关联。

总的来说,Channel注册过程所做的工作就是将Channel与对应的EventLoop关联,因此这也体现了,在Netty 中,每个Channel都会关联一个特定的EventLoop,并且这个Channel中的所有的操作都是在这个EventLoop中执行的;当关联好Channel和EventLoop后,会继续调用底层Java NIO的Socketchannel对象的register()方法,将底 层Java NIO的Socketchannel注册到指定的selector中。通过这两步,就完成了 Netty对Channel的注册过程。

五、handler的添加

Netty有一个强大和灵活之处就是基于Pipeline的自定义handler机制。基于此,我们可以像添加插件一样自由组 合各种各样的handler来完成业务逻辑。例如我们需要处理HTTP数据,那么就可以在pipeline前添加一个针对HTTP 编、解码的Handler,然后接着添加我们自己的业务逻辑的handler,这样网络上的数据流就向通过一个管道一样,从 不同的handler中流过并进行编、解码,最终在到达我们自定义的handler中。 说到这里,有些小伙伴肯定会好奇,既然这个pipeline机制是这么的强大,那么它是怎么实现的呢?在此我们这一章还不打算详细讲解,在本内容中,我们就体验一下handler是如何添加到ChannelPipeline中的。

先看关键方法handler:

handler是怎样添加到pipeline中的:pipeline可以自定义,这样我们就可以自由的组合各种各样的handler来处理不同的需求。网络上的数据像流一样,通过管道一层层的流过,经过不同的编解码等或其他处理。
pipeline暂时不做讲解,后面专门讲。

5.1 channelRegistered()[注册后触发事件]

ChannelInitializer有一个方法channelRegistered:

5.2 channelRegistered.initChannel()[内部方法]

channelRegistered调用了initChannel方法:

红框部分是一个钩子方法,他会调用我们自己写的initChannel方法:保存到initMap中去了。

Channellnitializer是一个抽象类,它有一个抽象的方法initChannel(),我们看到的匿名类正是实现了这个方法, 并在这个方法中添加的自定义的handler的。那么initChannel()是哪里被调用的呢淇实是在Channellnitializer的 channelRegistered()方法中。
接下来关注一下channelRegistered()方法。从上面的源码中,我们可以看到,在channelRegistered()方法中,会 调用 initChannel。方法,将自定义的 handler添加到 ChannelPipeline 然后调用 ctx.pipeline0.remove(this)方法将自己从pipeline中删除。上述的过程分析如下图片所示:

分析到这里,已经简单的了解自定义的handler是如何添加到pipeline中的,之后的章节我们再做深入的探讨。

六、客户端发起请求

经过上面的各种分析后,我们大致已经了解netty客户端初始化时,所做的工作,那么接下来我们直奔主题,分析客户端是如何发起tcp连接的。

6.1 connect()[客户端发起连接请求]

调用的是Bootstrap#connect方法:

6.2 doResolveAndConnect0()[内部方法]

6.3 doConnect()[内部方法]

追踪多个调用方法,直到Bootstrap#doConnect方法:

在doCoConnect方法中,会在eventLoop线程中调用Channel的connect()方法,而这个Channel的具体类型实际上就是NioSocketChannel,前面已经分析过了。继续追踪到channel#connect方法。

6.4 AbstractChannel.connect()

追踪多个调用方法,直到发现它调用的是DefaultChannelPipeline的connect()方法,pipeline的connect方法如下:

tail我们前面已经分析过了,他是一个TailContext实例,而TailContext又是AbstactChannelHandlerContext的子类,并且没有实现connect方法,因此调用的是AbstactChannelHandlerContext#connect方法,下面来看一下它的实现。

6.5 pipeline.connect()[调用head.connect()]

上面有一处关键代码——findContextOutbound(),调用findContextOutbound()方法,从DefaultChannelPipeline内的双向链表tail开始,不断向前查找第一个outbound为true的AbstractChannelHandlerContext,然后调用它的invokeChannel方法,其代码如下:

前面我们有提到,在DefaultChannelPipeline的构造器中,实例化了两个对象:head和tail,并形成了双向链表的头 和尾。head 是 HeadContext 的实例,它实现了 ChannelOutboundHandler 接口,并且它的outbound 设置为 true。因此在 findContextOutbound()方法中,找到 AbstractChannelHandlerContext 对象其实就是 head。进而在 invokeConnect()方法中,我们向上转换为ChanneOutboundHandler就是没问题的了。而又因为HeadContext重写 了 connect()方法,因此实际上调用的是HeadContext的connect()方法。我们接着跟踪到HeadContext的connect()方法,其代码如下:

这个connect()方法很简单,只是调用了 unsafe的connect()方法。回顾一下HeadContext的构造器,我们发现这个 unsafe其实就是pipeline.channel().unsafe()返回的Channel的unsafe字段。到这里为止,我们应该已经知道其实是AbstractNioByteChannel.NioByteUnsafe内部类兜了一大圈。最后,我们找到创建Socket连接的关键代码继续跟踪,其实调用的就是AbstractNioUnsafe的connect()方法:

在这个connect()方法中,又调用了 doConnect()方法。注意:这个方法不是AbstractNioUnsafe的方法,而是 AbstractNioChannel 的抽象方法。doConnect()方法是在 NioSocketChannel 中实现的,因此进入到 NioSocketChannel 的 doConnect() 方法中:

6.6 NioSocketChannel.connect()

我们终于看到的最关键的部分了,庆祝一下!上面的代码不用多说,首先是获取JavaNIO的SocketChannel,获取 NioSocketChannel 的 newSocket。返回的 Socketchannel 对象;然后调用 Socketchannel 的 connect()方法完成 Java NIO底层的Socket的连接。最后总结一下,客户端Bootstrap发起连接请求的流程可以用如下时序图直观地展示:

揭开BootStrap的神秘面纱相关推荐

  1. 了解黑客的关键工具---揭开Shellcode的神秘面纱

    2019独角兽企业重金招聘Python工程师标准>>> ref:  http://zhaisj.blog.51cto.com/219066/61428/ 了解黑客的关键工具---揭开 ...

  2. [转]揭开正则表达式的神秘面纱

    揭开正则表达式的神秘面纱 关闭高亮 [原创文章,转载请保留或注明出处:http://www.regexlab.com/zh/regref.htm] 引言 正则表达式(regular expressio ...

  3. 揭开PC-Lint9的神秘面纱

    前言 今天,又定位了一个令人懊恼的C++内存使用异常问题,最终结果,竟然是减少接口类的方法后,为了避免编译错误,顺手添加的强制类型转换导致的. 对于这样的问题,我们碰到很多很多次了.没有这样的问题,我 ...

  4. 未来已来?揭开量子计算机的神秘面纱

    从第一台现代计算机ENIAC的诞生到个人PC时代的降临,从互联网概念的提出到移动互联的疾跑,在这个信息年代里,变革正以前所未有的速度改变着我们熟悉的世界.熟悉的生活. 作为个人,我们早已习惯于智能计算 ...

  5. ASP.NET 运行时详解 揭开请求过程神秘面纱

    对于ASP.NET开发,排在前五的话题离不开请求生命周期.像什么Cache.身份认证.Role管理.Routing映射,微软到底在请求过程中干了哪些隐秘的事,现在是时候揭晓了.抛开乌云见晴天,接下来就 ...

  6. linux操作系统说课稿,信息技术《揭开LINUX的神秘面纱》教案范文

    信息技术<揭开LINUX的神秘面纱>教案范文 教学目标: 1.会启动LINUX系统: 2.会关闭LINUX系统: 3.LINUX基本界面的认识. 教学重点: 1.会启动LINUX系统: 2 ...

  7. 冰河浅析 - 揭开木马的神秘面纱(下)

    冰河浅析   -   揭开木马的神秘面纱(下)     作者:·   shotgun·yesky 四.破解篇(魔高一尺.道高一丈)         本文主要是探讨木马的基本原理,   木马的破解并非是 ...

  8. 揭开木马的神秘面纱 2

    揭开木马的神秘面纱zz 2 离冰河二的问世已经快一年了,大家对于木马这种远程控制软件也有了一定的认 识,比如:他会改注册表,他会监听端口等等,和一年前几乎没有人懂得木马是什么东   西相比,这是一个质 ...

  9. 【翻译】揭开HTML5的神秘面纱

    写在前面的话: 这篇文章摘自Mozilla官网,主要针对HTML5和本地应用发表了一些.没有设计到技术,所以基本是逐字翻译,但愿我蹩脚的英语水平能把大师的 Chris Heilmann的思想整理明白. ...

  10. 初识5G - 揭开5G的神秘面纱 从零开始学习

    揭开5G的神秘面纱 前言 一.移动通信的发展历程 二.5G的技术指标 | 应用场景 (1)七大关键技术指标 (2)应用场景 VR虚拟现实(Virtual Reality) AR增强现实(Augment ...

最新文章

  1. 10月21日!API 大赛决赛暨移动云开发者论坛邀您见证数字创新的力量
  2. STM32F405的 ADC参考电压选择问题
  3. QCustomPlot使用手册(二)
  4. 工业互联网智能智造-工业企业大数据汇聚通道-产品设计
  5. FlatBuffers要点
  6. Vue插值文本换行问题
  7. C++新特性探究(十八):智能指针
  8. java class object_[java]Class类和Object类的关系
  9. 浅谈APP与H5对比!优势都有哪些呢?
  10. 20191203每日一句
  11. 南方cass快捷键命令修改在哪_南方CASS操作快捷命令
  12. 【华为OD机试真题 JAVA】叠积木
  13. 《视觉SLAM十四讲》详细笔记
  14. Matplotlib Pyplot
  15. 中国糯米粉行业品牌竞争策略与规模现状分析报告2022-2028年
  16. PyQt5_pyqtgraph股票RSI指标
  17. 比利时和德国啤酒品牌
  18. 关于DecimalFormat的取舍问题,DecimalFormat四舍五入的坑
  19. excel中计算某年某月有多少天(函数:EOMONTH)
  20. [STM32] Mac开发STM32之Makefile

热门文章

  1. ireport在Java中不展示_编译错误:ireport with java;属性'uuid'不允许出现在元素'jasperReport'中...
  2. C/C++[codeup 2066]分组统计
  3. C/C++[PAT B level 1004,1012]
  4. Python中的输入输出(IO)
  5. AWS DeepRacer 强化学习RL,工作流程
  6. 阿里云云计算 46 阿里云DDoS防护
  7. 华为天才少年-廖明辉
  8. 特征的标准化和归一化
  9. 找出两个矩阵不同的元素_推荐系统传统推荐模型之矩阵分解
  10. python cgi模块 失败_python cgi 连接 sqlite3 失败的问题