netty源码学习之服务端客户端初始化

netty源码学习之服务端客户端初始化本篇博文从 netty 的使用例子出发 服务端 bind 方法和客户端的 connect 方法 分别分析了一下 netty 服务端和客户端的建立过程 大量篇幅 最终给予对上述理解简要总结了一次基于 netty 的一次请求和响应过程图解 希望对大家学习 netty 知识有所帮助 如果帮到你的话希望点赞 转发 一键三连让我们一起在技术的海洋遨游 奥利给

​ 在之前的文章中我们学习了 netty的io模型、netty的使用分析、netty线程模型、netty的ChannelPipeline类学习、netty的ChannelPromise类学习。下面我们从使用netty例子出发探究一下netty的工作流程(其中包括服务端和客户端的创建、一次完整的请求和响应)。

1. AbstractBootstrap类简介

在探究netty一次请求的完整流程之前我们先来了解一下XXXBootstrap组件。 它是netty为我们提供的便于我们使用的工具启动类。他以builder的模式让我们可以简单设置例如channel、TCP参数、相关处理器。其中客户端使用Bootstrap类,服务端使用ServerBootstrap类两者的基类为AbstractBootstrap类。

在这里插入图片描述

1.1. 核心方法

AbstractBootstrap的

  • bind():netty服务端的与端口绑定,也是建立netty服务端的核心方法

Bootstrap的

  • connect():netty客户端与服务端进行连接,也是建立netty客户端的核心方法

补充说明:netty服务底层是使用NIO原理实现的,所以此处分析netty相关源码我们也分析到NIO层,至于NIO的底层原理实现请期待笔者后续的博文。

2. netty服务端创建

2.1. 服务端启动入口

​ netty服务端创建相关代码如下,服务端创建是使用bind()核心方法,下面来分析bind方法

//一、netty 服务端启动  //.......省略ServerBootstrap构建过程 //绑定端口 netty服务端创建 ChannelFuture sync = serverBootstrap.bind(port).sync(); //服务监听端口关闭 sync.channel().closeFuture().sync(); 

bind()方法最终调用io.netty.bootstrap.AbstractBootstrap#doBind()方法,下面我们来分析netty服务端启动方法。

2.2. doBind()方法

private ChannelFuture doBind(final SocketAddress localAddress) { 
    //正如其方法名一样 初始化channel,并进行channel和selector(多路复用器的注册绑定) //其返回值是netty自己封装的异步结果接口ChannelFuture final ChannelFuture regFuture = initAndRegister(); final Channel channel = regFuture.channel(); //如果异步结果中有异常则直接返回 此时:netty服务端初始化失败 if (regFuture.cause() != null) { 
    return regFuture; } final ChannelPromise promise; //上述的initAndRegister的结果是异步通知的 所有此处需要进行条件分支判断 //如果上述操作已经完成(通道初始化完成)则直接进行doBind0()方法 if (regFuture.isDone()) { 
    promise = channel.newPromise(); //channel和端口进行绑定操作 doBind0(regFuture, channel, localAddress, promise); } else { 
    //一般情况下initAndRegister会很快完成并给出结果 但是万一呢? promise = new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE); //此处针对如果上述未完成 添加一个事件监听,待上述操作完成后触发doBind0() regFuture.addListener(new ChannelFutureListener() { 
    @Override public void operationComplete(ChannelFuture future) throws Exception { 
    doBind0(regFuture, channel, localAddress, promise); } }); } return promise; } 

netty服务端启动和绑定过程并不复杂主要分成了两部分(两个核心方法)

  • 初始化: initAndRegister方法,该方法主要功能是创建Channel对象(网络请求的数据流通道),并整合ServerBootstrap相关配置比如工作线程(NioEventLoopGroup)、option(参数配置)、attr(属性)、添加相关处理器(ChannelHandler),最后将多路复用器Selector注册到Channel。
  • 端口绑定:doBind0方法,该方法主要将端口和channel进行绑定。

