以下分析只講NIO
使用java nio做網路程式設計大致流程如下
這個流程有哪些可以優化的空間?
Netty是對java網路框架的包裝,它本身肯定也會有類似的處理流程。必定在這個方面做了自己的優化處理
獲得Selector
使用Netty的時候都會用到對應的EventLoopGroup,它實際上就完成了Selector的初始化過程
Netty自定義了SelectionKey的集合,做了層包裝,實際將Selector只有1個SelectorKey的集合換成了預設的兩個集合
獲得Channel
使用Netty時會執行channel的型別,然後在執行bind方法時,此處就會對channel實行初始化
構建的方式為
class.newInstance()
,以NioServerSocketChannel為例,它執行的就是對應的無參建構函式。
public NioServerSocketChannel() {
//newSocket即返回java的ServerSocketChannel
this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}
public NioServerSocketChannel(ServerSocketChannel channel) {
//指定當前channel用來接收連線請求,並在父類中指定為非阻塞
super(null, channel, SelectionKey.OP_ACCEPT);
//javaChannel()即這裡的引數channel
config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}
複製程式碼
緊接著Netty開始channel的初始化,在NioServerSocketChannel的pipeline最後新增了一個ChannelInboundHandlerAdapter
即ServerBootstrapAcceptor
,它會執有 childGroup
和childHandler
,childHandler即使用者自定義的channelHandler,而childGroup則是處理請求所用的EventLoop,此時整個pipeline的結構為
childGroup為原始碼中欄位的命名,對應為group中傳遞的worker執行緒池
channel的註冊與監聽埠地址關聯
註冊即建立channel和Selector的關係,值得注意的是,註冊使用的執行緒池為group
,對應使用者傳入的執行緒池即boss執行緒池,註冊和埠、地址關聯則順著Netty的啟動流程進行
至此可以看到,java nio所需要的準備工作都已經準備好了,剩下的就是等待事件發生以及處理髮生的事件。與普通java nio的不同之處在於
- Netty準備了兩個執行緒池,channel註冊、埠繫結監聽的只用到了其中同一個執行緒池
等待事件發生
NioEventLoop實現了Executor,意味著它接受其它地方提交任務給它執行,execute的大致結構如下
//判斷當前正在執行的執行緒是否是Netty自己的eventLoop中儲存的執行緒
boolean inEventLoop = inEventLoop();
if (inEventLoop) {
//往佇列裡新增任務
addTask(task);
} else {
//這裡即執行NioEventLoop自身的run方法
startThread();
addTask(task);
}
複製程式碼
NioEventLoop啟動執行緒執行run方法,整體結構如下
for (;;) {
if (hasTasks()) {
selectNow();
} else {
select(oldWakenUp);
}
processSelectedKeys();
runAllTasks();
}
複製程式碼
run迴圈處理的流程如下
值得注意的是,這是單個執行緒在執行,而且非本執行緒的任務一概不處理
boss執行緒的啟動時機
在啟動的過程中,有ServerBootstrap來串起整個流程,它的執行執行緒為主執行緒,而註冊事件都是交由執行緒池自己來執行的,用程式表達來講,就是執行了eventLoop自己的execute,此時執行執行緒必定不是EventLoop自己的執行緒,從而boss中的執行緒啟動,在佇列任務中完成註冊
新連線請求的到來
當NioServerSocketChannel繫結了埠之後,NioServerSocketChannel對應的NioEventLoop會等待channel發生事件。整個處理流程如下
-
讀取訊息的內容,發生在NioServerSocketChannel,對於這個新的連線事件,則包裝成一個客戶端的請求channel作為後續處理
protected int doReadMessages(List<Object> buf) throws Exception { //1:獲取請求的channel SocketChannel ch = javaChannel().accept(); try { if (ch != null) { //2:包裝成一個請求,Socket channel返回 buf.add(new NioSocketChannel(this, ch)); return 1; } } catch (Throwable t) { logger.warn("Failed to create a new channel from an accepted socket.", t); try { ch.close(); } catch (Throwable t2) { logger.warn("Failed to close a socket.", t2); } } return 0; } 複製程式碼
-
返回的NioSocketChannel則完成自身channel的初始化,註冊感興趣的事件
protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) { super(parent, ch, SelectionKey.OP_READ); } 複製程式碼
回想到boss中的下一環即ServerBootstrapAcceptor
,而它讀取訊息的處理則是新增使用者自己的handler,並繼續完成註冊事件
public void channelRead(ChannelHandlerContext ctx, Object msg) {
final Channel child = (Channel) msg;
child.pipeline().addLast(childHandler);
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);
}
}
for (Entry<AttributeKey<?>, Object> e: childAttrs) {
child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
}
try {
childGroup.register(child).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess()) {
forceClose(child, future.cause());
}
}
});
} catch (Throwable t) {
forceClose(child, t);
}
}
複製程式碼
worker執行緒的啟動時機
worker的註冊發生在boss的執行緒執行中,此刻必定不是同一個執行緒,因而開始啟動worker的執行緒,並在內部完成註冊事件,等待讀訊息的到來
OP_read訊息處理
連線建立後的請求則是交由NioSocketChannel
來處理,它將讀到的訊息封裝成ByteBuf,通過InBound
處理器fireChannelRead
依次傳給其它的地方消費,一直到tailContext訊息處理完畢
此處也可以得知管道的 in 表示資料傳入netty,回寫則是通過 out 一直到Head然後寫入channel
Netty中Nio的處理流程
從上述分析可以得到,Netty的處理流程如下
boss是否需要多個執行緒
mainReactor 多執行緒配置 ,對於多個埠監聽是有益的,當然1個也可以處理多埠
Reactor模式
CPU的處理速度快於IO處理速度,在處理事情時,最佳情況是CPU不會由於IO處理而遭到阻塞,造成CPU的”浪費“,當然可以用多執行緒去處理IO請求,但是這會增加執行緒的上下文切換,切換過去可能IO操作也還沒有完成,這也存在浪費的情況。
另一種方式是:當IO操作完成之後,再通知CPU進行處理。那誰來知曉IO操作完成?並將事件講給CPU處理呢?在Reactor模式中,這就是Reactor的作用,它啟動一個不斷執行的執行緒來等待IO發生,並按照事件型別,分發給不同的事先註冊好的事件處理器來處理
Reactor模式抽象如下