`

netty--ServerBootStrap

 
阅读更多

BootStrap在netty的应用程序中负责引导服务器和客户端。netty包含了两种不同类型的引导: 
1. 使用服务器的ServerBootStrap,用于接受客户端的连接以及为已接受的连接创建子通道。 
2. 用于客户端的BootStrap,不接受新的连接,并且是在父通道类完成一些操作。

ServerBootStrap的运行原理

      服务端的ServerBootstrap类继承图:

 

客户端的Bootstrap类继承图: 

 

      根据ServerBootstrap源码分析

      首先给出一个很简单的基于netty的聊天室的服务端的实例:

 

package netty.cookbook.simplechat;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import utils.LogUtil;

/**
 * 启动服务端
 */
public class SimpleChatServer {
    private int port;

    public SimpleChatServer(int port){
        this.port = port;
    }

    public void run() throws Exception{
        //NioEventLoopGroup是用来处理IO操作的多线程事件循环器
        //boss用来接收进来的连接
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        //用来处理已经被接收的连接;
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try{
            //是一个启动NIO服务的辅助启动类
            ServerBootstrap sBootstrap = new ServerBootstrap();
            //These EventLoopGroup's are used to handle all the events and IO for ServerChannel and Channel's.
            //为bootstrap设置acceptor的EventLoopGroup和client的EventLoopGroup
            //这些EventLoopGroups用于处理所有的IO事件
            //?这里为什么设置两个group呢?
            sBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new SimpleChatServerInitializer())
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true);

            LogUtil.log_debug("SimpleChatServer 启动了");

            //绑定端口,开始接收进来的连接
            ChannelFuture future = sBootstrap.bind(port).sync();
            //等待服务器socket关闭
            //在本例子中不会发生,这时可以关闭服务器了
            future.channel().closeFuture().sync();
        } finally {
            //
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();

            LogUtil.log_debug("SimpleChatServer 关闭了");
        }

    }

    public static void main(String[] args) throws Exception {
        new SimpleChatServer(8080).run();
    }
}

       根据上面的实例分析ServerBootStrap的运行流程:

1.实例化ServerBootstrap 

ServerBootstrap sBootstrap = new ServerBootstrap();

2.配置ServerBootstrap的group()

//boss用来接收进来的连接
EventLoopGroup bossGroup = new NioEventLoopGroup();
//用来处理已经被接收的连接;
EventLoopGroup workerGroup = new NioEventLoopGroup();
sBootstrap.group(bossGroup, workerGroup)
          .channel(NioServerSocketChannel.class)
          .childHandler(new SimpleChatServerInitializer())
          .option(ChannelOption.SO_BACKLOG, 128)
          .childOption(ChannelOption.SO_KEEPALIVE, true);

 从上面可知,上面创建了两个EventLoopGroup,分别是boss和worker,然后配置到ServerBootstrap的group中。我们先来看看ServerBootstrap.group(),这个函数有两个重载的实现:

public ServerBootstrap group(EventLoopGroup group) {
    return group(group, group);
}

public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
    super.group(parentGroup);
    if (childGroup == null) {
        throw new NullPointerException("childGroup");
    }
    if (this.childGroup != null) {
        throw new IllegalStateException("childGroup set already");
    }
    this.childGroup = childGroup;
    return this;
}

        这里如果调用只传入了一个EventLoopGroup,最后也会调用group(EventLoopGroup parentGroup, EventLoopGroup childGroup)。 传入的两个EventLoopGroup分别叫做parentGroup和childGroup。

        其实我觉得更加好理解的方式应该叫boss和worker。boss这个EventLoopGroup作为一个acceptor负责接收来自客户端的请求,然后分发给worker这个EventLoopGroup来处理所有的事件event和channel的IO。 

        查看上面的源码,我们可知,首先调用的是

super.group(parentGroup);

        这个方法调用了ServerBootstrap的父类AbstractBootstrap的group(EventLoopGroup group) 方法,下面看看AbstractBootstrap中方法的定义:

public B group(EventLoopGroup group) {
    //传参不能为空
    if (group == null) {
        throw new NullPointerException("group");
    }
    //AbstractBootstrap的group属性如果非空,就抛出异常,说明this.group是单例的
    if (this.group != null) {
        throw new IllegalStateException("group set already");
    }
    this.group = group;
    return (B) this;
}

 从上面的源码AbstractBootstrap里面的这个this.group属性是单例的。我们先看看该属性的定义:

volatile EventLoopGroup group;

       这里定义的是volatile的变量,提供可见性,而且变量是包级别的权限。最后将传入的EventLoopGroup赋值给AbstractBootstrap的group属性。

       后面的ServerBootstrap的childGroup也是同样的实现,不过childGroup是单例的赋值给了ServerBootstrap的childGroup属性。ServerBootstrap中的定义如下: 
private volatile EventLoopGroup childGroup;

关于EventLoopGroup下的parentGroup和childGroup(boss线程池和worker线程池):

        boos线程:监听服务端口,查看当前端口主要做了哪些事情

        worker线程:当boos接收(accept)到一个客户(client)的请求,boos会安排一个worker,这个worker全程为这位客户服务(read/write)。如果当前端口业务繁忙,一个worker可能会为多个客户进行服务。这就是Netty里面Boss和worker之间的关系。

3.配置ServerBootstrap的channel()

        这里以聊天室的实例为例

ServerBootstrap.channel(NioServerSocketChannel.class)

        实际上是调用的是AbstractBootstrap里面的channel()函数,下面先粘贴出该方法的源码:

public B channel(Class<? extends C> channelClass) {
    if (channelClass == null) {
        throw new NullPointerException("channelClass");
    }
    return channelFactory(new ReflectiveChannelFactory<C>(channelClass));
}

        这里传入的是一个Class对象,根据传入的不同的Class对象,实例化不同的Channel,主要是有两种代表NIO和OIO的对象:NioServerSocketChannel和OioServerSocketChannel(两个对象会在另一篇博客中进行分析)。在函数体的最后调用: 

channelFactory(new ReflectiveChannelFactory<C>(channelClass));

      参数里面实例化了一个 ReflectiveChannelFactory对象,这个对象实现了ChannelFactory这个接口的。 

      下面进入这个函数,最后调用的是:

public B channelFactory(ChannelFactory<? extends C> channelFactory) {
    //判断传参非空
    if (channelFactory == null) {
        throw new NullPointerException("channelFactory");
    }
    //AbstractBootstrap的channelFactory属性非空
    if (this.channelFactory != null) {
        throw new IllegalStateException("channelFactory set already");
    }
    //传递给channelFactory属性
    this.channelFactory = channelFactory;
    return (B) this;
}

        最后是把new的ReflectiveChannelFactory传递给了AbstractBootstrap的channelFactory属性,该属性定义如下:

private volatile ChannelFactory<? extends C> channelFactory;

4.配置ServerBootstrap的childHandler(ChannelHandler childHandler)

该函数的主要作用是设置channelHandler来处理客户端的请求的channel的IO。 
这里我们一般都用ChannelInitializer这个类的实例或则继承自这个类的实例,在我的聊天室程序中实例如下:

ServerBootstrap.childHandler(new SimpleChatServerInitializer()) 

 这里我是通过新建类SimpleChatServerInitializer继承自ChannelInitializer。具体的代码如下:

/**
 * 用来增加多个的处理类到ChannelPipeline上:包括编码,解码,SimpleChatServerHandler
 */
public class SimpleChatServerInitializer extends ChannelInitializer<SocketChannel>{

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();

        pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
        pipeline.addLast("decoder", new StringDecoder());
        pipeline.addLast("encoder", new StringEncoder());
        pipeline.addLast("handler", new SimpleChatServerHandler());

        System.out.println("SimpleChatClient:" + ch.remoteAddress()+"连接上");
    }
}

关于ChannelInitializer这个类可知道它最终是继承自ChannelHandler,这个类其实是往pipeline中添加了很多hannelHandler。在ServerBootstrap.childHandler(ChannelHandler childHandler)中的实现:

public ServerBootstrap childHandler(ChannelHandler childHandler) {
    if (childHandler == null) {
        throw new NullPointerException("childHandler");
    }
    this.childHandler = childHandler;
    return this;
}

 由最后一句可知,其实就是讲传入的childHandler赋值给ServerBootstrap的childHandler属性。

5.配置ServerBootstrap的option(ChannelOption option, T value)

这里调用的是父类的AbstractBootstrap的option()方法,源码如下: 

public <T> B option(ChannelOption<T> option, T value) {
    if (option == null) {
        throw new NullPointerException("option");
    }
    if (value == null) {
        synchronized (options) {
            options.remove(option);
        }
    } else {
        synchronized (options) {
            options.put(option, value);
        }
    }
    return (B) this;
}

 其中最重要的一行代码就是: 

options.put(option, value);

这里用到了options这个参数,在AbstractBootstrap的定义如下:

private final Map<ChannelOption<?>, Object> options = 
new LinkedHashMap<ChannelOption<?>,Object>();

 可知是私有变量,而且是一个Map集合。这个变量主要是设置TCP连接中的一些可选项,而且这些属性是作用于每一个连接到服务器被创建的channel。

6.配置ServerBootstrap的childOption(ChannelOption childOption, T value)

 这里调用的是父类的ServerBootstrap的childOption()方法,源码如下:

public <T> ServerBootstrap childOption(ChannelOption<T> childOption, T value) {
    if (childOption == null) {
        throw new NullPointerException("childOption");
    }
    if (value == null) {
        synchronized (childOptions) {
            childOptions.remove(childOption);
        }
    } else {
        synchronized (childOptions) {
            childOptions.put(childOption, value);
        }
    }
    return this;
}

 这个函数功能与option()函数几乎一样,唯一的区别是该属性设定只作用于被acceptor(也就是boss EventLoopGroup)接收之后的channel。

7.配置ServerBootstrap的绑定端口和启动:

   上面介绍的都是关于ServerBootstrap的一些配置的绑定,
   下面介绍在启动服务器时候做的工作:通过聊天室的服务端程序我们可以知道,绑定端口并启动服务程序如下:
//绑定端口,开始接收进来的连接
ChannelFuture future = sBootstrap.bind(port).sync();

    bind()函数是AbstractBootstrap里面的方法,首先分析bind(port)函数的功能:直接看源码: 

/**
 *  1.
 *创建一个通道并绑定到当前BootStrap
 */
public ChannelFuture bind(int inetPort) {
    return bind(new InetSocketAddress(inetPort));
}

/**
 *  2.
 *创建一个通道并绑定到当前BootStrap
 */
public ChannelFuture bind(SocketAddress localAddress) {
    validate();//
    if (localAddress == null) {
        throw new NullPointerException("localAddress");
    }
    return doBind(localAddress);

    从上面的源码可知bind(port)最后会调用bind(SocketAddress localAddress)函数,里面的 validate()函数会先校验AbstractBootstrap的成员属性group(也就是boss)和channelFactory非空。

    最后就是调用doBind(localAddress);方法了,这里才是bind()函数的核心:看源码分析这个函数做了什么工作:
private ChannelFuture doBind(final SocketAddress localAddress) {
    //注册
    final ChannelFuture regFuture = initAndRegister();
    final Channel channel = regFuture.channel();
    if (regFuture.cause() != null) {
        return regFuture;
    }

    if (regFuture.isDone()) {
        // At this point we know that the registration was complete and successful.
        ChannelPromise promise = channel.newPromise();
        doBind0(regFuture, channel, localAddress, promise);
        return promise;
    } 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 {
                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();

                    doBind0(regFuture, channel, localAddress, promise);
                }
            }
        });
        return promise;
    }
     从定义可知,这个函数是一个private函数,也就是类内部调用的。首先看
final ChannelFuture regFuture = initAndRegister();
     里面initAndRegister()函数的功能:
final ChannelFuture initAndRegister() {
    //new 了一个新的channel
    Channel channel = null;
    try {
        //调用了AbstractBootstrap的channelFactory属性新建了一个在ServerBootStrap中配置的channel类型,在我的聊天室程序中是NioServerSocketChannel。
        channel = channelFactory.newChannel();
        //初始化通道
        init(channel);
    } catch (Throwable t) {
        if (channel != null) {
            // channel can be null if newChannel crashed (eg SocketException("too many open files"))
            channel.unsafe().closeForcibly();
        }
        // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
        return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
    }

    ChannelFuture regFuture = config().group().register(channel);
    if (regFuture.cause() != null) {
        if (channel.isRegistered()) {
            channel.close();
        } else {
            channel.unsafe().closeForcibly();
        }
    }

    // If we are here and the promise is not failed, it's one of the following cases:
    // 1) If we attempted registration from the event loop, the registration has been completed at this point.
    //    i.e. It's safe to attempt bind() or connect() now because the channel has been registered.
    // 2) If we attempted registration from the other thread, the registration request has been successfully
    //    added to the event loop's task queue for later execution.
    //    i.e. It's safe to attempt bind() or connect() now:
    //         because bind() or connect() will be executed *after* the scheduled registration task is executed
    //         because register(), bind(), and connect() are all bound to the same thread.

    return regFuture;
}
      在initAndRegister()函数中,首先调用了AbstractBootstrap的channelFactory.newChannel()方法新建了一个在ServerBootStrap中配置的channel类型,在我的聊天室程序中NioServerSocketChannel。然后调用init()函数初始化这个通道(该函数在ServerBootstrap中实现):完整的源码就不给出的,主要说说       里面做了什么任务:   
      (1).获取ServerBootStrap中options和attrs的配置,然后设置在新创建的channel对象中,代码如下:
final Map<ChannelOption<?>, Object> options = options0();
synchronized (options) {
    channel.config().setOptions(options);
}
final Map<AttributeKey<?>, Object> attrs = attrs0();
synchronized (attrs) {
    for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
        @SuppressWarnings("unchecked")
        AttributeKey<Object> key = (AttributeKey<Object>) e.getKey();
        channel.attr(key).set(e.getValue());
    }
}
    (2).获取新new的channel的pipeline,然后添加handler到channel中。具体看源码注释
//获取channel的pipeline
ChannelPipeline p = channel.pipeline();

/** currentChildGroup也就是worker的EventLoopGroup*/
final EventLoopGroup currentChildGroup = childGroup;
/**currentChildHandler也就是在ServerBootStrap中添加的childHandler*/
final ChannelHandler currentChildHandler = childHandler;
/** 获取worker角色的EventLoopGroup的option设置和attr设置并配置到currentChildOptions和currentChildAttrs中 */
final Entry<ChannelOption<?>, Object>[] currentChildOptions;
final Entry<AttributeKey<?>, Object>[] currentChildAttrs;
synchronized (childOptions) {
    currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size()));
}
synchronized (childAttrs) {
    currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size()));
}

/** 向new的channel中添加handler **/
p.addLast(new ChannelInitializer<Channel>() {
    @Override
    public void initChannel(Channel ch) throws Exception {
        final ChannelPipeline pipeline = ch.pipeline();
        //获取ServerBootStrap中添加的childhandler,然后添加到channel中
        ChannelHandler handler = config.handler();
        if (handler != null) {
            pipeline.addLast(handler);
        }

        // We add this handler via the EventLoop as the user may have used a ChannelInitializer as handler.
        // In this case the initChannel(...) method will only be called after this method returns. Because
        // of this we need to ensure we add our handler in a delayed fashion so all the users handler are
        // placed in front of the ServerBootstrapAcceptor.
        //新建一个线程,将channelHandler的子类之new的ServerBootstrapAcceptor添加到pipeline中。
        ch.eventLoop().execute(new Runnable() {
            @Override
            public void run() {
                pipeline.addLast(new ServerBootstrapAcceptor(
                   currentChildGroup, currentChildHandler, currentChildOptions, 
                   currentChildAttrs));
            }
        });
    }
});

       下面再看看ServerBootstrapAcceptor这个类里面做了什么?ServerBootstrapAcceptor是ServerBootstrap里面的静态类,这个类本身也是一个channelHandler,我们分析它的channelRead(ChannelHandlerContext ctx, Object msg)方法,主要还是做了以下几个工作: 

            1. 向channel中添加handler; 
            2. 向worker这个EventLoopGroup中注册当前channel分析完init()函数,我们继续看initAndRegister()函数中的其余部分: 
                接下来往boss 这个EventLoopGroup中注册当前channel。最后在doBind()函数中,都会调用doBind0()这个函数,这个函数源码如下:
private static void doBind0(
        final ChannelFuture regFuture, final Channel channel,
        final SocketAddress localAddress, final ChannelPromise promise) {

    // This method is invoked before channelRegistered() is triggered.  Give user handlers a chance to set up
    // the pipeline in its channelRegistered() implementation.
    channel.eventLoop().execute(new Runnable() {
        @Override
        public void run() {
            if (regFuture.isSuccess()) {
                /********/
                channel.bind(localAddress, promise).addListener(
                        ChannelFutureListener.CLOSE_ON_FAILURE);
            } else {
                promise.setFailure(regFuture.cause());
            }
        }
    });
}
 从上面的源码可知,最后都会调用channel.bind()函数。
分享到:
评论

相关推荐

    netty-socketio api接口文档.7z

    3. **ServerBootstrap类**:这是Netty中启动服务器的主要入口点。在API接口文档中,你会看到如何配置ServerBootstrap以设置各种参数,如线程池、管道工厂等。 4. **Pipeline和Handler**:Netty使用Pipeline来处理...

    netty-4.1.9.Final

    Netty 是一个高性能、异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。这个“netty-4.1.9.Final”版本是Netty框架的稳定版本,提供了完整的开发包,包含了所有必要的jar文件,以便...

    netty-websocket.zip

    - `ServerBootstrap`配置文件:这是Netty中启动服务器的入口,用来配置服务器的线程模型、处理器管道以及绑定的端口等。 - `WebSocketServerHandler`类:处理WebSocket连接的自定义处理器,会实现心跳检查、消息收发...

    netty-socketio-demo

    在Netty-SocketIO-Demo中,首先需要设置一个ServerBootstrap,指定一个NioEventLoopGroup作为工作线程组,并配置ChannelInitializer来添加自定义的ChannelHandler。然后,通过bind方法绑定监听的端口,启动服务器。 ...

    spring boot 集成原生netty(非netty-io-socket)

    import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel....

    netty-4.1.16.Final

    在实际应用中,开发者可以根据需要选择使用Netty提供的各种组件,如创建ServerBootstrap来启动服务器,定义ChannelPipeline配置处理器,以及编写自定义的ChannelInboundHandler和ChannelOutboundHandler来处理入站和...

    maven-netty-server

    在项目中,开发者通常会定义一个`ServerBootstrap`,它是Netty的启动类,用于初始化服务器的配置,如线程池、事件循环组、处理器链等。接着,会设置服务器监听的端口,并绑定处理器,这些处理器会处理接收到的客户端...

    netty-4.1_javaNetty_netty_服务器_

    5. **Bootstrap** 和 **ServerBootstrap**:Bootstrap 用于创建客户端连接,ServerBootstrap 用于启动服务器。它们允许配置各种参数,如 EventLoopGroup(处理 I/O 事件的线程组)、Channel 实例等。 在 JavaNetty ...

    Springboot-Netty-Tcp-demo.zip

    3. 实现Netty的ServerBootstrap,配置线程池、通道处理器和绑定端口等。 4. 将Netty服务器作为Spring Boot的一个Bean注入到Spring容器中,以便于管理和控制。 5. 使用Spring的Service或Repository层处理业务逻辑,与...

    Netty实战-leolee-netty-practice.zip

    6. **Bootstrap和ServerBootstrap**: Bootstrap用于创建客户端连接,ServerBootstrap用于创建服务端监听。它们分别定义了客户端和服务器的配置,并用于启动连接或监听。 7. **Handlers**: Netty提供了一系列预定义...

    netty案例集锦,netty demo,netty实战-netty-all.zip

    这涉及到创建一个ServerBootstrap,配置ChannelPipeline,添加适当的处理器(如DelimiterBasedFrameDecoder用于按特定分隔符解码数据),以及编写处理接收到的数据的业务逻辑。 此外,你可能还会遇到如何实现HTTP...

    Netty-API-文档中文版

    通过“Netty-API-文档中文版”,你可以详细了解到如何创建ServerBootstrap、Bootstrap、ChannelFuture、ChannelInboundHandlerAdapter等核心组件,以及如何配置和使用各种编解码器。文档还会介绍如何进行连接管理、...

    Android-netty和socket通信的demo

    Netty的核心组件包括Bootstrap(启动器)、ServerBootstrap(服务器启动器)、Channel(通道)、EventLoop(事件循环)和ChannelHandler(处理程序)。通过这些组件,开发者可以方便地创建出可伸缩、低延迟的网络...

    掘金-Netty 入门与实战-netty-demo.zip

    这个压缩包“掘金-Netty 入门与实战-netty-demo.zip”包含了关于 Netty 的入门教程和实战示例,特别是通过 "netty-demo-pipeline-channelHandler" 这个文件名我们可以推测,它可能包含了一个展示 Netty ...

    netty-4.0.41-2016-9-29官网下载

    在使用时,可以参考Netty的官方文档,了解如何配置EventLoopGroup、创建ServerBootstrap、添加处理器等步骤,逐步搭建起一个完整的网络服务器。同时,也要注意版本兼容性和性能优化,以便在实际应用中发挥出Netty的...

    netty-4.0.30.Final.tar.bz2

    然后,可以通过创建一个ServerBootstrap实例来配置服务器,指定绑定的端口和处理客户端连接的ChannelHandler。对于客户端,可以创建一个Bootstrap实例并配置连接到服务器的参数。Netty的API设计使得这些操作非常直观...

    netty-socket实时在线人数统计

    5. **Netty服务器**:在Netty中创建ServerBootstrap,配置BossGroup和WorkerGroup,指定通道处理器。使用WebSocketFrameDecoder和WebSocketFrameEncoder处理WebSocket帧。 6. **连接监听**:当新的客户端连接到达时...

    netty-starter.zip

    2. **配置文件**:可能有 Netty 的配置,如 EventLoopGroup 的设置、ServerBootstrap 的配置等。 3. **测试用例**:用于验证 Netty 应用程序的正确性,通常包括启动服务器、连接到服务器、发送和接收数据的测试步骤...

    netty-dome保证可以运行

    1. **ObjectServerMain**: 这是服务器端程序,它创建一个 ServerBootstrap 实例,配置 EventLoopGroup(通常为 NioEventLoopGroup)和 Channel(通常是 NioServerSocketChannel)。然后,它设置 Pipeline,添加...

    netty-websocket-example 基于netty的websocket实现示例

    在启动时,我们会创建一个 EventLoopGroup(负责事件循环)和 ServerBootstrap(服务器引导类)或者 Bootstrap(客户端引导类),然后绑定监听端口或连接目标地址。 总的来说,“netty-websocket-example”项目是一...

Global site tag (gtag.js) - Google Analytics