閱讀這篇文章之前,建議先閱讀和這篇文章關聯的內容。
1. 詳細剖析分散式微服務架構下網路通訊的底層實現原理(圖解)
2. (年薪60W的技巧)工作了5年,你真的理解Netty以及為什麼要用嗎?(深度乾貨)
4. BAT面試必問細節:關於Netty中的ByteBuf詳解
5. 通過大量實戰案例分解Netty中是如何解決拆包黏包問題的?
6. 基於Netty實現自定義訊息通訊協議(協議設計及解析應用實戰)
8. 手把手教你基於Netty實現一個基礎的RPC框架(通俗易懂)
9. (年薪60W分水嶺)基於Netty手寫實現RPC框架進階篇(帶註冊中心和註解)
提前準備好如下程式碼, 從服務端構建著手,深入分析Netty服務端的啟動過程。
public class NettyBasicServerExample {
public void bind(int port){
//netty的服務端程式設計要從EventLoopGroup開始,
// 我們要建立兩個EventLoopGroup,
// 一個是boss專門用來接收連線,可以理解為處理accept事件,
// 另一個是worker,可以關注除了accept之外的其它事件,處理子任務。
//上面注意,boss執行緒一般設定一個執行緒,設定多個也只會用到一個,而且多個目前沒有應用場景,
// worker執行緒通常要根據伺服器調優,如果不寫預設就是cpu的兩倍。
EventLoopGroup bossGroup=new NioEventLoopGroup();
EventLoopGroup workerGroup=new NioEventLoopGroup();
try {
//服務端要啟動,需要建立ServerBootStrap,
// 在這裡面netty把nio的模板式的程式碼都給封裝好了
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
//配置Server的通道,相當於NIO中的ServerSocketChannel
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO)) //設定ServerSocketChannel對應的Handler
//childHandler表示給worker那些執行緒配置了一個處理器,
// 這個就是上面NIO中說的,把處理業務的具體邏輯抽象出來,放到Handler裡面
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline()
.addLast(new NormalInBoundHandler("NormalInBoundA",false))
.addLast(new NormalInBoundHandler("NormalInBoundB",false))
.addLast(new NormalInBoundHandler("NormalInBoundC",true));
socketChannel.pipeline()
.addLast(new NormalOutBoundHandler("NormalOutBoundA"))
.addLast(new NormalOutBoundHandler("NormalOutBoundB"))
.addLast(new NormalOutBoundHandler("NormalOutBoundC"))
.addLast(new ExceptionHandler());
}
});
//繫結埠並同步等待客戶端連線
ChannelFuture channelFuture=bootstrap.bind(port).sync();
System.out.println("Netty Server Started,Listening on :"+port);
//等待服務端監聽埠關閉
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//釋放執行緒資源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) {
new NettyBasicServerExample().bind(8080);
}
}
public class NormalInBoundHandler extends ChannelInboundHandlerAdapter {
private final String name;
private final boolean flush;
public NormalInBoundHandler(String name, boolean flush) {
this.name = name;
this.flush = flush;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("InboundHandler:"+name);
if(flush){
ctx.channel().writeAndFlush(msg);
}else {
throw new RuntimeException("InBoundHandler:"+name);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("InboundHandlerException:"+name);
super.exceptionCaught(ctx, cause);
}
}
public class NormalOutBoundHandler extends ChannelOutboundHandlerAdapter {
private final String name;
public NormalOutBoundHandler(String name) {
this.name = name;
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
System.out.println("OutBoundHandler:"+name);
super.write(ctx, msg, promise);
}
}
在服務端啟動之前,需要配置ServerBootstrap的相關引數,這一步大概分為以下幾個步驟
- 配置EventLoopGroup執行緒組
- 配置Channel型別
- 設定ServerSocketChannel對應的Handler
- 設定網路監聽的埠
- 設定SocketChannel對應的Handler
- 配置Channel引數
Netty會把我們配置的這些資訊組裝,釋出服務監聽。
ServerBootstrap引數配置過程
下面這段程式碼是我們配置ServerBootStrap相關引數,這個過程比較簡單,就是把配置的引數值儲存到ServerBootstrap定義的成員變數中就可以了。
bootstrap.group(bossGroup, workerGroup)
//配置Server的通道,相當於NIO中的ServerSocketChannel
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO)) //設定ServerSocketChannel對應的Handler
//childHandler表示給worker那些執行緒配置了一個處理器,
// 這個就是上面NIO中說的,把處理業務的具體邏輯抽象出來,放到Handler裡面
.childHandler(new ChannelInitializer<SocketChannel>() {
});
我們來看一下ServerBootstrap的類關係圖以及屬性定義
ServerBootstrap類關係圖
如圖8-1所示,表示ServerBootstrap的類關係圖。
- AbstractBootstrap,定義了一個抽象類,作為抽象類,一定是抽離了Bootstrap相關的抽象邏輯,所以很顯然可以推斷出Bootstrap應該也繼承了AbstractBootstrap
- ServerBootstrap,服務端的啟動類,
- ServerBootstrapAcceptor,繼承了ChannelInboundHandlerAdapter,所以本身就是一個Handler,當服務端啟動後,客戶端連線上來時,會先進入到ServerBootstrapAccepter。
AbstractBootstrap屬性定義
public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable {
@SuppressWarnings("unchecked")
private static final Map.Entry<ChannelOption<?>, Object>[] EMPTY_OPTION_ARRAY = new Map.Entry[0];
@SuppressWarnings("unchecked")
private static final Map.Entry<AttributeKey<?>, Object>[] EMPTY_ATTRIBUTE_ARRAY = new Map.Entry[0];
/**
* 這裡的EventLoopGroup 作為服務端 Acceptor 執行緒,負責處理客戶端的請求接入
* 作為客戶端 Connector 執行緒,負責註冊監聽連線操作位,用於判斷非同步連線結果。
*/
volatile EventLoopGroup group; //
@SuppressWarnings("deprecation")
private volatile ChannelFactory<? extends C> channelFactory; //channel工廠,很明顯應該是用來製造對應Channel的
private volatile SocketAddress localAddress; //SocketAddress用來繫結一個服務端地址
// The order in which ChannelOptions are applied is important they may depend on each other for validation
// purposes.
/**
* ChannelOption 可以新增Channer 新增一些配置資訊
*/
private final Map<ChannelOption<?>, Object> options = new LinkedHashMap<ChannelOption<?>, Object>();
private final Map<AttributeKey<?>, Object> attrs = new ConcurrentHashMap<AttributeKey<?>, Object>();
/**
* ChannelHandler 是具體怎麼處理Channer 的IO事件。
*/
private volatile ChannelHandler handler;
}
對於上述屬性定義,整體總結如下:
-
提供了一個ChannelFactory物件用來建立Channel,一個Channel會對應一個EventLoop用於IO的事件處理,在一個Channel的整個生命週期中 只會繫結一個EventLoop,這裡可理解給Channel分配一個執行緒進行IO事件處理,結束後回收該執行緒。
-
AbstractBootstrap沒有提供EventLoop而是提供了一個EventLoopGroup,其實我認為這裡只用一個EventLoop就行了。
-
不管是伺服器還是客戶端的Channel都需要繫結一個本地埠這就有了SocketAddress類的物件localAddress。
-
Channel有很多選項所有有了options物件LinkedHashMap<channeloption<?>, Object>
-
怎麼處理Channel的IO事件呢,我們新增一個事件處理器ChannelHandler物件。
ServerBootstrap屬性定義
ServerBootstrap可以理解為伺服器啟動的工廠類,我們可以通過它來完成伺服器端的 Netty 初始化。主要職責:|
EventLoop初始化
channel的註冊
pipeline的初始化
handler的新增過程
- 服務端連線處理。
public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel> {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(ServerBootstrap.class);
// The order in which child ChannelOptions are applied is important they may depend on each other for validation
// purposes.
//SocketChannel相關的屬性配置
private final Map<ChannelOption<?>, Object> childOptions = new LinkedHashMap<ChannelOption<?>, Object>();
private final Map<AttributeKey<?>, Object> childAttrs = new ConcurrentHashMap<AttributeKey<?>, Object>();
private final ServerBootstrapConfig config = new ServerBootstrapConfig(this); //配置類
private volatile EventLoopGroup childGroup; //工作執行緒組
private volatile ChannelHandler childHandler; //負責SocketChannel的IO處理相關的Handler
public ServerBootstrap() { }
}
服務端啟動過程分析
瞭解了ServerBootstrap相關屬性的配置之後,我們繼續來看服務的啟動過程,在開始往下分析的時候,先不妨來思考以下這些問題
- Netty自己實現的Channel與底層JDK提供的Channel是如何聯絡並且構建實現的
- ChannelInitializer這個特殊的Handler處理器的作用以及實現原理
- Pipeline是如何初始化以的
ServerBootstrap.bind
先來看ServerBootstrap.bind()方法的定義,這裡主要用來繫結一個埠並且釋出服務端監聽。
根據我們使用NIO相關API的理解,無非就是使用JDK底層的API來開啟一個服務端監聽並繫結一個埠。
ChannelFuture channelFuture=bootstrap.bind(port).sync();
public ChannelFuture bind(SocketAddress localAddress) {
validate();
return doBind(ObjectUtil.checkNotNull(localAddress, "localAddress"));
}
-
validate(), 驗證ServerBootstrap核心成員屬性的配置是否正確,比如group、channelFactory、childHandler、childGroup等,這些屬性如果沒配置,那麼服務端啟動會報錯
-
localAddress,繫結一個本地埠地址
doBind
doBind方法比較長,從大的程式碼結構,可以分為三個部分
initAndRegister
初始化並註冊Channel,並返回一個ChannelFuture,說明初始化註冊Channel是非同步實現regFuture.cause()
用來判斷initAndRegister()
是否發生異常,如果發生異常,則直接返回regFuture.isDone()
, 判斷initAndRegister()
方法是否執行完成。- 如果執行完成,則呼叫doBind0()方法。
- 如果未執行完成,regFuture新增一個監聽回撥,在監聽回撥中再次判斷執行結果進行相關處理。
- PendingRegistrationPromise 用來儲存非同步執行結果的狀態
從整體程式碼邏輯來看,邏輯結構還是非常清晰的, initAndRegister()
方法負責Channel的初始化和註冊、doBind0()
方法用來繫結埠。這個無非就是我們使用NIO相關API釋出服務所做的事情。
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;
}
}
initAndRegister
這個方法顧名思義,就是初始化和註冊,基於我們整個流程的分析可以猜測到
- 初始化,應該就是構建服務端的Handler處理鏈
- register,應該就是把當前服務端的連線註冊到selector上
下面我們通過原始碼印證我們的猜想。
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
//通過ChannelFactory建立一個具體的Channel實現
channel = channelFactory.newChannel();
init(channel); //初始化
} catch (Throwable t) {
//省略....
}
//這個程式碼應該和我們猜想是一致的,就是將當前初始化的channel註冊到selector上,這個過程同樣也是非同步的
ChannelFuture regFuture = config().group().register(channel);
if (regFuture.cause() != null) { //獲取regFuture的執行結果
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
return regFuture;
}
channelFactory.newChannel()
這個方法在分析之前,我們可以繼續推測它的邏輯。
在最開始構建服務端的程式碼中,我們通過channel設定了一個NioServerSocketChannel.class
類物件,這個物件表示當前channel的構建使用哪種具體的API
bootstrap.group(bossGroup, workerGroup)
//配置Server的通道,相當於NIO中的ServerSocketChannel
.channel(NioServerSocketChannel.class)
而在initAndRegister方法中,又用到了channelFactory.newChannel()來生成一個具體的Channel例項,因此不難想到,這兩者必然有一定的聯絡,我們也可以武斷的認為,這個工廠會根據我們配置的channel來動態構建一個指定的channel例項。
channelFactory有多個實現類,所以我們可以從配置方法中找到channelFactory的具體定義,程式碼如下。
public B channel(Class<? extends C> channelClass) {
return channelFactory(new ReflectiveChannelFactory<C>(
ObjectUtil.checkNotNull(channelClass, "channelClass")
));
}
channelFactory對應的具體實現是:ReflectiveChannelFactory,因此我們定位到newChannel()方法的實現。
ReflectiveChannelFactory.newChannel
在該方法中,使用constructor構建了一個例項。
@Override
public T newChannel() {
try {
return constructor.newInstance();
} catch (Throwable t) {
throw new ChannelException("Unable to create Channel from class " + constructor.getDeclaringClass(), t);
}
}
construtor的初始化程式碼如下, 用到了傳遞進來的clazz類,獲得該類的構造器,該構造器後續可以通過newInstance建立一個例項物件
而此時的clazz其實就是:NioServerSocketChannel
public class ReflectiveChannelFactory<T extends Channel> implements ChannelFactory<T> {
private final Constructor<? extends T> constructor;
public ReflectiveChannelFactory(Class<? extends T> clazz) {
ObjectUtil.checkNotNull(clazz, "clazz");
try {
this.constructor = clazz.getConstructor();
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException("Class " + StringUtil.simpleClassName(clazz) +
" does not have a public non-arg constructor", e);
}
}
}
NioServerSocketChannel
NioServerSocketChannel的構造方法定義如下。
public class NioServerSocketChannel extends AbstractNioMessageChannel
implements io.netty.channel.socket.ServerSocketChannel {
private static ServerSocketChannel newSocket(SelectorProvider provider) {
try {
return provider.openServerSocketChannel();
} catch (IOException e) {
throw new ChannelException(
"Failed to open a server socket.", e);
}
}
public NioServerSocketChannel() {
this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}
}
當NioServerSocketChannel例項化後,呼叫newSocket方法建立了一個服務端例項。
newSocket方法中呼叫了provider.openServerSocketChannel()
,來完成ServerSocketChannel的建立,ServerSocketChannel就是Java中NIO中的服務端API。
public ServerSocketChannel openServerSocketChannel() throws IOException {
return new ServerSocketChannelImpl(this);
}
通過層層推演,最終看到了Netty是如何一步步封裝,完成ServerSocketChannel的建立。
設定非阻塞
在NioServerSocketChannel中的構造方法中,先通過super呼叫父類做一些配置操作
public NioServerSocketChannel(ServerSocketChannel channel) {
super(null, channel, SelectionKey.OP_ACCEPT);
config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}
最終,super會呼叫AbstractNioChannel中的構造方法,
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
super(parent);
this.ch = ch;
this.readInterestOp = readInterestOp; //設定關心事件,此時是一個連線事件,所以是OP_ACCEPT
try {
ch.configureBlocking(false); //設定非阻塞
} catch (IOException e) {
try {
ch.close();
} catch (IOException e2) {
logger.warn(
"Failed to close a partially initialized socket.", e2);
}
throw new ChannelException("Failed to enter non-blocking mode.", e);
}
}
繼續分析initAndRegister
分析完成channel的初始化後,接下來就是要將當前channel註冊到Selector上,所以繼續回到initAndRegister方法。
final ChannelFuture initAndRegister() {
//省略....
//這個程式碼應該和我們猜想是一致的,就是將當前初始化的channel註冊到selector上,這個過程同樣也是非同步的
ChannelFuture regFuture = config().group().register(channel);
if (regFuture.cause() != null) { //獲取regFuture的執行結果
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
return regFuture;
}
註冊到某個Selector上,其實就是註冊到某個EventLoopGroup中,如果大家能有這個猜想,說明前面的內容是聽懂了的。
config().group().register(channel)
這段程式碼,其實就是獲取在ServerBootstrap中配置的bossEventLoopGroup,然後把當前的服務端channel註冊到該group中。
此時,我們通過快捷鍵想去看一下register的實現時,發現EventLoopGroup又有多個實現,我們來看一下類關係圖如圖8-2所示。
而我們在前面配置的EventLoopGroup的實現類是NioEventLoopGroup,而NioEventLoopGroup繼承自MultithreadEventLoopGroup,所以在register()方法中,我們直接找到父類的實現方法即可。
MultithreadEventLoopGroup.register
這段程式碼大家都熟了,從NioEventLoopGroup中選擇一個NioEventLoop,將當前channel註冊上去
@Override
public ChannelFuture register(Channel channel) {
return next().register(channel);
}
next()
方法返回的是NioEventLoop,而NioEventLoop又有多個實現類,我們來看圖8-4所示的類關係圖。
從類關係圖中發現,發現NioEventLoop派生自SingleThreadEventLoop,所以next().register(channel);
方法,執行的是SingleThreadEventLoop中的register
SingleThreadEventLoop.register
@Override
public ChannelFuture register(Channel channel) {
return register(new DefaultChannelPromise(channel, this));
}
@Override
public ChannelFuture register(final ChannelPromise promise) {
ObjectUtil.checkNotNull(promise, "promise");
promise.channel().unsafe().register(this, promise);
return promise;
}
ChannelPromise, 派生自Future,用來實現非同步任務處理回撥功能。簡單來說就是把註冊的動作非同步化,當非同步執行結束後會把執行結果回填到ChannelPromise中
AbstractChannel.register
抽象類一般就是公共邏輯的處理,而這裡的處理主要就是針對一些引數的判斷,判斷完了之後再呼叫register0()
方法。
@Override
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
ObjectUtil.checkNotNull(eventLoop, "eventLoop");
if (isRegistered()) { //判斷是否已經註冊過
promise.setFailure(new IllegalStateException("registered to an event loop already"));
return;
}
if (!isCompatible(eventLoop)) { //判斷eventLoop型別是否是EventLoop物件型別,如果不是則丟擲異常
promise.setFailure(
new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
return;
}
AbstractChannel.this.eventLoop = eventLoop;
//Reactor內部執行緒呼叫,也就是說當前register方法是EventLoop執行緒觸發的,則執行下面流程
if (eventLoop.inEventLoop()) {
register0(promise);
} else { //如果是外部執行緒
try {
eventLoop.execute(new Runnable() {
@Override
public void run() {
register0(promise);
}
});
} catch (Throwable t) {
logger.warn(
"Force-closing a channel whose registration task was not accepted by an event loop: {}",
AbstractChannel.this, t);
closeForcibly();
closeFuture.setClosed();
safeSetFailure(promise, t);
}
}
}
AbstractChannel.register0
Netty從EventLoopGroup執行緒組中選擇一個EventLoop和當前的Channel繫結,之後該Channel生命週期中的所有I/O事件都由這個EventLoop負責。
register0方法主要做四件事:
- 呼叫JDK層面的API對當前Channel進行註冊
- 觸發HandlerAdded事件
- 觸發channelRegistered事件
- Channel狀態為活躍時,觸發channelActive事件
在當前的ServerSocketChannel連線註冊的邏輯中,我們只需要關注下面的doRegister
方法即可。
private void register0(ChannelPromise promise) {
try {
// check if the channel is still open as it could be closed in the mean time when the register
// call was outside of the eventLoop
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return;
}
boolean firstRegistration = neverRegistered;
doRegister(); //呼叫JDK層面的register()方法進行註冊
neverRegistered = false;
registered = true;
// Ensure we call handlerAdded(...) before we actually notify the promise. This is needed as the
// user may already fire events through the pipeline in the ChannelFutureListener.
pipeline.invokeHandlerAddedIfNeeded(); //觸發Handler,如果有必要的情況下
safeSetSuccess(promise);
pipeline.fireChannelRegistered();
// Only fire a channelActive if the channel has never been registered. This prevents firing
// multiple channel actives if the channel is deregistered and re-registered.
if (isActive()) { //此時是ServerSocketChannel的註冊,所以連線還處於非活躍狀態
if (firstRegistration) {
pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
// This channel was registered before and autoRead() is set. This means we need to begin read
// again so that we process inbound data.
//
// See https://github.com/netty/netty/issues/4805
beginRead();
}
}
} catch (Throwable t) {
// Close the channel directly to avoid FD leak.
closeForcibly();
closeFuture.setClosed();
safeSetFailure(promise, t);
}
}
AbstractNioChannel.doRegister
進入到AbstractNioChannel.doRegister方法。
javaChannel().register()負責呼叫JDK層面的方法,把channel註冊到eventLoop().unwrappedSelector()
上,其中第三個引數傳入的是Netty自己實現的Channel物件,也就是把該物件繫結到attachment中。
這樣做的目的是,後續每次調Selector物件進行事件輪詢時,當觸發事件時,Netty都可以獲取自己的Channe物件。
@Override
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
return;
} catch (CancelledKeyException e) {
if (!selected) {
// Force the Selector to select now as the "canceled" SelectionKey may still be
// cached and not removed because no Select.select(..) operation was called yet.
eventLoop().selectNow();
selected = true;
} else {
// We forced a select operation on the selector before but the SelectionKey is still cached
// for whatever reason. JDK bug ?
throw e;
}
}
}
}
服務註冊總結
上述程式碼比較繞,但是整體總結下來並不難理解
- 初始化指定的Channel例項
- 把該Channel分配給某一個EventLoop
- 然後把Channel註冊到該EventLoop的Selector中
AbstractBootstrap.doBind0
分析完了註冊的邏輯後,再回到AbstractBootstrap類中的doBind0方法,這個方法不用看也能知道,ServerSocketChannel初始化了之後,接下來要做的就是繫結一個ip和埠地址。
private static void doBind0(
final ChannelFuture regFuture, final Channel channel,
final SocketAddress localAddress, final ChannelPromise promise) {
//獲取當前channel中的eventLoop例項,執行一個非同步任務。
//需要注意,以前我們在課程中講過,eventLoop在輪詢中一方面要執行select遍歷,另一方面要執行阻塞佇列中的任務,而這裡就是把任務新增到佇列中非同步執行。
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
//如果ServerSocketChannel註冊成功,則呼叫該channel的bind方法
if (regFuture.isSuccess()) {
channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
} else {
promise.setFailure(regFuture.cause());
}
}
});
}
channel.bind方法,會根據ServerSocketChannel中的handler鏈配置,逐個進行呼叫,由於在本次案例中,我們給ServerSocketChannel配置了一個 LoggingHandler的處理器,所以bind方法會先呼叫LoggingHandler,然後再呼叫DefaultChannelPipeline中的bind方法,呼叫鏈路
-> DefaultChannelPipeline.ind
-> AbstractChannel.bind
-> NioServerSocketChannel.doBind
最終就是呼叫前面初始化好的ServerSocketChannel中的bind方法繫結本地地址和埠。
protected void doBind(SocketAddress localAddress) throws Exception {
if (PlatformDependent.javaVersion() >= 7) {
javaChannel().bind(localAddress, config.getBacklog());
} else {
javaChannel().socket().bind(localAddress, config.getBacklog());
}
}
構建SocketChannel的Pipeline
在ServerBootstrap的配置中,我們針對SocketChannel,配置了入站和出站的Handler,也就是當某個SocketChannel的IO事件就緒時,就會按照我們配置的處理器連結串列進行逐一處理,那麼這個連結串列是什麼時候構建的,又是什麼樣的結構呢?下面我們來分析這部分的內容
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline()
.addLast(new NormalInBoundHandler("NormalInBoundA",false))
.addLast(new NormalInBoundHandler("NormalInBoundB",false))
.addLast(new NormalInBoundHandler("NormalInBoundC",true));
socketChannel.pipeline()
.addLast(new NormalOutBoundHandler("NormalOutBoundA"))
.addLast(new NormalOutBoundHandler("NormalOutBoundB"))
.addLast(new NormalOutBoundHandler("NormalOutBoundC"))
.addLast(new ExceptionHandler());
}
});
childHandler的構建
childHandler的構建過程,在AbstractChannel.register0
方法中實現
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
channel = channelFactory.newChannel(); //這是是建立channel
init(channel); //這裡是初始化
} catch (Throwable t) {
//省略....
}
ChannelFuture regFuture = config().group().register(channel); //這是是註冊
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
return regFuture;
}
ServerBootstrap.init
init方法,呼叫的是ServerBootstrap中的init()
,程式碼如下。
@Override
void init(Channel channel) {
setChannelOptions(channel, newOptionsArray(), logger);
setAttributes(channel, newAttributesArray());
ChannelPipeline p = channel.pipeline();
final EventLoopGroup currentChildGroup = childGroup;
final ChannelHandler currentChildHandler = childHandler; //childHandler就是在服務端配置時新增的ChannelInitializer
final Entry<ChannelOption<?>, Object>[] currentChildOptions = newOptionsArray(childOptions);
final Entry<AttributeKey<?>, Object>[] currentChildAttrs = newAttributesArray(childAttrs);
// 此時的Channel是NioServerSocketChannel,這裡是為NioServerSocketChannel新增處理器鏈。
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(final Channel ch) {
final ChannelPipeline pipeline = ch.pipeline();
ChannelHandler handler = config.handler(); //如果在ServerBootstrap構建時,通過.handler新增了處理器,則會把相關處理器新增到NioServerSocketChannel中的pipeline中。
if (handler != null) {
pipeline.addLast(handler);
}
ch.eventLoop().execute(new Runnable() { //非同步天劍一個ServerBootstrapAcceptor處理器,從名字來看,
@Override
public void run() {
pipeline.addLast(new ServerBootstrapAcceptor(
//currentChildHandler,表示SocketChannel的pipeline,當收到客戶端連線時,就會把該handler新增到當前SocketChannel的pipeline中
ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
}
其中,對於上述程式碼的核心部分說明如下
-
ChannelPipeline 是在AbstractChannel中的構造方法中初始化的一個DefaultChannelPipeline
protected AbstractChannel(Channel parent) { this.parent = parent; id = newId(); unsafe = newUnsafe(); pipeline = newChannelPipeline(); }
-
p.addLast
是為NioServerSocketChannel新增handler處理器鏈,這裡新增了一個ChannelInitializer回撥函式,該回撥是非同步觸發的,在回撥方法中做了兩件事- 如果ServerBootstrap.handler新增了處理器,則會把相關處理器新增到該pipeline中,在本次演示的案例中,我們新增了LoggerHandler
- 非同步執行新增了ServerBootstrapAcceptor,從名字來看,它是專門用來接收新的連線處理的。
我們在這裡思考一個問題,為什麼NioServerSocketChannel需要通過ChannelInitializer回撥處理器呢? ServerBootstrapAcceptor為什麼通過非同步任務新增到pipeline中呢?
原因是,NioServerSocketChannel在初始化的時候,還沒有開始將該Channel註冊到Selector物件上,也就是沒辦法把ACCEPT事件註冊到Selector上,所以事先新增了ChannelInitializer處理器,等待Channel註冊完成後,再向Pipeline中新增ServerBootstrapAcceptor。
ServerBootstrapAcceptor
按照下面的方法演示一下SocketChannel中的Pipeline的構建過程
- 啟動服務端監聽
- 在ServerBootstrapAcceptor的channelRead方法中打上斷點
- 通過telnet 連線,此時會觸發debug。
public void channelRead(ChannelHandlerContext ctx, Object msg) {
final Channel child = (Channel) msg;
child.pipeline().addLast(childHandler); //在這裡,將handler新增到SocketChannel的pipeline中
setChannelOptions(child, childOptions, logger);
setAttributes(child, childAttrs);
try {
//把當前客戶端的連結SocketChannel註冊到某個EventLoop中。
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);
}
}
ServerBootstrapAcceptor是服務端NioServerSocketChannel中的一個特殊處理器,該處理器的channelRead事件只會在新連線產生時觸發,所以這裡通過 final Channel child = (Channel) msg;
可以直接拿到客戶端的連結SocketChannel。
ServerBootstrapAcceptor接著通過childGroup.register()方法,把當前NioSocketChannel註冊到工作執行緒中。
事件觸發機制的流程
在ServerBootstrapAcceptor中,收到客戶端連線時,會呼叫childGroup.register(child)
把當前客戶端連線註冊到指定NioEventLoop的Selector中。
這個註冊流程和前面講解的NioServerSocketChannel註冊流程完全一樣,最終都會進入到AbstractChannel.register0方法。
AbstractChannel.register0
private void register0(ChannelPromise promise) {
try {
// check if the channel is still open as it could be closed in the mean time when the register
// call was outside of the eventLoop
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return;
}
boolean firstRegistration = neverRegistered;
doRegister();
neverRegistered = false;
registered = true;
// Ensure we call handlerAdded(...) before we actually notify the promise. This is needed as the
// user may already fire events through the pipeline in the ChannelFutureListener.
pipeline.invokeHandlerAddedIfNeeded();
safeSetSuccess(promise);
pipeline.fireChannelRegistered(); //執行pipeline中的ChannelRegistered()事件。
// Only fire a channelActive if the channel has never been registered. This prevents firing
// multiple channel actives if the channel is deregistered and re-registered.
if (isActive()) {
if (firstRegistration) {
pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
// This channel was registered before and autoRead() is set. This means we need to begin read
// again so that we process inbound data.
//
// See https://github.com/netty/netty/issues/4805
beginRead();
}
}
} catch (Throwable t) {
// Close the channel directly to avoid FD leak.
closeForcibly();
closeFuture.setClosed();
safeSetFailure(promise, t);
}
}
pipeline.fireChannelRegistered()
@Override
public final ChannelPipeline fireChannelRegistered() {
AbstractChannelHandlerContext.invokeChannelRegistered(head);
return this;
}
下面的事件觸發,分為兩個邏輯
- 如果當前的任務是在eventLoop中觸發的,則直接呼叫invokeChannelRegistered
- 否則,非同步執行invokeChannelRegistered。
static void invokeChannelRegistered(final AbstractChannelHandlerContext next) {
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
next.invokeChannelRegistered();
} else {
executor.execute(new Runnable() {
@Override
public void run() {
next.invokeChannelRegistered();
}
});
}
}
invokeChannelRegistered
觸發下一個handler的channelRegistered方法。
private void invokeChannelRegistered() {
if (invokeHandler()) {
try {
((ChannelInboundHandler) handler()).channelRegistered(this);
} catch (Throwable t) {
invokeExceptionCaught(t);
}
} else {
fireChannelRegistered();
}
}
Netty服務端啟動總結
到此為止,整個服務端啟動的過程,我們就已經分析完成了,主要的邏輯如下
- 建立服務端Channel,本質上是根據使用者配置的實現,呼叫JDK原生的Channel
- 初始化Channel的核心屬性,unsafe、pipeline
- 初始化Channel的Pipeline,主要是新增兩個特殊的處理器,ChannelInitializer和ServerBootstrapAcceptor
- 註冊服務端的Channel,新增OP_ACCEPT事件,這裡底層呼叫的是JDK層面的實現,講Channel註冊到BossEventLoop中的Selector上
- 繫結埠,呼叫JDK層面的API,繫結埠。
版權宣告:本部落格所有文章除特別宣告外,均採用 CC BY-NC-SA 4.0 許可協議。轉載請註明來自
Mic帶你學架構
!
如果本篇文章對您有幫助,還請幫忙點個關注和贊,您的堅持是我不斷創作的動力。歡迎關注「跟著Mic學架構」公眾號公眾號獲取更多技術乾貨!