2.3. netty服务初始化

2.3.1. initAndRegister()方法

final ChannelFuture initAndRegister() { 
    Channel channel; try { 
    //1、创建网络通道Channel channel = createChannel(); } catch (Throwable t) { 
    return VoidChannel.INSTANCE.newFailedFuture(t); } try { 
    //2、为channel配置 线程、option、attr、事件处理器ChannelHandler init(channel); } catch (Throwable t) { 
    channel.unsafe().closeForcibly(); return channel.newFailedFuture(t); } ChannelPromise regFuture = channel.newPromise(); //channel注册多路复用器selector channel.unsafe().register(regFuture); if (regFuture.cause() != null) { 
    if (channel.isRegistered()) { 
    channel.close(); } else { 
    channel.unsafe().closeForcibly(); } } return regFuture; } 

netty服务端初始化也可以分成三部分

  1. 创建netty的channel对象,因为我们在构建的ServerBootstrap对象的时候调用了channel方法传入NioServerSocketChannel.class
//调用channel(NioServerSocketChannel.class)方法 会创建ServerBootstrapChannelFactory return channelFactory(new ServerBootstrapChannelFactory<ServerChannel>(channelClass)); 

createChannel方法调用ServerBootstrapChannelFactory的newChannel(),通过反射调用NioServerSocketChannel的构造函数

//构建函数 public NioServerSocketChannel(EventLoop eventLoop, EventLoopGroup childGroup) { 
    //父构造函数 newSocket()创建一个NIO的ServerSocketChannel 和eventLoop线程以及工作线程childGroup //包装成NioServerSocketChannel super(null, eventLoop, childGroup, newSocket(), SelectionKey.OP_ACCEPT); config = new DefaultServerSocketChannelConfig(this, javaChannel().socket()); } 
  1. 初始化channel配置:init()方法整合ServerBootstrap相关配置比如工作线程(NioEventLoopGroup)、option(参数配置)、attr(属性)、添加相关处理器(ChannelHandler)并配置childHandler、currentChildOptions、currentChildAttrs包装成ServerBootstrapAcceptor
  2. channel注册selector:通过channel的unsafe对象(io.netty.channel.AbstractChannel.AbstractUnsafe)的register方法。

2.3.2. init()方法

​ 在上述方法的第二部分init()方法初始化channel配置最终实现类为io.netty.bootstrap.ServerBootstrap#init。

