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));
}
}
代碼說明:
- channelActive 事件是通道建立時觸發該事件,并且僅觸發一次該事件,通常情況下,在 channelActive 里面實現登錄認證;
- 客戶端往服務端發送數據的時候需要使用對象流進行序列化,客戶端接收服務端響應信息的時候,需要通過對象流進行反序列化;
- 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();
}
}
}
代碼說明:
- channelRead 方法,在客戶端有數據可讀取的時候會觸發該方法;
- 接受到的數據不可以直接使用,并且先轉換 ByteBuf,再轉換 byte [],最后通過對象流轉換成目標類型的數據;
- 解析出來的賬號、密碼,進行認證(這里是寫死,真實是連接數據庫進行校驗),把認證結果響應給客戶端。
6. 復雜業務開發
上面的登錄認證案例只是比較簡單的一個業務,真實項目當中,肯定是很多的業務組合而成的,那么如何基于 Netty 的 Handler 去實現呢?
6.1 共用一個 Handler
所有業務共用一個 Handler,由該 Handler 進行根據業務編碼作為路由標識進行業務分發。
方案優缺點:
- 優點: 思路簡單,適合業務不是很復雜的業務;
- 缺點: 如果業務很多的情況下,代碼會變的非常的臃腫,不太好管理。
實例:
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 根據業務編碼來判斷是否需要處理,如果是則處理,否則繼續向下流轉。
方案優缺點:
- 優點: 把所有的業務解耦,不用所有業務都耦合在一起,使項目結構更清晰,更加容易維護;
- 缺點: 客戶端和服務端都有維護一份業務編碼,一旦編碼發生變更,則需要找到具體業務點去調整,相對比較麻煩。
實例:
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 去處理。
方案優缺點:
- 優點: ①把業務解耦,每個業務對應獨立的 Handler;②不需要維護一份業務編碼;
- 缺點: 所有的封裝封裝都得對應一個實體,實體數量會比較多,但是嚴格意義來說,也不能說是缺點,現在基本上都是面向對象來進行編程。
這種模式在真實開發當中是使用最廣泛的。
實例:
public class FirstHandler extends SimpleChannelInboundHandler<User> {
protected void channelRead0(ChannelHandlerContext channelHandlerContext, User user) throws Exception {
//處理業務邏輯
}
}
7. 小結
本節內容,主要講解如何在真實項目當中去使用 Handler,需要掌握的知識點如下:
- 如何自定義一個 Handler;
- Handler 的幾個核心方法的使用;
- 如果復雜業務,三種業務邏輯處理方式以及優缺點。