亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

首頁 慕課教程 Netty 教程 Netty 教程 Netty ChannelHandler業務處理

Netty ChannelHandler 業務處理

1. 前言

本節,主要講解基于 ChannelHandler 去自定義專門處理業務邏輯的 Handler。使用 Netty 開發的客戶端和服務端之間通信,通信只是數據的傳輸,但是接受到數據如何去處理,此時就需要用到我們的自定義 Handler 去實現了。并且通常情況下,不同的業務需要對應不同的 Handler。

2. 自定義 Handler 步驟

如果是接受對方傳輸數據并且做處理,則繼承 ChannelInboundHandlerAdapter。

實例:

public class InboundHandler1 extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        super.channelRead(ctx, msg);
    }
}

如果是向對方寫數據時,則繼承 ChannelOutboundHandlerAdapter。

實例:

public class OutboundHandler1 extends ChannelOutboundHandlerAdapter {
    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        super.write(ctx, msg, promise);
    }
}

3. 核心方法

方法 說明
channelActive(ChannelHandlerContext ctx) 在客戶端連接建立成功之后被調用,并且只會調用一次。一般用來做一些初始化工作、登錄認證等。
channelInactive(ChannelHandlerContext ctx) 連接斷開時,觸發該事件,無論是客戶端主動斷開,還是服務端主動斷開,客戶端和服務端的該方法都會監聽到事件。
channelRead(ChannelHandlerContext ctx, Object msg) 當 channel 上面有數據到來時會觸發 channelRead 事件,當數據到來時,eventLoop 被喚醒繼而調用 channelRead 方法處理數據。
channelReadComplete(ChannelHandlerContext ctx) channelRead 期間做個判斷,read 到 0 個字節或者是 read 到的字節數小于 buffer 的容量,滿足以上條件就會調用 channelReadComplete 方法。
exceptionCaught(ChannelHandlerContext ctx, Throwable cause) 發生異常時,觸發該事件。

以上方法是自定義 Handler 重新最多的方法,其中 channelRead () 是重點掌握。

4. ChannelHandler 處理流程

圖片描述

5. 簡單業務開發

需求:實現客戶端向服務端發送登錄認證請求。

5.1 客戶端

客戶端實現的功能:在連接準備就緒時 channelActive () 發起登錄認證。

實例:

public class ClientLoginHandler extends ChannelInboundHandlerAdapter {
    //1.通道激活的時候,發送賬號、密碼
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        Map<String,String> map=new HashMap<String,String>();
        map.put("username","admin");
        map.put("password","1234567");

        //對象流序列化Map
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(os);
        oos.writeObject(map);
        byte[] bytes=os.toByteArray();

        //關閉流
        oos.close();
        os.close();
        
        //發送
        ctx.channel().writeAndFlush(Unpooled.copiedBuffer(bytes));
    }
}

代碼說明:

  1. channelActive 事件是通道建立時觸發該事件,并且僅觸發一次該事件,通常情況下,在 channelActive 里面實現登錄認證;
  2. 客戶端往服務端發送數據的時候需要使用對象流進行序列化,客戶端接收服務端響應信息的時候,需要通過對象流進行反序列化;
  3. Netty 底層是 ByteBuf 進行傳輸的(后面章節會詳細介紹),最終網絡底層傳輸則是 byte [],因此需要做序列化和反序列化操作。

5.2 服務端

服務端實現的功能:接受客戶端的請求數據,并且做賬戶和密碼的校驗。

實例:

public class ServerLoginHandler extends ChannelInboundHandlerAdapter {
    //1.讀取客戶端發送過來的數據
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //1.轉換ByteBuf
        ByteBuf buffer=(ByteBuf)msg;
        //2.定義一個byte數組,長度是ByteBuf的可讀字節數
        byte[] bytes=new byte[buffer.readableBytes()];
        //3.往自定義的byte[]讀取數據
        buffer.readBytes(bytes);

        //4.對象流反序列化
        ByteArrayInputStream is=new ByteArrayInputStream(bytes);
        ObjectInputStream iss=new ObjectInputStream(is);
        Map<String,String> map=(Map<String,String>)iss.readObject();
        
        //5.關閉流
        is.close();
        iss.close();
        
        //6.認證賬號、密碼,并且響應
        String username=map.get("username");
        String password=map.get("password");
        if(username.equals("admin")&&password.equals("123456")){
            ctx.channel().writeAndFlush(Unpooled.copiedBuffer("success".getBytes()));
        }else{
            ctx.channel().writeAndFlush(Unpooled.copiedBuffer("error".getBytes()));
            ctx.channel().closeFuture();
        }
    }
}

代碼說明:

  1. channelRead 方法,在客戶端有數據可讀取的時候會觸發該方法;
  2. 接受到的數據不可以直接使用,并且先轉換 ByteBuf,再轉換 byte [],最后通過對象流轉換成目標類型的數據;
  3. 解析出來的賬號、密碼,進行認證(這里是寫死,真實是連接數據庫進行校驗),把認證結果響應給客戶端。

6. 復雜業務開發

上面的登錄認證案例只是比較簡單的一個業務,真實項目當中,肯定是很多的業務組合而成的,那么如何基于 Netty 的 Handler 去實現呢?

6.1 共用一個 Handler

所有業務共用一個 Handler,由該 Handler 進行根據業務編碼作為路由標識進行業務分發。

方案優缺點:

  1. 優點: 思路簡單,適合業務不是很復雜的業務;
  2. 缺點: 如果業務很多的情況下,代碼會變的非常的臃腫,不太好管理。

實例:

public class LoginHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        Map<String,String> map=(Map<String,String>)msg;
        String code=map.get("code");
        if(code.equals("login")){
            //具體邏輯
        }else if(code.equals("getUserInfo")){
            //具體邏輯
        }else{
            //..............
        }
    }
}

6.2 多個 Handler 手工流轉

定義多個 Handler,每個 Handler 根據業務編碼來判斷是否需要處理,如果是則處理,否則繼續向下流轉。

方案優缺點:

  1. 優點: 把所有的業務解耦,不用所有業務都耦合在一起,使項目結構更清晰,更加容易維護;
  2. 缺點: 客戶端和服務端都有維護一份業務編碼,一旦編碼發生變更,則需要找到具體業務點去調整,相對比較麻煩。

實例:

public class LoginHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        Map<String,String> map=(Map<String,String>)msg;
        String code=map.get("code");
        if(code.equals("login")){
			//邏輯處理
            
        }else{
            //手工流轉下一個Handler
            super.channelRead(ctx, msg);
        }
    }
}

6.3 SimpleChannelInboundHandler

根據客戶端提交的參數類型,自動流轉到指定的 Handler 去處理。

方案優缺點:

  1. 優點: ①把業務解耦,每個業務對應獨立的 Handler;②不需要維護一份業務編碼;
  2. 缺點: 所有的封裝封裝都得對應一個實體,實體數量會比較多,但是嚴格意義來說,也不能說是缺點,現在基本上都是面向對象來進行編程。

這種模式在真實開發當中是使用最廣泛的。

實例:

public class FirstHandler extends SimpleChannelInboundHandler<User> {
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, User user) throws Exception {
        //處理業務邏輯
    }
}

7. 小結

本節內容,主要講解如何在真實項目當中去使用 Handler,需要掌握的知識點如下:

  1. 如何自定義一個 Handler;
  2. Handler 的幾個核心方法的使用;
  3. 如果復雜業務,三種業務邏輯處理方式以及優缺點。