Netty如何自定義編碼解碼器
Netty 自定義編碼解碼器
入棧:InboundHandler ,出棧:OutboundHandler。Netty 構(gòu)建 chain 來(lái)處理業(yè)務(wù)。
自定義一個(gè)解碼器
解碼器主要是對(duì)客戶(hù)端發(fā)送的消息進(jìn)行解碼處理,所以他是一個(gè)入棧的處理器,因此他會(huì)有一個(gè)入棧的標(biāo)識(shí)ibound=true;
解碼器一般我都們都會(huì)基層 Netty 提供給的一個(gè)實(shí)現(xiàn)類(lèi)來(lái)實(shí)現(xiàn)自己的解碼邏輯 -> io.netty.handler.codec.ByteToMessageDecoder 這就是解碼的抽象類(lèi),默認(rèn)我們要實(shí)現(xiàn)一個(gè)抽象方法
com.netty.codec.custom.InboundAndOutboundHandler#decode
這個(gè)方法有大概三個(gè)參數(shù);
ChannelHandlerContext channelHandlerContext這個(gè)是上下文信息,可以獲取通道、管道等信息。ByteBuf byteBuf客戶(hù)端發(fā)送的消息就是存在這個(gè)參數(shù)的對(duì)象里面我們要通過(guò)這個(gè)對(duì)象的read***方法讀取我們需要的數(shù)據(jù)類(lèi)型,可以是Long,Byte等類(lèi)型的數(shù)據(jù)然后我們可以就可以轉(zhuǎn)換成為我們需要的格式。List<Object> list集合,將解碼后的數(shù)據(jù)傳遞給下一個(gè)inboundHandler處理類(lèi)。
代碼示例
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) {
// Long => 8 byte
if (byteBuf.readableBytes() >= 8) {
list.add(byteBuf.readLong());
} else log.warn("字節(jié)數(shù)異常 => {}", byteBuf.readableBytes());
}這樣我們就實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的解碼器。
自定義一個(gè)編碼器
解碼器主要是對(duì)服務(wù)端將要發(fā)送給客戶(hù)端的消息進(jìn)行編碼處理,所以他是一個(gè)出棧的處理器,因此他會(huì)有一個(gè)入棧的標(biāo)識(shí)outbound=true;
使用 Netty 提供的抽象類(lèi) => io.netty.handler.codec.MessageToByteEncoder<T> 泛型 T 表示你要發(fā)送的消息的類(lèi)型,實(shí)現(xiàn)抽象方法 => com.netty.codec.custom.OutboundHandler#encode 方法的參數(shù)有三個(gè):
ChannelHandlerContext channelHandlerContext這個(gè)是上下文信息,可以獲取通道、管道等信息。Long msg服務(wù)端要發(fā)送給客戶(hù)端的消息ByteBuf byteBuf
代碼示例
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, Long msg, ByteBuf byteBuf) {
byteBuf.writeLong(msg);
log.info("發(fā)送消息成功");
}后續(xù) -> io.netty.handler.codec.ReplayingDecoder
ByteToMessage 其實(shí)在使用過(guò)程中會(huì)遇到一些問(wèn)題,例如:
當(dāng)我們的解碼器中想要將字節(jié)轉(zhuǎn)換為一個(gè) Integer ,我們知道 Integer 是四個(gè)字節(jié)的,但是如果在讀取的時(shí)候不夠四個(gè)字節(jié),這個(gè)時(shí)候我們就需要做一些判斷邏輯 => if (byteBuf.readableBytes() >= 4) 當(dāng)這個(gè)返回值為 true 的時(shí)候我么就可以繼續(xù)執(zhí)行解碼的邏輯。
那我們?cè)趺纯梢蕴^(guò)這一步不判斷直接進(jìn)行我們的轉(zhuǎn)換邏輯呢?
這個(gè)時(shí)候就可以使用 Netty 的
io.netty.handler.codec.ReplayingDecoder
可以不用判斷可讀字節(jié)數(shù)的原理:
ReplayingDecoder 是 ByteToMessage 的子類(lèi),源碼如下:
public abstract class ReplayingDecoder<S> extends ByteToMessageDecoder {
...
}ReplayingDecoder 的秘密就是對(duì) ByteToMessage 的 CallDecode(...) 方法的重寫(xiě),觀摩一下具體實(shí)現(xiàn):
@Override
protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
replayable.setCumulation(in);
try {
while (in.isReadable()) {
int oldReaderIndex = checkpoint = in.readerIndex();
int outSize = out.size();
if (outSize > 0) {
fireChannelRead(ctx, out, outSize);
out.clear();
// Check if this handler was removed before continuing with decoding.
// If it was removed, it is not safe to continue to operate on the buffer.
//
// See:
// - https://github.com/netty/netty/issues/4635
if (ctx.isRemoved()) {
break;
}
outSize = 0;
}
S oldState = state;
int oldInputLength = in.readableBytes();
try {
decodeRemovalReentryProtection(ctx, replayable, out);
// Check if this handler was removed before continuing the loop.
// If it was removed, it is not safe to continue to operate on the buffer.
//
// See https://github.com/netty/netty/issues/1664
if (ctx.isRemoved()) {
break;
}
if (outSize == out.size()) {
if (oldInputLength == in.readableBytes() && oldState == state) {
throw new DecoderException(
StringUtil.simpleClassName(getClass()) + ".decode() must consume the inbound " +
"data or change its state if it did not decode anything.");
} else {
// Previous data has been discarded or caused state transition.
// Probably it is reading on.
continue;
}
}
} catch (Signal replay) {
replay.expect(REPLAY);
// Check if this handler was removed before continuing the loop.
// If it was removed, it is not safe to continue to operate on the buffer.
//
// See https://github.com/netty/netty/issues/1664
if (ctx.isRemoved()) {
break;
}
// Return to the checkpoint (or oldPosition) and retry.
int checkpoint = this.checkpoint;
if (checkpoint >= 0) {
in.readerIndex(checkpoint);
} else {
// Called by cleanup() - no need to maintain the readerIndex
// anymore because the buffer has been released already.
}
break;
}
if (oldReaderIndex == in.readerIndex() && oldState == state) {
throw new DecoderException(
StringUtil.simpleClassName(getClass()) + ".decode() method must consume the inbound data " +
"or change its state if it decoded something.");
}
if (isSingleDecode()) {
break;
}
}
} catch (DecoderException e) {
throw e;
} catch (Exception cause) {
throw new DecoderException(cause);
}
}實(shí)現(xiàn)不需要判斷的邏輯就是因?yàn)?/p>
int oldReaderIndex = checkpoint = in.readerIndex();
如果在執(zhí)行過(guò)程中出現(xiàn)異常就會(huì)在代碼中重置 index
總結(jié)
雖然 ReplayingDecoder節(jié)約了判斷的邏輯,但是從他的代碼實(shí)現(xiàn)邏輯看到是通過(guò)拋出異常來(lái)不斷的重試,所以在某些特殊的情況下會(huì)造成性能的下降。所以還是再選擇的時(shí)候要根據(jù)自己的實(shí)際需求來(lái)判斷是使用 ByteToMessage 還是使用 ReplayingDecoder。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
- 解決gateway報(bào)netty堆外內(nèi)存溢出io.netty.util.internal.OutOfDirectMemoryError
- springboot接入netty實(shí)現(xiàn)在線(xiàn)統(tǒng)計(jì)人數(shù)
- 基于Netty實(shí)現(xiàn)WebSocket的常用處理器及區(qū)別解析
- 利用Netty+SpringBoot實(shí)現(xiàn)定時(shí)后端向前端推送數(shù)據(jù)
- springboot整合netty實(shí)現(xiàn)心跳檢測(cè)和自動(dòng)重連
- SpringCloud整合Netty集群實(shí)現(xiàn)WebSocket的示例代碼
- io.netty項(xiàng)目UDP實(shí)現(xiàn)方式
相關(guān)文章
Spring實(shí)現(xiàn)內(nèi)置監(jiān)聽(tīng)器
這篇文章主要介紹了Spring 實(shí)現(xiàn)自定義監(jiān)聽(tīng)器案例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧,希望能給你帶來(lái)幫助2021-07-07
Java String.join()從入門(mén)到高階架構(gòu)實(shí)踐
本文詳細(xì)介紹了Java 8中引入的String.join()方法,探討了其設(shè)計(jì)哲學(xué)、核心技術(shù)原理、高級(jí)用法、架構(gòu)模式、性能優(yōu)化以及實(shí)際應(yīng)用場(chǎng)景,String.join()不僅簡(jiǎn)化了字符串拼接操作,還提供了更好的性能和可維護(hù)性,感興趣的朋友跟隨小編一起看看吧2026-01-01
springsecurity第三方授權(quán)認(rèn)證的項(xiàng)目實(shí)踐
Spring security 是一個(gè)強(qiáng)大的和高度可定制的身份驗(yàn)證和訪(fǎng)問(wèn)控制框架,本文主要介紹了springsecurity第三方授權(quán)認(rèn)證的項(xiàng)目實(shí)踐,具有一定的參考價(jià)值,感興趣可以了解一下2023-08-08
java中調(diào)用GDAL DLL的實(shí)現(xiàn)方法
本篇文章是對(duì)java中調(diào)用GDAL DLL的實(shí)現(xiàn)方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05
SpringBoot訪(fǎng)問(wèn)windows共享文件的方法
這篇文章主要介紹了SpringBoot訪(fǎng)問(wèn)windows共享文件,項(xiàng)目使用minio存儲(chǔ)且不在同一臺(tái)服務(wù)器上,為了優(yōu)化速度決定使用windows共享功能進(jìn)行文件傳輸,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-02-02
Mybatis中的PageHelper的執(zhí)行流程分析
這篇文章主要介紹了Mybatis的PageHelper執(zhí)行流程,本文給大家介紹介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-02-02
Java語(yǔ)言實(shí)現(xiàn)對(duì)MySql數(shù)據(jù)庫(kù)中數(shù)據(jù)的增刪改查操作的代碼
這篇文章主要介紹了Java語(yǔ)言實(shí)現(xiàn)對(duì)MySql數(shù)據(jù)庫(kù)中數(shù)據(jù)的增刪改查操作的代碼,實(shí)現(xiàn)了連接數(shù)據(jù)庫(kù),和數(shù)據(jù)庫(kù)的增刪改查操作,有興趣的可以了解一下。2016-12-12
Java動(dòng)態(tài)導(dǎo)出Word登記表的完整方案
本文詳細(xì)講解如何使用 Java動(dòng)態(tài)導(dǎo)出包含多人員報(bào)名表的Word文檔,每人占據(jù)獨(dú)立一頁(yè),并支持動(dòng)態(tài)表格行,我們對(duì)比了多種實(shí)現(xiàn)方案,最終推薦基于 Freemarker + XML模板或docx4j的靈活方式,并提供完整代碼示例與最佳實(shí)踐,助你高效實(shí)現(xiàn)復(fù)雜Word導(dǎo)出需求2025-07-07

