歡迎關注公衆號:【愛編程】
如果有需要後臺回覆2019贈送1T的學習資料哦!!
本文是基於Netty4.1.36進行分析
服務端
Netty服務端的啓動代碼基本都是如下:
private void start() throws Exception {
final EchoServerHandler serverHandler = new EchoServerHandler();
/**
* NioEventLoop並不是一個純粹的I/O線程,它除了負責I/O的讀寫之外
* 創建了兩個NioEventLoopGroup,
* 它們實際是兩個獨立的Reactor線程池。
* 一個用於接收客戶端的TCP連接,
* 另一個用於處理I/O相關的讀寫操作,或者執行系統Task、定時任務Task等。
*/
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup childGroup = new NioEventLoopGroup();
try {
//ServerBootstrap負責初始化netty服務器,並且開始監聽端口的socket請求
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, childGroup)
.channel(NioServerSocketChannel.class)
.localAddress(new InetSocketAddress(port))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
// 爲監聽客戶端read/write事件的Channel添加用戶自定義的ChannelHandler
socketChannel.pipeline().addLast(serverHandler);
}
});
ChannelFuture f = b.bind().sync();
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully().sync();
childGroup.shutdownGracefully().sync();
}
}
從上圖的代碼可以總結爲以下幾個步驟:
1、創建ServerBootStrap實例
2、設置並綁定Reactor線程池:EventLoopGroup,EventLoop就是處理所有註冊到本線程的Selector上面的Channel
3、設置並綁定服務端的channel
4、5、創建處理網絡事件的ChannelPipeline和handler,網絡時間以流的形式在其中流轉,handler完成多數的功能定製:比如編解碼 SSl安全認證
6、綁定並啓動監聽端口
7、當輪訓到準備就緒的channel後,由Reactor線程:NioEventLoop執行pipline中的方法,最終調度並執行channelHandler
服務端創建時序圖
ServerBootStrap引導啓動服務端
它就是主要引導啓動服務端,工作包括以下:
- 1.創建服務端Channel
- 2.初始化服務端Channel
- 3.將Channel註冊到selector
- 4.端口綁定
1.創建服務端Channel
流程:
首先從用戶代碼的bind()其實就是AbstractBootstrap.bind(),然後通過反射工廠將用戶通過b.channel(NioServerSocketChannel.class)傳入的NioServerSocketChannel通過調用底層的jdk的SelectorProvider創建channel,同時也接着創建好對應的ChannelPipeline。
詳情可以參考下圖,自己去查看一下源碼:
2.初始化服務端Channel
主要工作如下:
1)設置的option緩存到NioServerSocketChannelConfig裏
2)設置的attr設置到channel裏
3)保存配置的childOptions,配置的childAttrs 到ServerBootstrapAcceptor裏
4)往NioSocketChannel的pipeline中添加一個ServerBootstrapAcceptor
主要的核心源碼如下:
@Override
void init(Channel channel) throws Exception {
final Map<ChannelOption<?>, Object> options = options0();
synchronized (options) {
setChannelOptions(channel, options, logger);
}
final Map<AttributeKey<?>, Object> attrs = attrs0();
synchronized (attrs) {
for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
@SuppressWarnings("unchecked")
AttributeKey<Object> key = (AttributeKey<Object>) e.getKey();
channel.attr(key).set(e.getValue());
}
}
ChannelPipeline p = channel.pipeline();
final EventLoopGroup currentChildGroup = childGroup;
final ChannelHandler currentChildHandler = childHandler;
final Entry<ChannelOption<?>, Object>[] currentChildOptions;
final Entry<AttributeKey<?>, Object>[] currentChildAttrs;
synchronized (childOptions) {
currentChildOptions = childOptions.entrySet().toArray(newOptionArray(0));
}
synchronized (childAttrs) {
currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(0));
}
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(final Channel ch) throws Exception {
final ChannelPipeline pipeline = ch.pipeline();
ChannelHandler handler = config.handler();
if (handler != null) {
pipeline.addLast(handler);
}
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
pipeline.addLast(new ServerBootstrapAcceptor(
ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
}
小結:
總體如上面工作流程所述。
特別地建議:查看ServerBootstrapAcceptor源碼,你可以發現ServerBootstrapAcceptor在channelRead事件觸發的時候(也就有客戶端連接的時候),把childHandler加到childChannel Pipeline的末尾,設置childHandler的options和attrs,最後把childHandler註冊進childGroup
3.將Channel註冊到selector
註冊過程如下圖
小結:
Channel 註冊過程所做的工作就是將 Channel 與對應的 EventLoop 關聯。
1).每個 Channel 都會關聯一個特定的 EventLoop, 並且這個 Channel 中的所有 IO 操作都是在這個 EventLoop 中執行的;
2).當關聯好 Channel 和 EventLoop 後, 會繼續調用底層的 Java NIO SocketChannel 的 register 方法, 將底層的 Java NIO SocketChannel 註冊到指定的 selector 中.
通過這兩步, 就完成了 Netty Channel 的註冊過程.
4.端口綁定
端口綁定的源碼流程基本如下圖,詳情可以還是你自己讀一下源碼比較好點。
小結:
其實netty端口綁定是調用 jdk的javaChannel().bind(localAddress, config.getBacklog());進行綁定,然後TCP鏈路建立成功,Channel激活事件,通過channelPipeline進行傳播。
客戶端
客戶端啓動的常規代碼如下:
private void start() throws Exception {
/**
* Netty用於接收客戶端請求的線程池職責如下。
* (1)接收客戶端TCP連接,初始化Channel參數;
* (2)將鏈路狀態變更事件通知給ChannelPipeline
*/
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.remoteAddress(new InetSocketAddress(host,port))
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new EchoClientHandler());
}
});
//綁定端口
ChannelFuture f = b.connect().sync();
f.channel().closeFuture().sync();
} catch (Exception e) {
group.shutdownGracefully().sync();
}
}
流程:
1.用戶線程創建Bootstrap實例,通過API設置創建客戶端相關的參數,異步發起客戶端連接。
2.創建處理客戶端連接、I/O讀寫的Reactor線程組NioEventLoopGroup,默認爲CPU內核數的2倍。
3.通過Bootstrap的ChannelFactory和用戶指定的Channel類型創建用於客戶端NioSocketChannel,它的功能類似於JDK NIO類庫提供的SocketChannel
4.創建默認的Channel Handler Pipeline,用於調度和執行網路事件。
5.異步發起TCP連接,判斷連接是否成功。如果成功,則直接將NioSocketChannel註冊到多路複用器上,監聽讀操作位,用於數據包讀取和消息發送,如果沒有立即連接成功,則註冊連接監聽爲到多路複用器,等待連接結果。
6.註冊對應的網絡監聽狀態爲到多路複用器。
7.由多路複用器在I/O現場中輪詢個Channel,處理連接結果。
8.如果連接成功,設置Future結果,發送連接成功事件,觸發ChannelPipeline執行。
9.由ChannelPipeline調度執行系統和用戶的ChannelHandler,執行邏輯。
源碼調用流程如下圖:
小結:
客戶端是如何發起 TCP 連接的?
如下圖:
特別提醒:
在AbstractChannelHandlerContext.connect()#findContextOutbound這步操作是返回的結果next其實是頭節點,也就是說在下一步next.invokeConnect()這裏的next就是頭節點,所以最終是調用HeadContext .connect()
總結
本文主要講述netty服務端和客戶端的簡單工作流程。
具體服務端與客戶端如何通信,以及內存管理等方面的知識下一次再寫。
最後
如果對 Java、大數據感興趣請長按二維碼關注一波,我會努力帶給你們價值。覺得對你哪怕有一丁點幫助的請幫忙點個贊或者轉發哦。
關注公衆號【愛編碼】,回覆2019有相關資料哦。