IO - Netty的核心模塊

 這篇博客主要簡單介紹Netty上表中的組件

1. Bootstrap、ServerBootstrap

Bootstrap意思是引導,一個Netty應用通常由一個Bootstrap開始,主要作用是配置整個Netty程序,串聯各個組件,Netty中Bootstrap類是客戶端程序的啓動引導類,ServerBootstrap是服務端啓動引導類

常見的方法有

public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) #該方法用於服務器端,用來設置兩個EventLoopGroup
public B group(EventLoopGroup group) #該方法用於客戶端,用來設置一個EventLoopGroup
public B channel(Class<? extends C> channelClass) #該方法用來設置一個服務器端的通道實現
public <T> B option(ChannelOption<T> option, T value) #用來給ServerChannel添加配置
public <T> ServerBootstrap childOption(ChannelOption<T> childOption, T value) #用來給接收到的通道添加配置
public ServerBootstrap childHandler(ChannelHandler childHandler)  #該方法用來設置業務處理Handler(自定義的handler)
public ChannelFuture bind(int inetPort) #該方法用於服務器端,用來設置佔用的端口號
public ChannelFuture connect(String inetHost, int inetPort) #該方法用於客戶端,用來連接服務器端

2. Future,ChannelFuture

Netty 中所有的 IO 操作都是異步的,不能立刻得知消息是否被正確處理。但是可以過一會等它執行完成或者直接註冊一個監聽,具體的實現就是通Future和ChannelFuture,他們可以註冊一個監聽,當操作執行成功或失敗時監聽會自動觸發註冊的監聽事件
常見的方法有

Channel channel() #返回當前正在進行IO操作的通道
ChannelFuture sync() #等待異步操作執行完畢

3. Channel

Netty網絡通信的組件,能夠用於執行網絡I/O操作。通過Channel可獲得當前網絡連接的通道的狀態。通過Channel可獲得網絡連接的配置參數(例如接收緩衝區大小)。 Channel提供異步的網絡I/O操作(如建立連接,讀寫,綁定端口),異步調用意味着任何 I/O 調用都將立即返回,並且不保證在調用結束時所請求的I/O操作已完成。調用立即返回一個ChannelFuture實例,通過註冊監聽器到ChannelFuture上,可以I/O操作成功、失敗或取消時回調通知調用方。支持關聯 I/O操作與對應的處理程序
不同協議、不同的阻塞類型的連接都有不同的 Channel 類型與之對應,常用的 Channel 類型:

NioSocketChannel #異步的客戶端TCP Socket連接
NioServerSocketChannel #異步的服務器端TCP Socket連接
NioDatagramChannel #異步的UDP連接
NioSctpChannel #異步的客戶端Sctp連接
NioSctpServerChannel #異步的Sctp服務器端連接,這些通道涵蓋了UDP和TCP網絡IO以及文件IO

4. Selector

Netty基於Selector對象實現I/O多路複用,通過Selector一個線程可以監聽多個連接的Channel事件。當向一個Selector中註冊Channel後,Selector內部的機制就可以自動不斷地查詢(Select) 這些註冊的Channel是否有已就緒的I/O事件(例如可讀,可寫,網絡連接完成等),這樣程序就可以很簡單地使用一個線程高效地管理多個Channel

5. ChannelHandler

ChannelHandler是一個接口,處理I/O事件或攔截I/O操作,並將其轉發到其ChannelPipeline(業務處理鏈)中的下一個處理程序。ChannelHandler本身並沒有提供很多方法,因爲這個接口有許多的方法需要實現,方便使用期間可以繼承它的子類

ChannelHandler及其實現類

ChannelInboundHandler / ChannelInboundHandlerAdapter- 處理入站I/O事件
ChannelOutboundHandler / ChannelOutboundHandlerAdapter - 處理出站I/O事件

我們經常需要自定義一個Handler類去繼承ChannelInboundHandlerAdapter,然後通過重寫相應方法實現業務邏輯,可以重寫方法如下

    // channel註冊事件
    public void channelRegistered(ChannelHandlerContext ctx)    // channel取消註冊事件
    public void channelUnregistered(ChannelHandlerContext ctx)    // channel激活事件
    public void channelActive(ChannelHandlerContext ctx)    // channel不活躍事件
    public void channelInactive(ChannelHandlerContext ctx)    // channel讀取事件
    public void channelRead(ChannelHandlerContext ctx, Object msg)    // channel讀取完畢事件
    public void channelReadComplete(ChannelHandlerContext ctx)    // channel用戶事件觸發事件
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt)    // channel可寫改變事件
    public void channelWritabilityChanged(ChannelHandlerContext ctx)    // channel捕獲到異常
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)

