netty 源码分析之(二.1)ServerBootstrap、Boss/workers线程池

1、 ServerBootstrap bootstrap = new ServerBootstrap(

                new NioServerSocketChannelFactory(

                        Executors.newCachedThreadPool(),

                        Executors.newCachedThreadPool()));

或者,NioServerSocketChannelFactory(

            Executor bossExecutor, Executor workerExecutor,

            int workerCount) {

中,第二个参数(线程池)用于运行NioWorker。第三个参数(workerCount)表示NioWorker最大个数,即最大的并发量。默认是(cpu个数*2)。

 

boss线程和worker线程

 

可以这么说,ServerBootstrap监听的一个端口对应一个boss线程,它们一一对应。比如你需要netty监听80和443端口,那么就会有两个boss线程分别负责处理来自两个端口的socket请求。在boss线程接受了socket连接求后,会产生一个channel(一个打开的socket对应一个打开的channel),并把这个channel交给ServerBootstrap初始化时指定的ServerSocketChannelFactory来处理,boss线程则继续处理socket的请求。

 

ServerSocketChannelFactory则会从worker线程池中找出一个worker线程来继续处理这个请求。
如果是OioServerSocketChannelFactory的话,那个这个channel上所有的socket消息消息,从开始到channel(socket)关闭,都只由这个特定的worker来处理,也就是说一个打开的socket对应一个指定的worker线程,这个worker线程在socket没有关闭的情况下,也只能为这个socket处理消息,无法服务器他socket。

 

如果是NioServerSocketChannelFactory的话则不然,每个worker可以服务不同的socket或者说channel,worker线程和channel不再有一一对应的关系。
显然,NioServerSocketChannelFactory只需要少量活动的worker线程及能很好的处理众多的channel,而OioServerSocketChannelFactory则需要与打开channel等量的worker线程来服务。

 

线程是一种资源,所以当netty服务器需要处理长连接的时候,最好选择NioServerSocketChannelFactory,这样可以避免创建大量的worker线程。在用作http服务器的时候,也最好选择NioServerSocketChannelFactory,因为现代浏览器都会使用http keepalive功能(可以让浏览器的不同http请求共享一个信道),这也是一种长连接

 

Netty网络线程模型主要有两类线程组成:boss线程、worker线程。启动一个server实例只会产生一个boss线程,boss线程主要负责监听端口,当有新的连接请求时就会产生一个task交给worker线程池处理,worker线程池中线程的个数默认是cpu个数的2倍。

 

 

Netty网络I/O操作采用了目前流行的I/O多路选择器Selector方式(具体的实现是select/poll,还是epoll/kqueue,是由java虚拟机根据具体操作系统的实现来决定的)。

       Worker线程池中每个线程都维护几个处理队列(如注册队列,写任务队列等),每个队列中实际上都注册着一些interestOPS(感兴趣的操作集),worker线程不断的循环地做select操作,boss线程不断地将新的连接请求注册到一个work线程的注册队列中,每次都是轮询出worker线程池中的一个线程:

 

[java]  view plain copy
  1. NioWorker nextWorker() {  
  2.         return workers[Math.abs(workerIndex.getAndIncrement() % workers.length)];  
  3. }  


Worker线程中如果selectors.keys操作结果为空,则会主动退出,当又有新的连接请求被boss线程加到相应的worker线程的队列时,该worker线程又会重新启动。

 

 

       这里在处理线程间同步和互斥时用了2个锁:

 

  1.  一个started标志,主要用于判断当前线程是否已经启动,当boss线程选择了一个worker线程后,想把新请求加入到该worker线程的新请求队列时,若该started=false,则表示该worker线程没有启动,所以要先启动worker线程,若started=true,则表示该worker线程已启动,则直接把请求加入到该worker线程的请求队列就行了。当worker线程的队列中没有了操作,即为空时,worker线程会主动退出,并设置started=false。故,对于started,boss线程和worker线程间要进行同步,所以使用了       synchronized(startStopLock)
  2.  Selectors选择器操作本身是同步的,但对selectionkey的操作则需要进行同步控制,所以又用到了一个锁:selectorGuard = newReentrantReadWriteLock()

 

 

第一个是互斥锁;第二个是读写锁

 

每个worker线程各自使用一个selector,即每个worker线程的请求队列中所有的新请求(即一个SocketChannelclient)都会在该worker线程的Selector中注册感兴趣的选择键集。

 

 

### Netty ServerBootstrap 中 NullPointerException 的原因分析 在 Java 编程中,`NullPointerException` 是一种常见的运行时异常。对于 `ServerBootstrap` 而言,这种异常通常发生在某些必要的配置未被正确设置的情况下。 #### 可能的原因及其解决方案 1. **未指定 Channel 类型** 如果在调用 `bind()` 方法之前没有通过 `channel(Class<? extends C> channelClass)` 设置具体的通道类,则会抛出 `NullPointerException`。这是因为 `AbstractBootstrap.channel(Class<? extends C> channelClass)` 方法会在参数为空时显式抛出此异常[^1]。 解决方案是在创建 `ServerBootstrap` 实例后立即调用 `.channel(NioServerSocketChannel.class)` 或其他合适的通道实现来指定服务器使用的具体通道类型。 2. **未正确设置 EventLoopGroup** 当调用 `group(EventLoopGroup parentGroup, EventLoopGroup childGroup)` 方法时,如果传入的 `parentGroup` 或 `childGroup` 参数为 `null`,则会触发 `NullPointerException`。这是因为在该方法内部有针对这些参数的非空校验逻辑[^2]。 正确的做法是确保传递给 `group(...)` 方法的两个 `EventLoopGroup` 对象均已被实例化且有效。例如: ```java EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); bootstrap.group(bossGroup, workerGroup); ``` 3. **缺少 ChildHandler 配置** 即使完成了上述两项基本配置,在绑定端口前如果没有定义如何处理客户端连接请求(即未调用 `.childHandler(ChannelInitializer<SocketChannel>)`),也可能间接导致后续操作失败而引发类似的错误行为[^3]。 应当提供一个自定义的 `ChannelInitializer` 来初始化新建立的每个子信道,并向其管道添加所需的处理器链。例如: ```java bootstrap.childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new ObjectEncoder()); pipeline.addLast(new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null))); pipeline.addLast(new RpcServerHandler(registerMap)); } }); ``` 4. **启动线程中的潜在问题** 在一些简单的例子中可能会看到像下面这样的代码片段用于启动服务端监听器[^4]: ```java public static void main(String[] args) { System.out.println("启动服务端开始"); new Thread(new ServerSocket()).start(); // 假设这里存在某种形式的服务端实现 System.out.println("启动服务端完成"); } ``` 这种方式虽然可以工作,但如果其中涉及到了复杂的异步流程或者依赖于外部资源加载等情况下的同步机制缺失的话也有可能造成意想不到的结果包括但不限于NPE等问题的发生。 综上所述,要彻底解决由 `ServerBootstrap` 引发的 `NullPointerException` 问题,需仔细检查以上提到的各项基础配置是否均已妥善安排到位。 ```java // 完整示例代码展示正确的配置过程 public class NettyServer { private final int port; public NettyServer(int port) { this.port = port; } public void run() throws InterruptedException { EventLoopGroup bossGroup = new NioEventLoopGroup(1); // 主线程组 EventLoopGroup workerGroup = new NioEventLoopGroup(); // 工作线程组 try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) // 指定使用 NIO 方式的 ServerSocketChannel .childHandler(new ChannelInitializer<SocketChannel>() { // 添加子信道初始化器 @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline p = ch.pipeline(); p.addLast(new StringEncoder()); // 示例编码器 p.addLast(new StringDecoder()); // 示例解码器 p.addLast(new SimpleChannelInboundHandler<String>() { // 处理业务逻辑 @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { System.out.println("收到消息:" + msg); ctx.writeAndFlush("已接收您的消息!"); } }); } }); // 绑定端口并等待关闭 ChannelFuture f = b.bind(port).sync(); f.channel().closeFuture().sync(); } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } } public static void main(String[] args) throws InterruptedException { new NettyServer(8080).run(); } } ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值