读了一小段Netty Discard Server Demo背后的源代码

Netty的官方文档使用Discard Server Demo作为第一个入门的例子。我跟了一下这段代码后面的Netty框架代码,以了解Netty是怎么玩NIO的。 我看的代码版本是Netty 3.2.7

一些关键步骤如下

1.      Bootstrap.bind()时会最终调用到 Boss的构造方法,在这里会创建selector,并注册OP_ACCEPT

2.      然后进入Boss这个runnable的run方法,在这个方法里它会轮询连接。奇怪的是,即使select()没有找到key, 它也会用accept()方法试图获得连接;而且如果找到key了,它也会将所有key清空。也就是说,它对key根本不感兴趣。

3.      建立连接后,Boss. registerAcceptedChannel()的方法会调用woker.register()方法。在这个方法里,会新建一个worker级别的selector。但这时Netty并不会立即注册worker channel,而是生成一个RegisterTask,置入某个队列里

4.      而NioWorker这个Runnable的run方法已经跑起来了,它会把RegisterTask从队列里取出来,并同步地调用RegisterTask.run()方法(很怪)。在这个方法里,会设置configureBlocking(false), 并暂时把OP_READ作为interest将channel注入到这个worker自己的selector中

5.      同时NioWorker的run()方法里会自已通过selector.select(500)不停地轮询,等待read readiness.默认情况下的NioWorker个数是cpu个数的两倍(对我的机器来说,就是4个),加上Boss,共有5个线程在侦听

6.      客户发送一段数据后,NioWorker会走到processSelectedKeys()这个方法。在这里面的迭代逻辑是:

    a)        取一个key,然后立即将其从selectKeys()里移除。

    b)        调用read(key)方法,把数据读出来。整个过程是同步的。

        i.             读出ByteBuffer

        ii.             把buffer包装成Event对象通过fireMessageReceived()方法传给Handler

        iii.             Handler处理Event

7.      如果连接数超过4个,worker数并不会跟着增长,相反worker会被共用. 也就是说,一个worker负责超过一个连接,这个worker里的selector上登记了超过一个channel. 而总的工作线程数仍等于worker数。

总结

     Netty会使用一个Boss线程通过轮询来侦听新连接,一旦有新连接,就把它丢到一个线程数固定的worker线程池里去。池里的worker线程会接收、登记新连接并轮询当前是否有read readiness,如果有,则同步地处理数据,除非你的handler里自己实现了异步处理。

     Boss和worker的轮询都是通过selector.select()来实现的,并且都使用了超时参数,对worker来说,使用超时机制可以避免让自己阻塞过长时间,它可以阻塞一小段时间后做点别的事情,这个事情就是接收、登记新连接。 

    另外还可以看出,Worker既做select(),又亲自处理数据。

Leave a Comment

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.