Netty 源碼解析(九): connect 過程和 bind 過程分析


原創申明:本文由公眾號【猿燈塔】原創,轉載請說明出處標注
今天是猿燈塔“365篇原創計划”第九篇。
接下來的時間燈塔君持續更新Netty系列一共九篇
Netty 源碼解析(一): 開始
Netty 源碼解析(二): Netty 的 Channel
Netty 源碼解析(三): Netty的 Future 和 Promise
Netty 源碼解析(四): Netty 的 ChannelPipeline
Netty 源碼解析(五): Netty 的線程池分析
Netty 源碼解析(六): Channel 的 register 操作
Netty 源碼解析(七): NioEventLoop 工作流程
Netty 源碼解析(八): 回到 Channel的 register 操作
當前:Netty 源碼解析(九): connect過程和bind過程分析
今天呢!燈塔君跟大家講:
connect 過程和 bind過程分析

connect 過程和 bind 過程分析

上面我們介紹的 register 操作非常關鍵,它建立起來了很多的東西,它是 Netty 中 NioSocketChannel 和 NioServerSocketChannel 開始工作的起點。這一節,我們來說說 register 之后的 connect 操作和 bind 操作。這節非常簡單。

connect 過程分析

對於客戶端 NioSocketChannel 來說,前面 register 完成以后,就要開始 connect 了,這一步將連接到服務端。
privateChannelFuturedoResolveAndConnect(finalSocketAddressremoteAddress,finalSocketAddresslocalAddress){
//這里完成了register操作
finalChannelFutureregFuture=initAndRegister();
finalChannelchannel=regFuture.channel();

//這里我們不去糾結register操作是否isDone()
if(regFuture.isDone()){
if(!regFuture.isSuccess()){
returnregFuture;
}
//看這里
returndoResolveAndConnect0(channel,remoteAddress,localAddress,channel.newPromise());
}else{
....
}
}

這里大家自己一路點進去,我就不浪費篇幅了。最后,我們會來到 AbstractChannel 的 connect 方法:
@Override
publicChannelFutureconnect(SocketAddressremoteAddress,ChannelPromisepromise){
returnpipeline.connect(remoteAddress,promise);
}
我們看到,connect 操作是交給 pipeline 來執行的。進入 pipeline 中,我們會發現,connect 這種 Outbound 類型的操作,是從 pipeline 的 tail 開始的:
前面我們介紹的 register 操作是 Inbound 的,是從 head 開始的
@Override
publicfinalChannelFutureconnect(SocketAddressremoteAddress,ChannelPromisepromise){
returntail.connect(remoteAddress,promise);
}

接下來就是 pipeline 的操作了,從 tail 開始,執行 pipeline 上的 Outbound 類型的 handlers 的 connect(...) 方法,那么真正的底層的 connect 的操作發生在哪里呢?還記得我們的 pipeline 的圖嗎?

 

 
 
從 tail 開始往前找 out 類型的 handlers,每經過一個 handler,都執行里面的 connect() 方法,最后會到 head 中,因為 head 也是 Outbound 類型的,我們需要的 connect 操作就在 head 中,它會負責調用 unsafe 中提供的 connect 方法:
//HeadContext
publicvoidconnect(
ChannelHandlerContextctx,
SocketAddressremoteAddress,SocketAddresslocalAddress,
ChannelPromisepromise)throwsException{
unsafe.connect(remoteAddress,localAddress,promise);
}