6. ChannelPipeline

ChannelPipeline是一個Handler的集合,它負責處理和攔截inbound或者outbound的事件和操作,相當於一個貫穿Netty的鏈。(即:ChannelPipeline是保存ChannelHandler的List,用於處理或攔截Channel的入站事件和出站操作)。ChannelPipeline實現了一種高級形式的攔截過濾器模式,使用戶可以完全控制事件的處理方式,以及Channel中各個的ChannelHandler如何相互交互。在Netty中每個Channel都有且僅有一個ChannelPipeline與之對應,它們的組成關係如下

1. 一個Channel包含了一個ChannelPipeline,ChannelPipeline中又維護了一個由ChannelHandlerContext組成的雙向鏈表,並且每個ChannelHandlerContext中又關聯着一個ChannelHandler
2. 入站事件和出站事件在一個雙向鏈表中,入站事件會從鏈表的head向後傳遞到最後一個入站的handler,出站事件會從tail向前傳遞到最前一個出站的handler,兩張類型的handler互不干擾

7. ChannelHandlerContext

保存Channel相關的所有上下文信息,同時關聯一個ChannelHandler對象。即ChannelHandlerContext中包含一個具體的事件處理器ChannelHandler, 同時ChannelHandlerContext中也綁定了對應的pipeline和Channel的信息,方便對ChannelHandler進行調用

ChannelHandlerContext常用方法

Channel channel() #獲取通道 : 在ChannelHandlerContext接口中定義的方法 

ChannelPipeline pipeline() #獲取管道 : 在ChannelHandlerContext接口中定義的方法 

ChannelHandler handler() #獲取處理器 : 在ChannelHandlerContext接口中定義的方法 

ChannelHandlerContext flush() #刷新數據 : 在ChannelHandlerContext接口中定義的方法 

ChannelFuture close() #關閉通道 : 在ChannelOutboundInvoker接口中定義的方法 

ChannelFuture writeAndFlush(Object msg) #寫出數據 : 在ChannelOutboundInvoker接口中定義的方法 , 作用是將數據寫出到ChannelPipeline管道中 

8. ChannelOption

Netty在創建Channel實例後, 一般都需要設置ChannelOption參數。ChannelOption參數如下:

 1、ChannelOption.SO_BACKLOG
   ChannelOption.SO_BACKLOG對應的是tcp/ip協議listen函數中的backlog參數,函數listen(int socketfd,int backlog)用來初始化服務端可連接隊列,
服務端處理客戶端連接請求是順序處理的,所以同一時間只能處理一個客戶端連接,多個客戶端來的時候,服務端將不能處理的客戶端連接請求放在隊列中等待處理,
backlog參數指定了隊列的大小  
2、ChannelOption.SO_REUSEADDR    這個參數表示允許重複使用本地地址和端口,比如,某個服務器進程佔用了TCP的80端口進行監聽,此時再次監聽該端口就會返回錯誤,使用該參數就可以解決問題,該參數允許共用該端口,
這個在服務器程序中比較常使用,比如某個進程非正常退出,該程序佔用的端口可能要被佔用一段時間才能允許其他進程使用,而且程序死掉以後,內核一需要一定的時間才能夠釋放此端口,
不設置SO_REUSEADDR就無法正常使用該端口。  
3、ChannelOption.SO_KEEPALIVE    該參數用於設置TCP連接,當設置該選項以後,連接會測試鏈接的狀態,這個選項用於可能長時間沒有數據交流的連接。當設置該選項以後,如果在兩小時內沒有數據的通信時,
TCP會自動發送一個活動探測數據報文。  
4、ChannelOption.SO_SNDBUF和ChannelOption.SO_RCVBUF    這兩個參數用於操作接收緩衝區和發送緩衝區的大小,接收緩衝區用於保存網絡協議站內收到的數據,直到應用程序讀取成功,發送緩衝區用於保存發送數據,直到發送成功。  5、ChannelOption.SO_LINGER    Linux內核默認的處理方式是當用戶調用close()方法的時候,函數返回,在可能的情況下,儘量發送數據,不一定保證會發生剩餘的數據,造成了數據的不確定性,
使用SO_LINGER可以阻塞close()的調用時間,直到數據完全發送  
6、ChannelOption.TCP_NODELAY    該參數的使用與Nagle算法有關,Nagle算法是將小的數據包組裝爲更大的幀然後進行發送,而不是輸入一次發送一次,因此在數據包不足的時候會等待其他數據的到了,組裝成大的數據包進行發送,
雖然該方式有效提高網絡的有效負載,但是卻造成了延時,而該參數的作用就是禁止使用Nagle算法,使用於小數據即時傳輸,於TCP_NODELAY相對應的是TCP_CORK,
該選項是需要等到發送的數據量最大的時候,一次性發送數據,適用於文件傳輸。