void init(Channel channel) throws Exception { 
    //获取AbstractBootstrap的options(TCP配置信息) 设置到ChannelConfig中 final Map<ChannelOption<?>, Object> options = options(); synchronized (options) { 
    channel.config().setOptions(options); } //获取AbstractBootstrap的attr属性 设置到Attribute中 final Map<AttributeKey<?>, Object> attrs = attrs(); synchronized (attrs) { 
    for (Map.Entry<AttributeKey<?>, Object> e: attrs.entrySet()) { 
    @SuppressWarnings("unchecked") AttributeKey<Object> key = (AttributeKey<Object>) e.getKey(); channel.attr(key).set(e.getValue()); } } //获取AbstractBootstrap的ChannelHandler 添加到ChannelHandlerPipline中 ChannelPipeline p = channel.pipeline(); if (handler() != null) { 
    p.addLast(handler()); } //子类型的Handler、options、attrs (用户一般使用会创建ChannelInitializer 并通过initChannel // 添加自定义的ChannelHandler) final ChannelHandler currentChildHandler = childHandler; final Map.Entry<ChannelOption<?>, Object>[] currentChildOptions; final Map.Entry<AttributeKey<?>, Object>[] currentChildAttrs; synchronized (childOptions) { 
    currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size())); } synchronized (childAttrs) { 
    currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size())); } //将在ServerBootstrap通过childHandler()、childOptions()、childAttrs()等方法 // childHandler、currentChildOptions、currentChildAttrs 包装成ServerBootstrapAcceptor使用 p.addLast(new ChannelInitializer<Channel>() { 
    @Override public void initChannel(Channel ch) throws Exception { 
    //ServerBootstrapAcceptor ch.pipeline().addLast(new ServerBootstrapAcceptor(currentChildHandler, currentChildOptions, currentChildAttrs)); } }); } 

init()方法的分析可以分成如下部分,

  1. options:通过ServerBootstrap设置的options添加到channel的ChannelConfig实例中(DefaultServerSocketChannelConfig实现类在createChannel方法中实现的)
  2. attrs:通过ServerBootstrap设置的attrs添加到channel的Attribute中。
  3. handler:过ServerBootstrap设置的handler添加到ChannelHandlerPipline中。
  4. 为通道添加一个ChannelInitializer实现类,该类是一个继承ChannelHandlerAdapter的抽象类,其只重写了channelRegistered事件,当channel通过后面的方法 channel.unsafe().register(regFuture) 注册后会触发其中的channelRegistered方法。
  5. 当前的channel进行注册成功后会将childHandler、currentChildOptions、currentChildAttrs 包装成ServerBootstrapAcceptor使用,ServerBootstrapAcceptor是一个特殊的ChannelHandler,该类也是继承了ChannelHandlerAdapter,其重写了channelRead事件,每当有一个客户端连接进来后,会先进入到ServerBootstrapAcceptor的channelRead()。

    补充: channelRegistered事件:channel和selector注册后会触发该方法

    ​ channelRead事件:每当有一个客户端连接进来后,会先进入到ServerBootstrapAcceptor的channelRead()

    上述的4和5步骤我们需要了解ChannelInitializer实现,同时对ServerBootstrapAcceptor类作用进行介绍。

2.3.3. ServerBootstrapAcceptor作用

 p.addLast(new ChannelInitializer<Channel>() { 
    @Override public void initChannel(Channel ch) throws Exception { 
    //Channel添加ServerBootstrapAcceptor ch.pipeline().addLast(new ServerBootstrapAcceptor(currentChildHandler, currentChildOptions, currentChildAttrs)); } }); 

​ 在上述init()方法中最终在ChannelPipline通过addLast方法 添加了一个匿名实现ChannelInitializer

public final void channelRegistered(ChannelHandlerContext ctx) throws Exception { 
    ChannelPipeline pipeline = ctx.pipeline(); boolean success = false; try { 
    //提供的抽象方法 用于自定义实现 initChannel((C) ctx.channel()); //将其从ChannelPipline中移除 pipeline.remove(this); //将channelRegistered事件透传到别的ChannelHandler ctx.fireChannelRegistered(); success = true; } catch (Throwable t) { 
    //异常处理 } finally { 
    } } 

ChannelInitializer类的作用是处理channelRegistered事件,主要是执行initChannel,然后移除本匿名ChannelInitializer实现类。结合上述netty源码,这里就是往pipline中添加ServerBootstrapAcceptor对象同时移除这里的ChannelInitializer实现类。

在之前的博文中netty的 线程模型 Reactor多线程模型中 netty会通过注册一个Acceptor事件处理器到mainReactor中,Acceptor事件处理器所关注的事件是ACCEPT事件,这样mainReactor会监听客户端向服务器端发起的连接请求事件(ACCEPT事件),Acceptor会将客户端的I/O事件分发到sub Reactor线程池。 ServerBootstrapAcceptor对象的作用是那个所谓的Acceptor事件处理器,其重写了channelRead方法。

//该方法触发条件是 每一个客户端与服务端建立连接后都会调用该方法 public void channelRead(ChannelHandlerContext ctx, Object msg) { 
    //msg是客户端连接服务端创建的Channel(NioSocketChannel) 强转为Channel Channel child = (Channel) msg; //为ChannelPipeline添加childHandler(通过ServerBootstrap的childHandler()方法设置的) child.pipeline().addLast(childHandler); //childOptions配置到客户端Channel的ChannelConfig中 for (Entry<ChannelOption<?>, Object> e: childOptions) { 
    try { 
    if (!child.config().setOption((ChannelOption<Object>) e.getKey(), e.getValue())) { 
    logger.warn("Unknown channel option: " + e); } } catch (Throwable t) { 
    logger.warn("Failed to set a channel option: " + child, t); } } //childOptions配置到客户端Channel的attr中 for (Entry<AttributeKey<?>, Object> e: childAttrs) { 
    child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue()); } //客户端Channel进行注册  child.unsafe().register(child.newPromise()); } 

这个方法和我们上面的init方法的Channel的注册逻辑相似。这里需要补充说明一下:Channel注册分为ServerSocketChannel和SockerChannel注册,服务端启动的时候会先进行ServerSocketChannel的注册,启动完成后,每当有一个客户端连接过来,会进行一次SocketChannel注册。所以之前的init方法注册的是netty服务端的channel而这里的注册是netty客户端的channel。

2.3.4. register()方法

下面我们继续来分析initAndRegister方法的第三部分注册,其调用不是由channel直接调用而是使用channel的工具类Unsafe类的register()方法去执行真正的注册逻辑。

public final void register(final ChannelPromise promise) { 
    //netty使用这种机制保证线程安全 //如果调用和eventloop是同一个线程 则直接调用 if (eventLoop.inEventLoop()) { 
    register0(promise); } else { 
    try { 
    //否则将调用包装成任务交由eventloop  eventLoop.execute(new Runnable() { 
    @Override public void run() { 
    register0(promise); } }); } catch (Throwable t) { 
    //异常处理 } } } 
netty线程安全的特性
register0方法
private void register0(ChannelPromise promise) { 
    try { 
    //注册之前确保channel通道是打开状态 if (!ensureOpen(promise)) { 
    return; } //调用底层NIO的将Channel注册到selector(多路复用器) doRegister(); registered = true; promise.setSuccess(); //将channelRegistered事件透传(事件往后传递让别的对该事件感兴趣的 // ChannelHandler处理器进行处理) (这里我们所有实现了channelRegistered都有可能接受到该事件 调用自己initChannel方法) pipeline.fireChannelRegistered(); //javaChannel().socket().isBound() //isBound() 方法判断 ServerSocket 是否已经与一个端口绑定 此时应该没有绑定 if (isActive()) { 
    //产生一个channelActive事件 放入pipeline中 pipeline.fireChannelActive(); } } catch (Throwable t) { 
    //异常处理 } } 

在这里插入图片描述

该方法主要分成三个部分

  1. 调用java底层的NIO的api将channel注册到eventLoop的selector(一个selector可以轮询处理多个线程,所以一个eventLoop可以和多个channel进行绑定),eventLoop的selector是创建EventLoopGroup的子EventLoop初始化的。 ops为0表明此处只简单的注册不对任何事件感兴趣。
  2. fireChannelRegistered :将channelRegistered事件透传(事件往后传递让别的对该事件感兴趣的ChannelHandler处理器进行处理)。
  3. ServerSocket 如果和一个端口绑定了就产生一个channelActive事件 放入pipeline中。

2.4. nety服务端口绑定

2.4.1 doBind0()方法

private static void doBind0( final ChannelFuture regFuture, final Channel channel, final SocketAddress localAddress, final ChannelPromise promise) { 
    //在触发channelRegistered()之前调用此方法。给用户处理程序一个设置的机会  channel.eventLoop().execute(new Runnable() { 
    @Override public void run() { 
    //上述的initAndRegister()方法处理完成且成功 if (regFuture.isSuccess()) { 
    //服务端Channel进行端口绑定,并添加异步结果监听(绑定结果失败的话关闭channel) channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE); } else { 
    //失败设置失败信息 promise.setFailure(regFuture.cause()); } } }); } 

不解释,直接看channel的bind方法

2.4.2 bind()方法

//调用pipeline的bind方法  public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) { 
    return pipeline.bind(localAddress, promise); } //调用pipeline管理的chanelHandler列表 public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) { 
    return tail.bind(localAddress, promise); } //从tail往前查找对bind事件刚兴趣的ChannelHandler public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) { 
    DefaultChannelHandlerContext next = findContextOutbound(MASK_BIND); next.invoker.invokeBind(next, localAddress, promise); return promise; } //通过调用其运行环境invoker中的invokeBindNow方法 ctx.handler().bind(ctx, localAddress, promise); //最终查找到header public void bind( ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception { 
    unsafe.bind(localAddress, promise); } 