接下來,我們來看一看 connect 在 unsafe 類中所謂的底層操作:
//AbstractNioChannel.AbstractNioUnsafe
@Override
publicfinalvoidconnect(
finalSocketAddressremoteAddress,finalSocketAddresslocalAddress,finalChannelPromisepromise){
......

booleanwasActive=isActive();
//大家自己點進去看doConnect方法
//這一步會做JDK底層的SocketChannelconnect,然后設置interestOps為SelectionKey.OP_CONNECT
//返回值代表是否已經連接成功
if(doConnect(remoteAddress,localAddress)){
//處理連接成功的情況
fulfillConnectPromise(promise,wasActive);
}else{
connectPromise=promise;
requestedRemoteAddress=remoteAddress;
//下面這塊代碼,在處理連接超時的情況,代碼很簡單
//這里用到了NioEventLoop的定時任務的功能,這個我們之前一直都沒有介紹過,因為我覺得也不太重要
intconnectTimeoutMillis=config().getConnectTimeoutMillis();
if(connectTimeoutMillis>0){
connectTimeoutFuture=eventLoop().schedule(newRunnable(){
@Override
publicvoidrun(){
ChannelPromiseconnectPromise=AbstractNioChannel.this.connectPromise;
ConnectTimeoutExceptioncause=
newConnectTimeoutException("connectiontimedout:"+remoteAddress);
if(connectPromise!=null&&connectPromise.tryFailure(cause)){
close(voidPromise());
}
}
},connectTimeoutMillis,TimeUnit.MILLISECONDS);
}
promise.addListener(newChannelFutureListener(){
@Override
publicvoidoperationComplete(ChannelFuturefuture)throwsException{
if(future.isCancelled()){
if(connectTimeoutFuture!=null){
connectTimeoutFuture.cancel(false);
}
connectPromise=null;
close(voidPromise());
}
}
});
}
}catch(Throwablet){
promise.tryFailure(annotateConnectException(t,remoteAddress));
closeIfClosed();
}
}
如果上面的 doConnect 方法返回 false,那么后續是怎么處理的呢?在上一節介紹的 register 操作中,channel 已經 register 到了 selector 上,只不過將 interestOps 設置為了 0,也就是什么都不監聽。而在上面的 doConnect 方法中,我們看到它在調用底層的 connect 方法后,會設置 interestOps 為 SelectionKey.OP_CONNECT。剩下的就是 NioEventLoop 的事情了,還記得 NioEventLoop 的 run() 方法嗎?也就是說這里的 connect 成功以后,這個 TCP 連接就建立起來了,后續的操作會在 NioEventLoop.run() 方法中被 processSelectedKeys() 方法處理掉。

bind 過程分析

說完 connect 過程,我們再來簡單看下 bind 過程:
privateChannelFuturedoBind(finalSocketAddresslocalAddress){
//**前面說的initAndRegister**
finalChannelFutureregFuture=initAndRegister();

finalChannelchannel=regFuture.channel();
if(regFuture.cause()!=null){
returnregFuture;
}

if(regFuture.isDone()){
//register動作已經完成,那么執行bind操作
ChannelPromisepromise=channel.newPromise();
doBind0(regFuture,channel,localAddress,promise);
returnpromise;
}else{
......
}
}

然后一直往里看,會看到,bind 操作也是要由 pipeline 來完成的: // AbstractChannel
@Override
publicChannelFuturebind(SocketAddresslocalAddress,ChannelPromisepromise){
returnpipeline.bind(localAddress,promise);
}

bind 操作和 connect 一樣,都是 Outbound 類型的,所以都是 tail 開始:
@Override
publicfinalChannelFuturebind(SocketAddresslocalAddress,ChannelPromisepromise){
returntail.bind(localAddress,promise);
}
最后的 bind 操作又到了 head 中,由 head 來調用 unsafe 提供的 bind 方法:
@Override
publicvoidbind(
ChannelHandlerContextctx,SocketAddresslocalAddress,ChannelPromisepromise)
throwsException{
unsafe.bind(localAddress,promise);
}
感興趣的讀者自己去看一下 unsafe 中的 bind 方法,非常簡單,bind 操作也不是什么異步方法,我們就介紹到這里了。本節非常簡單,就是想和大家介紹下 Netty 中各種操作的套路。
365天干貨不斷微信搜索「猿燈塔」第一時間閱讀,回復【資料】【面試】【簡歷】有我准備的一線大廠面試資料和簡歷模板
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM