日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

二、Netty服务端/客户端启动整体流程

發布時間:2025/3/15 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 二、Netty服务端/客户端启动整体流程 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、綜述

Netty 的整體流程相對來說還是比較復雜的,初學者往往會被繞暈。所以這里總結了一下整體的流程,從而對 Netty 的整體服務流程有一個大致的了解。從功能上,流程可以分為服務啟動、建立連接、讀取數據、業務處理、發送數據、關閉連接以及關閉服務。整體流程如下所示(圖中沒有包含關閉的部分):

  • Server端工作原理
  • server端啟動時綁定本地某個端口,將自己NioServerSocketChannel注冊到某個boss NioEventLoop的selector上。
  • server端包含1個boss NioEventLoopGroup和1個worker NioEventLoopGroup,NioEventLoopGroup相當于1個事件循環組,這個組里包含多個事件循環NioEventLoop,每個NioEventLoop包含1個selector和1個事件循環線程。
  • 每個boss NioEventLoop循環執行的任務包含3步:

    • 第1步:輪詢accept事件;
    • 第2步:處理io任務,即accept事件,與client建立連接,生成NioSocketChannel,并將NioSocketChannel注冊到某個worker NioEventLoop的selector上;
    • 第3步:處理任務隊列中的任務,runAllTasks。任務隊列中的任務包括用戶調用eventloop.execute或schedule執行的任務,或者其它線程提交到該eventloop的任務。

    每個worker NioEventLoop循環執行的任務包含3步:

    • 第1步:輪詢read、write事件;
    • 第2步:處理io任務,即read、write事件,在NioSocketChannel可讀、可寫事件發生時進行處理;
    • 第3步:處理任務隊列中的任務,runAllTasks。
    • Client端工作原理

    client端啟動時connect到server,建立NioSocketChannel,并注冊到某個NioEventLoop的selector上。client端只包含1個NioEventLoopGroup,每個NioEventLoop循環執行的任務包含3步:

    • 第1步:輪詢connect、read、write事件;
    • 第2步:處理io任務,即connect、read、write事件,在NioSocketChannel連接建立、可讀、可寫事件發生時進行處理;
    • 第3步:處理非io任務,runAllTasks。

    二、啟動流程概述

    • Netty服務端的啟動流程如下:

      啟動流程大致分為五步
  • 創建ServerBootstrap實例,ServerBootstrap是Netty服務端的啟動輔助類,其存在意義在于其整合了Netty可以提供的所有能力,并且盡可能的進行了封裝,以方便我們使用
  • 設置并綁定EventLoopGroup,EventLoopGroup其實是一個包含了多個EventLoop的NIO線程池,在上一篇文章我們也有比較詳細的介紹過EventLoop事件循環機制,不過值得一提的是,Netty中的EventLoop不僅僅只處理IO讀寫事件,還會處理用戶自定義或系統的Task任務
  • 創建服務端ChannelNioServerSocketChannel,并綁定至一個EventLoop上。在初始化NioServerSocketChannel的同時,會創建ChannelPipeline,ChannelPipeline其實是一個綁定了多個ChannelHandler的執行鏈,后面我們會詳細介紹
  • 為服務端Channel添加并綁定ChannelHandler,ChannelHandler是Netty開放給我們的一個非常重要的接口,在觸發網絡讀寫事件后,Netty都會調用對應的ChannelHandler來處理,后面我們會詳細介紹
  • 為服務端Channel綁定監聽端口,完成綁定之后,Reactor線程(也就是第三步綁定的EventLoop線程)就開始執行Selector輪詢網絡IO事件了,如果Selector輪詢到網絡IO事件了,則會調用Channel對應的ChannelPipeline來依次執行對應的ChannelHandler
    • netty客戶端啟動流程
    • 關鍵步驟:
  • 用戶線程創建Bootstrap實例,通過API設置創建客戶端相關的參數,異步發起客戶端連接。
  • 創建處理客戶端連接、I/O讀寫的Reator線程組NioEventLoopGroup。可以通過構造函數指定I/O線程的個數,默認為CPU內核數的2倍。
  • 通過Bootstrap的ChannelFactory和用戶指定的Channel類型創建用于客戶端連接的NioSocketChannel,它的功能類似于JDK NIO類庫提供的SocketChannel。
  • 創建默認的Channel Handler Pipeline,用于調度和執行網絡事件。
  • 異步發起TCP連接,判斷連接是否成功。如果成功,則直接將NioSocketChannel注冊到多路復用器上,監聽讀操作位,用于數據報讀取和消息發送。如果沒有立即連接成功,則注冊連接監聽位到多路復用器,等待連接結果。
  • 注冊對應的網絡監聽狀態位到多路復用器。
  • 由多路復用器在I/O現場中輪詢Channel,處理連接結果。
  • 如果連接成功,設置Future結果,發送連接成功事件,觸發ChannelPipeline執行。
  • 有ChannelPipeline調度執行系統和用戶的ChannelHandler,執行業務邏輯。
    • 結合代碼解析:

    三、NettyServer啟動源碼

    public class NettyServer {public static void main(String[] args) throws Exception {//創建BossGroup 和 WorkerGroup//說明//1. 創建兩個線程組 bossGroup 和 workerGroup//2. bossGroup 只是處理連接請求 , 真正的和客戶端業務處理,會交給 workerGroup完成//3. 兩個都是無限循環//4. bossGroup 和 workerGroup 含有的子線程(NioEventLoop)的個數// 默認實際 cpu核數 * 2EventLoopGroup bossGroup = new NioEventLoopGroup(1);EventLoopGroup workerGroup = new NioEventLoopGroup(); //8try {//創建服務器端的啟動對象,配置參數ServerBootstrap bootstrap = new ServerBootstrap();//使用鏈式編程來進行設置bootstrap.group(bossGroup, workerGroup) //設置兩個線程組.channel(NioServerSocketChannel.class) //使用NioSocketChannel 作為服務器的通道實現.option(ChannelOption.SO_BACKLOG, 128) // 設置線程隊列得到連接個數.childOption(ChannelOption.SO_KEEPALIVE, true) //設置保持活動連接狀態 // .handler(null) // 該 handler對應 bossGroup , childHandler 對應 workerGroup.childHandler(new ChannelInitializer<SocketChannel>() {//創建一個通道初始化對象(匿名對象)//給pipeline 設置處理器@Overrideprotected void initChannel(SocketChannel ch) throws Exception {System.out.println("客戶socketchannel hashcode=" + ch.hashCode()); //可以使用一個集合管理 SocketChannel, 再推送消息時,可以將業務加入到各個channel 對應的 NIOEventLoop 的 taskQueue 或者 scheduleTaskQueuech.pipeline().addLast(new NettyServerHandler());}}); // 給我們的workerGroup 的 EventLoop 對應的管道設置處理器System.out.println(".....服務器 is ready...");//綁定一個端口并且同步, 生成了一個 ChannelFuture 對象//啟動服務器(并綁定端口)ChannelFuture cf = bootstrap.bind(6668).sync();//給cf 注冊監聽器,監控我們關心的事件cf.addListener(new ChannelFutureListener() {@Overridepublic void operationComplete(ChannelFuture future) throws Exception {if (cf.isSuccess()) {System.out.println("監聽端口 6668 成功");} else {System.out.println("監聽端口 6668 失敗");}}});//對關閉通道進行監聽cf.channel().closeFuture().sync();}finally {bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}} }
    • 簡要流程圖
    • boosGroup和workerGroup內部結構圖

    • 源碼解析:
  • 可以看到上面的代碼首先創建了兩個EventLoopGroup,Netty的線程模型有三種,而不同的EventLoopGroup配置對應了三種不同的線程模型。這里創建的兩個EventLoopGroup則是用了多線程Reactor模型,其中bossEventLoopGroup對應的就是處理Accept事件的線程組,而workEventLoopGroup則負責處理IO讀寫事件。
  • 然后就是創建了一個啟動輔助類ServerBootstrap,并且配置了如下幾個重要參數
    • group : 兩個Reactor線程組(bossEventLoopGroup, workEventLoopGroup)
    • channel: 服務端Channel
    • option: 服務端socket參數配置 例如SO_BACKLOG指定內核未連接的Socket連接排隊個數
    • handler 服務端Channel對應的Handler
    • childHandler 客戶端請求Channel對應的Handler
  • 綁定服務端監聽端口,啟動服務 -> ChannelFuture f = b.bind(PORT).sync();
    這篇文章主要是分析Netty的啟動流程。我們直接看b.bind(PORT).sync()的源碼。bind 發現該方法內部實際調用的doBind(final SocketAddress localAddress)方法
    • bind方法
    private ChannelFuture doBind(final SocketAddress localAddress) { // 初始化服務端Channelfinal ChannelFuture regFuture = initAndRegister();final Channel channel = regFuture.channel();if (regFuture.cause() != null) {return regFuture;}if (regFuture.isDone()) {// 初始化一個 promise(異步回調)ChannelPromise promise = channel.newPromise();// 綁定監聽端口doBind0(regFuture, channel, localAddress, promise);return promise;} .... // 省略其他代碼 }

    doBind主要做了兩個事情

    • initAndRegister() 初始化Channel
    • doBind0 綁定監聽端口
    final ChannelFuture initAndRegister() {Channel channel = null;try {// new一個新的服務端Channelchannel = channelFactory.newChannel();// 初始化Channelinit(channel);} catch (Throwable t) {...}// 將Channel注冊到EventLoopGroup中一個EventLoop上ChannelFuture regFuture = config().group().register(channel);if (regFuture.cause() != null) {if (channel.isRegistered()) {channel.close();} else {channel.unsafe().closeForcibly();}}return regFuture; }
  • channelFactory.newChannel()其實就是通過反射創建配置的服務端Channel類,在這里是NioServerSocketChannel

  • 創建完成的NioServerSocketChannel進行一些初始化操作,例如將我們配置的Handler加到服務端Channel的pipeline中

  • 將Channel注冊到EventLoopGroup中一個EventLoop上

  • 下面我們來看下NioServerSocketChannel類的構造方法,看看它到底初始化了哪些東西,先看下其繼承結構

    • NioServerSocketChannel初始化

    下面是它的構造方法的調用順序,依次分為了四步

    // 1 public NioServerSocketChannel() { // 通過 SelectProvider來初始化一個Java NioServerChannelthis(newSocket(DEFAULT_SELECTOR_PROVIDER)); }// 2. public NioServerSocketChannel(ServerSocketChannel channel) {super(null, channel, SelectionKey.OP_ACCEPT);// 創建一個配置類,持有Java Channelconfig = new NioServerSocketChannelConfig(this, javaChannel().socket()); }// 3. protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {super(parent);this.ch = ch;this.readInterestOp = readInterestOp;try {// 設置Channel為非阻塞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);} }// 4 protected AbstractChannel(Channel parent) {this.parent = parent;// 生成一個channel Idid = newId();// 創建一個 unSafe 類,unsafe封裝了Netty底層的IO讀寫操作unsafe = newUnsafe();// 創建一個 pipeline類pipeline = newChannelPipeline(); }

    可以看到NioServerSocketChannel的構造函數主要是初始化并綁定了以下3類

  • 綁定一個Java ServerSocketChannel類
  • 綁定一個unsafe類,unsafe封裝了Netty底層的IO讀寫操作
  • 綁定一個pipeline,每個Channel都會唯一綁定一個pipeline
  • init(Channel channel)

    void init(Channel channel) { // 設置Socket參數setChannelOptions(channel, newOptionsArray(), logger);setAttributes(channel, attrs0().entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY));ChannelPipeline p = channel.pipeline();// 子EventLoopGroup用于完成Nio讀寫操作final EventLoopGroup currentChildGroup = childGroup;// 為workEventLoop配置的自定義Handlerfinal ChannelHandler currentChildHandler = childHandler;final Entry<ChannelOption<?>, Object>[] currentChildOptions;synchronized (childOptions) {currentChildOptions = childOptions.entrySet().toArray(EMPTY_OPTION_ARRAY);}// 設置附加參數final Entry<AttributeKey<?>, Object>[] currentChildAttrs = childAttrs.entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY);// 為服務端Channel pipeline 配置 對應的Handlerp.addLast(new ChannelInitializer<Channel>() {@Overridepublic void initChannel(final Channel ch) {final ChannelPipeline pipeline = ch.pipeline();ChannelHandler handler = config.handler();if (handler != null) {pipeline.addLast(handler);}ch.eventLoop().execute(new Runnable() {@Overridepublic void run() {pipeline.addLast(new ServerBootstrapAcceptor(ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));}});}}); }

    這里主要是為服務端Channel配置一些參數,以及對應的處理器ChannelHandler,注意這里不僅僅會把我們自定義配置的ChannelHandler加上去,同時還會自動幫我們加入一個系統Handler(ServerBootstrapAcceptor),這就是Netty用來接收客戶端請求的Handler,在ServerBootstrapAcceptor內部會完成SocketChannel的連接,EventLoop的綁定等操作,之后我們會著重分析這個類

    Channel的注冊

    // MultithreadEventLoopGroup public ChannelFuture register(Channel channel) { // next()會選擇一個EventLoop來完成Channel的注冊return next().register(channel); }// SingleThreadEventLoop public ChannelFuture register(Channel channel) {return register(new DefaultChannelPromise(channel, this)); } // AbstractChannel 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)) {promise.setFailure(new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));return;}AbstractChannel.this.eventLoop = eventLoop;if (eventLoop.inEventLoop()) {// 注冊邏輯register0(promise);} .... }

    完成注冊流程

  • 完成實際的Java ServerSocketChannel與Select選擇器的綁定
  • 并觸發channelRegistered以及channelActive事件
  • 到這里為止,其實Netty服務端已經基本啟動完成了,就差綁定一個監聽端口了。可能讀者會很詫異,怎么沒有看到Nio線程輪詢 IO事件的循環呢,講道理肯定應該有一個死循環才對?那我們下面就把這段代碼找出來

    在之前的代碼中,我們經常會看到這樣一段代碼

    // 往EventLoop中丟了一個異步任務(其實是同步的,因為只有一個Nio線程,不過因為是事件循環機制(丟到一個任務隊列中),看起來像是異步的) eventLoop.execute(new Runnable() {@Overridepublic void run() {...} });

    eventLoop.execute到底做了什么事情?

    private void execute(Runnable task, boolean immediate) {boolean inEventLoop = inEventLoop();// 把當前任務添加到任務隊列中addTask(task);// 不是Nio線程自己調用的話,則表明是初次啟動if (!inEventLoop) {// 啟動EventLoop的Nio線程startThread();...}... }/*** 啟動EventLoop的Nio線程*/ private void doStartThread() {assert thread == null;// 啟動Nio線程executor.execute(new Runnable() {@Overridepublic void run() {thread = Thread.currentThread();if (interrupted) {thread.interrupt();}boolean success = false;updateLastExecutionTime();try {SingleThreadEventExecutor.this.run();success = true;} catch (Throwable t) {logger.warn("Unexpected exception from an event executor: ", t);} finally {... }

    通過上面的代碼可以知道這里主要做了兩件事情

  • 創建的任務被丟入了一個隊列中等待執行
  • 如果是初次創建,則啟動Nio線程
  • SingleThreadEventExecutor.this.run(); 調用子類的Run實現(執行IO事件的輪詢) 看下
    NioEventLoop的Run方法實現
  • protected void run() {int selectCnt = 0;for (;;) {try {int strategy;try {// 獲取IO事件類型strategy = selectStrategy.calculateStrategy(selectNowSupplier, hasTasks());... default:}} catch (IOException e) {// 出現異常 重建SelectorrebuildSelector0();selectCnt = 0;handleLoopException(e);continue;}selectCnt++;cancelledKeys = 0;needsToSelectAgain = false;final int ioRatio = this.ioRatio;boolean ranTasks;if (ioRatio == 100) {try {if (strategy > 0) {// 處理對應事件,激活對應的ChannelHandler事件processSelectedKeys();}} finally {// 處理完事件了才執行全部TaskranTasks = runAllTasks();}}...} } }

    到這里的代碼是不是就非常熟悉了,熟悉的死循環輪詢事件

  • 通過Selector來輪詢IO事件
  • 觸發Channel所綁定的Handler處理對應的事件
  • 處理完IO事件了會執行系統或用戶自定義加入的Task
    • doBind0
      實際的Bind邏輯在 NioServerSocketChannel中執行,我們直接省略前面一些冗長的調用,來看下最底層的調用代碼,發現其實就是調用其綁定的Java Channel來執行對應的監聽端口綁定邏輯
    protected void doBind(SocketAddress localAddress) throws Exception { // 如果JDK版本大于7if (PlatformDependent.javaVersion() >= 7) {javaChannel().bind(localAddress, config.getBacklog());} else {javaChannel().socket().bind(localAddress, config.getBacklog());} }

    四、Netty服務端詳細的工作流程圖

    流程圖

    參考文章1
    參考文章2
    參考文章3

    與50位技術專家面對面20年技術見證,附贈技術全景圖

    總結

    以上是生活随笔為你收集整理的二、Netty服务端/客户端启动整体流程的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。