9. EventLoopGroup,NioEventLoopGroup

對於Netty的使用,都會使用bossGroup和workerGroup的方式,而常說的bossGroup和workerGroup其實是NioEventLoopGroup的實例。在Netty中,EventLoopGroup和NioEventLoopGroup其實就是一個線程池。EventLoopGroup是一組EventLoop的抽象, Netty爲了更好的利用多核CPU資源,一般會有多個EventLoop同時工作,每個EventLoop維護着一個Selector實例。EventLoopGroup提供next接口,可以從組裏面按照一定規則獲取其中一個EventLoop來處理任務。通常一個服務端口即一個ServerSocketChannel對應一個Selector和一個EventLoop線程。 BossEventLoop負責接收客戶端的連接並將SocketChannel交給WorkerEventLoopGroup來進行IO處理

10. Unpooled

Netty 提供一個專門用來操作緩衝區(即Netty的數據容器)的工具類

常用方法

public static ByteBuf copiedBuffer(CharSequence string, Charset charset) #通過給定的數據和字符編碼返回ByteBuf

通過readerindex和writerIndex和capacity, 將buffer分成三個區域

 0---readerindex            已經讀取的區域
 readerindex---writerIndex  可讀的區域
 writerIndex -- capacity    可寫的區域

樣例代碼

package com.kawa.io.netty;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

@Slf4j
public class UnpooledTest {

    @Test
    public void Unpooled_Test_CopiedBuffer() {
        ByteBuf byteBuf = Unpooled.copiedBuffer("{'region':'cn'}", CharsetUtil.UTF_8);
        if (byteBuf.hasArray()) {
            byte[] bytes = byteBuf.array();
            log.info(">>>>>>>>>> byteBuf: {}", byteBuf);
            log.info(">>>>>>>>>> byteBuf convert to String: {}", new String(bytes, CharsetUtil.UTF_8));
            log.info(">>>>>>>>>> byteBuf arrayOffset:{}, readerIndex:{}, writerIndex:{}, capacity:{}", byteBuf.arrayOffset(), byteBuf.readerIndex(), byteBuf.writerIndex(), byteBuf.capacity());

            log.info(">>>>>>>>>> byteBuf > getByte: {}", (char) byteBuf.getByte(0));
            log.info(">>>>>>>>>> byteBuf > readableBytes: {}", byteBuf.readableBytes());
            log.info(">>>>>>>>>> byteBuf arrayOffset:{}, readerIndex:{}, writerIndex:{}, capacity:{}", byteBuf.arrayOffset(), byteBuf.readerIndex(), byteBuf.writerIndex(), byteBuf.capacity());

            log.info(">>>>>>>>>> byteBuf > readByte: {}", (char) byteBuf.readByte());
            log.info(">>>>>>>>>> byteBuf > readableBytes: {}", byteBuf.readableBytes());
            log.info(">>>>>>>>>> byteBuf arrayOffset:{}, readerIndex:{}, writerIndex:{}, capacity:{}", byteBuf.arrayOffset(), byteBuf.readerIndex(), byteBuf.writerIndex(), byteBuf.capacity());

            log.info(">>>>>>>>>> byteBuf > readByte: {}", (char) byteBuf.readByte());
            log.info(">>>>>>>>>> byteBuf > readableBytes: {}", byteBuf.readableBytes());
            log.info(">>>>>>>>>> byteBuf arrayOffset:{}, readerIndex:{}, writerIndex:{}, capacity:{}", byteBuf.arrayOffset(), byteBuf.readerIndex(), byteBuf.writerIndex(), byteBuf.capacity());

            log.info(">>>>>>>>>> byteBuf > getCharSequence: {}", byteBuf.getCharSequence(2, 5, CharsetUtil.UTF_8));
            log.info(">>>>>>>>>> byteBuf > getCharSequence: {}", byteBuf.getCharSequence(5, 10, CharsetUtil.UTF_8));
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章