原創申明:本文由公眾號【猿燈塔】原創,轉載請說明出處標注
今天是猿燈塔“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天干貨不斷微信搜索「猿燈塔」第一時間閱讀,回復【資料】【面試】【簡歷】有我准備的一線大廠面試資料和簡歷模板