调用链如下:

channel.bind() -->pipeline.bind() -->next.invoker.invokeBind() -->header.bind() 
public final void bind(final SocketAddress localAddress, final ChannelPromise promise) { 
    //确保 if (!ensureOpen(promise)) { 
    return; } //端口绑定之前 wasActive 为false  //调用方法isBound() 方法判断 ServerSocket 是否已经与一个端口绑定 boolean wasActive = isActive(); try { 
    //最终调用java原生NIO api进行端口绑定 doBind(localAddress); } catch (Throwable t) { 
    } //端口绑定后isActive() 为true 则此时产生一个channelActive事件 if (!wasActive && isActive()) { 
    invokeLater(new Runnable() { 
    @Override public void run() { 
    //产生channelActive事件放入管道 同时selector监听ON_ACCEPT事件 pipeline.fireChannelActive(); } }); } //成功异步操作标识 promise.setSuccess(); } 

在这里插入图片描述

两部分

  • 调用java原生NIO api进行端口绑定。
  • 产生channelActive事件放入管道。同时该方法还会给多路复用器添加一个ON_ACCEPT事件,服务端准备就绪可以接收客户端的连接请求。

2.4.3. fireChannelActive()

​ 上述服务端创建过程以经接近尾声,channel完成了初始化、同时注册了多路复用器(监听的事件是0)不对任何事件感兴趣,这里是有点疑问,后续客户端连接和读写事件怎么处理?别着急这里为多路复用器添加感兴趣的事件在fireChannelActive方法中 下面我们来看看该方法逻辑

public ChannelPipeline fireChannelActive() { 
    //触发pipeline中ChannelHandler的channelActive事件 head.fireChannelActive(); //调用chanel的read(autoRead属性默认为true) if (channel.config().isAutoRead()) { 
    //调用链如下: // channel.read() // -----> pipeline.read // ------>tail.read() 从tail往上查找符合MASK_READ的ChannelHandler 这里找到的是headHandler // ------>unsafe.beginRead(); 调用channel的unsafe //。 ------> unsafe.doBeginRead();找到最终调用,我们分析一下该方法 channel.read(); } return this; } 
protected void doBeginRead() throws Exception { 
    if (inputShutdown) { 
    return; } //获取当前channel感兴趣的SelectionKey(java的NIO四种事件集合) final SelectionKey selectionKey = this.selectionKey; if (!selectionKey.isValid()) { 
    return; } final int interestOps = selectionKey.interestOps(); //readInterestOp是该channel在初始化阶段传入的ON_ACCEPT事件 //所以此处将channel的interestOps为0 添加监听ON_ACCEPT事件 if ((interestOps & readInterestOp) == 0) { 
    selectionKey.interestOps(interestOps | readInterestOp); } 

3. netty客户端连接

3.1. netty客户端入口

netty客户端创建相关代码如下,服务端创建是使用bind()核心方法,下面来分析bind方法

//一、netty 客户端启动  //.......省略Bootstrap构建过程 //发起异步连接请求 ChannelFuture sync = bootstrap.connect(host,port).sync(); //客户端监听端口关闭 sync.channel().closeFuture().sync(); 

connect()方法最终调用io.netty.bootstrap.AbstractBootstrap#doConnect()方法,下面我们来分析netty客户端连接方法。其实netty客户端建立连接的过程和netty服务端启动过程类似,这里我们也只对区别说明。

3.2. doConnect()方法

private ChannelFuture doConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) { 
    //netty客户端初始化类似netty服务端类似  final ChannelFuture regFuture = initAndRegister(); final Channel channel = regFuture.channel(); if (regFuture.cause() != null) { 
    return regFuture; } final ChannelPromise promise = channel.newPromise(); //此处不再是doBind0方法而是doConnect0方法 if (regFuture.isDone()) { 
    doConnect0(regFuture, channel, remoteAddress, localAddress, promise); } else { 
    regFuture.addListener(new ChannelFutureListener() { 
    @Override public void operationComplete(ChannelFuture future) throws Exception { 
    doConnect0(regFuture, channel, remoteAddress, localAddress, promise); } }); } return promise; } 

