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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

Netty源码学习4——服务端是处理新连接的&netty的reactor模式

發布時間:2023/11/20 windows 41 coder
生活随笔 收集整理的這篇文章主要介紹了 Netty源码学习4——服务端是处理新连接的&netty的reactor模式 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

系列文章目錄和關于我

零丶引入

在前面的源碼學習中,梳理了服務端的啟動,以及NioEventLoop事件循環的工作流程,并了解了Netty處理網絡io重要的Channel ,ChannelHandler,ChannelPipeline。

這一篇將學習服務端是如何構建新的連接。

一丶網絡包接收流程

當客戶端發送的網絡數據幀通過網絡傳輸到網卡時,網卡的DMA引擎將網卡接收緩沖區中的數據拷貝到DMA環形緩沖區,數據拷貝完成后網卡硬件觸發硬中斷,通知操作系統數據已到達。

隨后網卡中斷處理程序將DMA環形緩沖區的數據拷貝到sk_buffer,sk_buffer位于內核中,它提供了一個緩沖區,使得網卡中斷程序可以將他接收到的數據暫存起來,避免數據丟失和切換。

隨后發起軟中斷,網絡協議棧會處理數據包,對數據包進行解析,路由,分發(根據目的端口號,分發給對應的應用程序,通過網絡編程套接字,應用程序可以監聽指定端口號,并接受網絡協議棧的數據包)

  • 當新的連接建立時,網絡協議處理棧會將這個連接的套接字標記為可讀,并生成一個accept事件,這個事件通知應用程序有新的連接需要處理
  • 當已經建立的連接上有數據到達時,網絡協議處理棧會將套接字標記為刻度,并生成一個read事件,這個事件通知應用程序有數據可供讀取
  • 當應用程序向已經建立的連接寫入數據時,如果寫緩沖區有足夠的空間,寫操作會立即完成,不會產生write事件。但如果寫緩沖區已滿,那么寫操作將被暫停,當寫緩沖區有足夠的空間時,write事件將被觸發,通知應用程序可以繼續寫入數據。

也就是說netty 服務端程序會監聽不同的網絡事件,并進行處理,這也是源碼學習的切入點!

二丶服務端NioEventLoop處理網絡IO事件

如上是NioEventLoop的運行機制,在《Netty源碼學習2——NioEventLoop的執行》中我們進行了大致流程的學習,這一篇我么主要關注其run中處理網絡IO事件的部分。

無論是否優化,最終都是拿到就緒的SelectionKey,循環處理每一個就緒的網絡事件,如下便是處理的邏輯:

可以看到無論是accept事件還是read事件都是調用AbstractNioChannel的Unsafe#read方法

Unsafe是對netty對底層網絡事件處理的封裝,下面我們先看下AbstractNioChannel的類圖,可以看到NioServerSocketChannel,和NioSocketChannel都使用繼承了AbstractNioChannel,只是父類有所不同

那么NioServerSocketChannel和NioSocketChannel是什么時候Accept or read事件感興趣的昵?

三丶NioServerSocketChannel設置對accept事件感興趣

重點在ServerBootstrap#bind中,此方法會調用doBind0

doBind0會調用Channel#bind,然后處理ChannelPipeline#bind的執行,由于bind是出站事件,將從DefaultChannelPipeline的TailContext開始執行,然后調用到HeadContext#bind方法,最終會調用NioServerSocketChannel的unsafe#bind方法

如下是NioServerSocketChannel的unsafe#bind的內容:

主要完成兩部分操作:

  • 調用java原生ServerSocketChannel#bind方法,進行端口綁定,這樣操作系統網絡協議棧在分發網絡數據的時候,才直到該分發到這個端口的ServerSocketChannel

  • 向EventLoop中提交一個pipeline.fireChannelActive()的任務,將在pipeline上觸發channelActive方法,HeadContext#channelActive將被調用到

    這里將調用到Channel#read方法,最終會調用到HeadContext#read

四丶服務端處理Accept事件

前面我們說到,NioEventLoop處理accept事件和read事件都是調用unsafe#read方法,如下是NioServerSocketChannel#unsafe的read方法

  public void read() {
            assert eventLoop().inEventLoop();
            final ChannelConfig config = config();
            final ChannelPipeline pipeline = pipeline();
            final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
            allocHandle.reset(config);

            boolean closed = false;
            Throwable exception = null;
            try {
                try {
                    do {
                        //讀取數據
                        int localRead = doReadMessages(readBuf);
                        if (localRead == 0) {
                            break;
                        }
                        if (localRead < 0) {
                            closed = true;
                            break;
                        }
						// 計數
                        allocHandle.incMessagesRead(localRead);
                    } while (continueReading(allocHandle));
                } catch (Throwable t) {
                    exception = t;
                }
				
                int size = readBuf.size();
                for (int i = 0; i < size; i ++) {
                    readPending = false;
                    // 觸發channelRead
                    pipeline.fireChannelRead(readBuf.get(i));
                }
                readBuf.clear();
                allocHandle.readComplete();
                // 觸發channelReadComplete
                pipeline.fireChannelReadComplete();

               // 省略
            } finally {
               // 省略
            }
        }

