Netty ChannelHandler 業(yè)務(wù)處理
1. 前言
本節(jié),主要講解基于 ChannelHandler 去自定義專門處理業(yè)務(wù)邏輯的 Handler。使用 Netty 開(kāi)發(fā)的客戶端和服務(wù)端之間通信,通信只是數(shù)據(jù)的傳輸,但是接受到數(shù)據(jù)如何去處理,此時(shí)就需要用到我們的自定義 Handler 去實(shí)現(xiàn)了。并且通常情況下,不同的業(yè)務(wù)需要對(duì)應(yīng)不同的 Handler。
2. 自定義 Handler 步驟
如果是接受對(duì)方傳輸數(shù)據(jù)并且做處理,則繼承 ChannelInboundHandlerAdapter
。
實(shí)例:
public class InboundHandler1 extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
super.channelRead(ctx, msg);
}
}
如果是向?qū)Ψ綄憯?shù)據(jù)時(shí),則繼承 ChannelOutboundHandlerAdapter
。
實(shí)例:
public class OutboundHandler1 extends ChannelOutboundHandlerAdapter {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
super.write(ctx, msg, promise);
}
}
3. 核心方法
方法 | 說(shuō)明 |
---|---|
channelActive(ChannelHandlerContext ctx) | 在客戶端連接建立成功之后被調(diào)用,并且只會(huì)調(diào)用一次。一般用來(lái)做一些初始化工作、登錄認(rèn)證等。 |
channelInactive(ChannelHandlerContext ctx) | 連接斷開(kāi)時(shí),觸發(fā)該事件,無(wú)論是客戶端主動(dòng)斷開(kāi),還是服務(wù)端主動(dòng)斷開(kāi),客戶端和服務(wù)端的該方法都會(huì)監(jiān)聽(tīng)到事件。 |
channelRead(ChannelHandlerContext ctx, Object msg) | 當(dāng) channel 上面有數(shù)據(jù)到來(lái)時(shí)會(huì)觸發(fā) channelRead 事件,當(dāng)數(shù)據(jù)到來(lái)時(shí),eventLoop 被喚醒繼而調(diào)用 channelRead 方法處理數(shù)據(jù)。 |
channelReadComplete(ChannelHandlerContext ctx) | channelRead 期間做個(gè)判斷,read 到 0 個(gè)字節(jié)或者是 read 到的字節(jié)數(shù)小于 buffer 的容量,滿足以上條件就會(huì)調(diào)用 channelReadComplete 方法。 |
exceptionCaught(ChannelHandlerContext ctx, Throwable cause) | 發(fā)生異常時(shí),觸發(fā)該事件。 |
以上方法是自定義 Handler 重新最多的方法,其中 channelRead () 是重點(diǎn)掌握。
4. ChannelHandler 處理流程
5. 簡(jiǎn)單業(yè)務(wù)開(kāi)發(fā)
需求:實(shí)現(xiàn)客戶端向服務(wù)端發(fā)送登錄認(rèn)證請(qǐng)求。
5.1 客戶端
客戶端實(shí)現(xiàn)的功能:在連接準(zhǔn)備就緒時(shí) channelActive () 發(fā)起登錄認(rèn)證。
實(shí)例:
public class ClientLoginHandler extends ChannelInboundHandlerAdapter {
//1.通道激活的時(shí)候,發(fā)送賬號(hào)、密碼
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
Map<String,String> map=new HashMap<String,String>();
map.put("username","admin");
map.put("password","1234567");
//對(duì)象流序列化Map
ByteArrayOutputStream os = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(map);
byte[] bytes=os.toByteArray();
//關(guān)閉流
oos.close();
os.close();
//發(fā)送
ctx.channel().writeAndFlush(Unpooled.copiedBuffer(bytes));
}
}
代碼說(shuō)明:
- channelActive 事件是通道建立時(shí)觸發(fā)該事件,并且僅觸發(fā)一次該事件,通常情況下,在 channelActive 里面實(shí)現(xiàn)登錄認(rèn)證;
- 客戶端往服務(wù)端發(fā)送數(shù)據(jù)的時(shí)候需要使用對(duì)象流進(jìn)行序列化,客戶端接收服務(wù)端響應(yīng)信息的時(shí)候,需要通過(guò)對(duì)象流進(jìn)行反序列化;
- Netty 底層是 ByteBuf 進(jìn)行傳輸?shù)模ê竺嬲鹿?jié)會(huì)詳細(xì)介紹),最終網(wǎng)絡(luò)底層傳輸則是 byte [],因此需要做序列化和反序列化操作。
5.2 服務(wù)端
服務(wù)端實(shí)現(xiàn)的功能:接受客戶端的請(qǐng)求數(shù)據(jù),并且做賬戶和密碼的校驗(yàn)。
實(shí)例:
public class ServerLoginHandler extends ChannelInboundHandlerAdapter {
//1.讀取客戶端發(fā)送過(guò)來(lái)的數(shù)據(jù)
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//1.轉(zhuǎn)換ByteBuf
ByteBuf buffer=(ByteBuf)msg;
//2.定義一個(gè)byte數(shù)組,長(zhǎng)度是ByteBuf的可讀字節(jié)數(shù)
byte[] bytes=new byte[buffer.readableBytes()];
//3.往自定義的byte[]讀取數(shù)據(jù)
buffer.readBytes(bytes);
//4.對(duì)象流反序列化
ByteArrayInputStream is=new ByteArrayInputStream(bytes);
ObjectInputStream iss=new ObjectInputStream(is);
Map<String,String> map=(Map<String,String>)iss.readObject();
//5.關(guān)閉流
is.close();
iss.close();
//6.認(rèn)證賬號(hào)、密碼,并且響應(yīng)
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();
}
}
}
代碼說(shuō)明:
- channelRead 方法,在客戶端有數(shù)據(jù)可讀取的時(shí)候會(huì)觸發(fā)該方法;
- 接受到的數(shù)據(jù)不可以直接使用,并且先轉(zhuǎn)換 ByteBuf,再轉(zhuǎn)換 byte [],最后通過(guò)對(duì)象流轉(zhuǎn)換成目標(biāo)類型的數(shù)據(jù);
- 解析出來(lái)的賬號(hào)、密碼,進(jìn)行認(rèn)證(這里是寫死,真實(shí)是連接數(shù)據(jù)庫(kù)進(jìn)行校驗(yàn)),把認(rèn)證結(jié)果響應(yīng)給客戶端。
6. 復(fù)雜業(yè)務(wù)開(kāi)發(fā)
上面的登錄認(rèn)證案例只是比較簡(jiǎn)單的一個(gè)業(yè)務(wù),真實(shí)項(xiàng)目當(dāng)中,肯定是很多的業(yè)務(wù)組合而成的,那么如何基于 Netty 的 Handler 去實(shí)現(xiàn)呢?
6.1 共用一個(gè) Handler
所有業(yè)務(wù)共用一個(gè) Handler,由該 Handler 進(jìn)行根據(jù)業(yè)務(wù)編碼作為路由標(biāo)識(shí)進(jìn)行業(yè)務(wù)分發(fā)。
方案優(yōu)缺點(diǎn):
- 優(yōu)點(diǎn): 思路簡(jiǎn)單,適合業(yè)務(wù)不是很復(fù)雜的業(yè)務(wù);
- 缺點(diǎn): 如果業(yè)務(wù)很多的情況下,代碼會(huì)變的非常的臃腫,不太好管理。
實(shí)例:
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 多個(gè) Handler 手工流轉(zhuǎn)
定義多個(gè) Handler,每個(gè) Handler 根據(jù)業(yè)務(wù)編碼來(lái)判斷是否需要處理,如果是則處理,否則繼續(xù)向下流轉(zhuǎn)。
方案優(yōu)缺點(diǎn):
- 優(yōu)點(diǎn): 把所有的業(yè)務(wù)解耦,不用所有業(yè)務(wù)都耦合在一起,使項(xiàng)目結(jié)構(gòu)更清晰,更加容易維護(hù);
- 缺點(diǎn): 客戶端和服務(wù)端都有維護(hù)一份業(yè)務(wù)編碼,一旦編碼發(fā)生變更,則需要找到具體業(yè)務(wù)點(diǎn)去調(diào)整,相對(duì)比較麻煩。
實(shí)例:
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{
//手工流轉(zhuǎn)下一個(gè)Handler
super.channelRead(ctx, msg);
}
}
}
6.3 SimpleChannelInboundHandler
根據(jù)客戶端提交的參數(shù)類型,自動(dòng)流轉(zhuǎn)到指定的 Handler 去處理。
方案優(yōu)缺點(diǎn):
- 優(yōu)點(diǎn): ①把業(yè)務(wù)解耦,每個(gè)業(yè)務(wù)對(duì)應(yīng)獨(dú)立的 Handler;②不需要維護(hù)一份業(yè)務(wù)編碼;
- 缺點(diǎn): 所有的封裝封裝都得對(duì)應(yīng)一個(gè)實(shí)體,實(shí)體數(shù)量會(huì)比較多,但是嚴(yán)格意義來(lái)說(shuō),也不能說(shuō)是缺點(diǎn),現(xiàn)在基本上都是面向?qū)ο髞?lái)進(jìn)行編程。
這種模式在真實(shí)開(kāi)發(fā)當(dāng)中是使用最廣泛的。
實(shí)例:
public class FirstHandler extends SimpleChannelInboundHandler<User> {
protected void channelRead0(ChannelHandlerContext channelHandlerContext, User user) throws Exception {
//處理業(yè)務(wù)邏輯
}
}
7. 小結(jié)
本節(jié)內(nèi)容,主要講解如何在真實(shí)項(xiàng)目當(dāng)中去使用 Handler,需要掌握的知識(shí)點(diǎn)如下:
- 如何自定義一個(gè) Handler;
- Handler 的幾個(gè)核心方法的使用;
- 如果復(fù)雜業(yè)務(wù),三種業(yè)務(wù)邏輯處理方式以及優(yōu)缺點(diǎn)。