netty服务端启动和绑定过程并不复杂主要分成了两部分(两个核心方法)

  • 初始化: initAndRegister方法,该方法主要功能是创建Channel对象(网络请求的数据流通道),
    1. createChannel方法netty客户端是通过BootstrapChannelFactory对象创建的是NioSocketChannel实例,netty服务端是通过ServerBootstrapChannelFactory对象创建NioServerSocketChannel
    2. init(Bootstrap实现)方法只是添加handler、options、attr,netty服务端init(ServerBootstrap) 还会将child相关配置包装成ServerBootstrapAcceptor
    3. register0实现方式两者一致
  • 连接建立:doConnect0方法,该方法绑定客户端channel并和netty服务端建立连接。着重描述。

3.3 doConnect0()

private static void doConnect0( final ChannelFuture regFuture, final Channel channel, final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) { 
    channel.eventLoop().execute(new Runnable() { 
    @Override public void run() { 
    if (regFuture.isSuccess()) { 
    //客户端连接建立 if (localAddress == null) { 
    channel.connect(remoteAddress, promise); } else { 
    channel.connect(remoteAddress, localAddress, promise); } //连接建立失败监听 关键channel promise.addListener(ChannelFutureListener.CLOSE_ON_FAILURE); } else { 
    promise.setFailure(regFuture.cause()); } } }); } 