這里出現一個RecvByteBufAllocator.Handle,這里不需要過多關注,在NioServerSocketChannel建立連接的過程中,它負責控制是否還需要繼續讀取數據

ServerSocketChannel類提供了accept()方法,用于接受客戶端的連接請求,返回一個SocketChannel代表了一個底層的TCP連接。

如上將jdk SocketChannel包裝NioSocketChannel的時候會設置SocketChannel非阻塞并在屬性readInterestOp記錄感興趣事件為read

包裝生成的NioSocketChannel會放到List中,后續每一個就緒的連接會一次傳播ChannelRead,并最終傳播ChannelReadComplete

1.channeRead事件的傳播

上面說到NioEventLoop讀取NioServerSocketChannel上的accept事件,將每一個新連接封裝為NioServerChannel后,將依次觸發channelRead。

如下是ServerBootstrapAcceptor#channelRead方法,可以看到它會將讀取生成的NioServerChannel注冊到childGroup,這里的childGroup就是ServerBootstrap啟動時候指定EventLoopGroup(主從reactor模式中的從reactor)

也就是說主reactor負責處理accept事件,從reactor負責處理read事件

2.channelReadComplete事件傳播

大多數人看到 channelReadComplete 都會認為這是 Netty 讀取了完整的數據,然而有時卻不是這樣。channelReadComplete 其實只是表明了本次從 Socket 讀了數據,該方法通常可以用來進行一些收尾工作,例如發送響應數據或進行資源的釋放等。channelReadComplete方法在每次讀取數據完成后,即使沒有更多的數據可讀,也會被調用一次。

五丶netty對多種reactor模式的支持

這里其實可以看出netty對多種reactor模式(單線程,多線程,主從reactor)的支持

我們其實可以通過修改bossGroup,和workerGroup使netty使用不同的reactor模式

六丶將NioSocketChannel注冊到從reactor

上面我們說到主reactor監聽accept事件后傳播channelRead事件,最終由ServerBootstrapAcceptor調用childGroup#register將包裝生成的NioSocketChannel注冊到從reactor(也就是workerGroup——EventLoopGroup)下面我們看看這個注冊會發生什么

首先workerGroup這個EventLoopGroup會調用next方法選擇出一個EventLoop執行register,然后

  • 將NioSocketChannel中的jdk SockectChannel注冊到Selector中,并將NioSocketChannel當作附件,這樣selector#select到事件的時候,可以從附件中拿到網絡事件對應的NioSocketChannel

  • 觸發handlerAdd

    這一步觸發ChannelHandler#handlerAdded

    最終會調用到childHandler中指定的ChannelInitializer,它會將我們指定的ServerHandler(這里可以擴展我們的業務處理邏輯)加到NioSockectChannel的pipeline中

  • 觸發ChannelRegistered

  • 觸發channelActive

    由于這是一個新連接,是第一次注冊到EventLoop,因此會觸發channelActive

    這將調用到DefaultChannelPipeline的HeadContext#readIfIsAutoRead,最終就和我們第三節的【NioServerSocketChannel設置對accept事件感興趣】差不多
    ——HeadContext#readIfIsAutoRead會調用NioSockectChannel的read方法,最終調用到NioSockectChannel#unsafe的read方法——將注冊對read事件感興趣

七丶再看Netty的Reactor模式

筆者認為netty的reactor有以下幾個要點

  • ServerBootstrap#bind方法

    不僅僅會綁定端口,還會觸發channelActive事件,從而使DefaultChannelPipeline中的HeadContext觸發netty channel unsafe#beginRead,注冊ServerSockectChannel對accept感興趣

  • NioEventLoop處理新連接

    這一步Netty 使用Selector進行IO多路復用,當accept事件產生的時候,調用NioServerSocketChannel#unsafe的read方法,這一步會將新連接封裝NioSocketChannel,然后將對應連接的套接字注冊到Selector上,然后傳播channeRead事件

  • ServerBootstrapAcceptor 對channeRead事件的處理

    筆者認為這是netty reactor模式的核心,它將NioSocketChannel注冊到從reactor上,讓子reactor負責處理NioSocketChannel上的事件,并最終注冊SocketChannel對read事件感興趣!

和tomcat的reactor(《Reactor 模式與Tomcat中的Reactor 》)有異曲同工之妙,只是netty Pipeline的設計讓整個流程更具備擴展性,當然也增加了源碼學習的復雜度doge

八丶啟下

下一篇我們將學習從reactor是如何處理read事件的,整個流程和主reactor處理accept事件類似,后續應該會設計到netty編解碼相關的知識。

這一篇是雙11結束后忙里偷閑的產物,附上一張雙11后和女朋友游烏鎮的風景圖

總結

以上是生活随笔為你收集整理的Netty源码学习4——服务端是处理新连接的&netty的reactor模式的全部內容,希望文章能夠幫你解決所遇到的問題。

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