调用链如下:

pipeline.connect(remoteAddress, localAddress, promise) --------->tail.connect(remoteAddress, localAddress, promise) ---------->findContextOutbound //从tail往前查找对connect感兴趣的HeadHandler ---------->unsafe.connect(remoteAddress, localAddress, promise) //最终调用unsafe.connect 

3.4. unsafe.connect

​ connect实际调用的是AbstractNioUnsafe的connect方法

public void connect( final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) { 
    if (!ensureOpen(promise)) { 
    return; } try { 
    if (connectPromise != null) { 
    throw new IllegalStateException("connection attempt already made"); } boolean wasActive = isActive(); //调用Java原生API 与服务端建立连接。 //连接立即建立的话直接调用fulfillConnectPromise(作用是连接建立完成触发channelActive和channel的read方法, //表示通道中已经有了可读的数据,可以执行读操作了) if (doConnect(remoteAddress, localAddress)) { 
    fulfillConnectPromise(promise, wasActive); } else { 
    //否则异步处理连接请求 connectPromise = promise; requestedRemoteAddress = remoteAddress; //如果设置了超时事件则创建一个延时任务(延时事件即为超时时间,里面用来处理超时连接的问题) 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 = null; close(voidPromise()); } } }); } } catch (Throwable t) { 
    //省略异常处理  } } 

方法逻辑如下:

 1. doConnect 调用原生java API 进行和服务端建立连接 2. 连接立即建立:接着调用fulfillConnectPromise 触发客户端的读请求 发送数据到channel 3. 连接异步建立:设置超时任务处理连接(连接成功后该Channel注册在NioEventLoop上selector会轮循所有的事件进行处理)可以进行后续的读请求处理。 

3.5. NioSocketChannel.doConnect()方法

@Override protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception { 
    if (localAddress != null) { 
    //调用原生java NIO 客户端socket绑定端口 javaChannel().socket().bind(localAddress); } boolean success = false; try { 
    //调用原生java NIO 客户端socket发起服务端建立连接  //此时客户端会在服务端channel ON_ACCEPT事件 此时服务端添加的ServerBootstrapAcceptor对象的 //channelRead方法为服务端处理请求添加业务自定义的ChannelHandler。 boolean connected = javaChannel().connect(remoteAddress); if (!connected) { 
    //连接建立是异步的 //所以此处该channel还需要监听 服务端建立完连接的ON_CONNECT事件 selectionKey().interestOps(SelectionKey.OP_CONNECT); } success = true; return connected; } finally { 
    if (!success) { 
    doClose(); } } } 

4、netty一次请求响应过程总结

在这里插入图片描述

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/224243.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月17日 下午12:23
下一篇 2026年3月17日 下午12:23


相关推荐

  • Vue.js高效前端开发 • 【初识Vue.js】

    1.1Vue概述1.1.1Web前端框架介绍近几年,互联网前端行业发展得依旧迅猛,涌现出了很多优秀的JavaScript框架,同时这些JavaScript框架也正在逐渐改变统的前端开发方式。在这些新出现的JavaScript框架中,最具代表性的框架有Angular.js、React.js和Vue.js。1.1.2MVC和MVVMMVC是著名的设计模式,基本思想是将软件结构分解为Model(模型)、View(视图)和Controller(控制器)三部分组成。Model:主要负责数据处理和

    2022年4月9日
    41
  • break 与continue语句的区别_return用法

    break 与continue语句的区别_return用法来源:http://blog.csdn.net/u014612521/article/details/42720987break可以离开当前switch、for、while、dowhile的程序块,并前进至程序块后下一条语句,在switch中主要用来中断下一个case的比较。在for、while与dowhile中,主要用于中断目前的循环执行。continue的作用与

    2025年11月3日
    7
  • springboot到底是什么_Springboot启动流程

    springboot到底是什么_Springboot启动流程SpringBoot是干哈的介绍:springboot是由Pivotal团队提供的全新框架。spring的出现是为了解决企业级开发应用的复杂性,spring的通过注册bean的方式来管理类,但是随着业务的增加,使用xml配置bean的方式也显得相当繁琐,所以springboot就是为了解决spring配置繁琐的问题而诞生的,并且近几年来非常流行开启我的第一个HelloSpringBoot!开启方式根据https://start.spring.io网址创建一个springboot项目

    2022年8月21日
    7
  • C语言二分法查找法

    C语言二分法查找法C 语言 二分查找法 所谓的二分查找法 其实是一种有序的查找方法 也称折半查找 BinarySearch 如果是无序的则要先进行排序操作 基本思想是 目标值通过与中间元素比较 可分为三种情况 第一种情况 目标值与中间元素相等 查找结束 第二种情况 目标值比中间元素大 则把后半部分的中间元素与目标值比较 第二种情况 目标值比中间元素小 则把前半部分的中间元素与目标值

    2026年3月18日
    2
  • Macbook上打开多个终端的方法[通俗易懂]

    Macbook上打开多个终端的方法

    2022年2月9日
    44
  • 记录:Unknown custom element: [v-table] – did you register the component correctly…报错【亲测有效】

    记录:Unknown custom element: [v-table] – did you register the component correctly…报错【亲测有效】记录 Unknowncusto v table didyouregist 报错

    2026年3月20日
    3

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号