目錄
1、概念
2、Netty提供的編解碼器類型
2.1 解碼器
2.1.1?ByteToMessageDecoder
2.1.2?ReplayingDecoder
2.1.3?MessageToMessageDecoder
2.2 編碼器
2.2.1?MessageToByteEncoder
2.2.2?MessageToMessageEncoder
2.3 編解碼器
2.3.1?ByteToMessageCodec
2.3.2?MessageToMessageCodec
2.3.3?CombinedChannelDuplexHandler
3、使用注意事項
4、示例:一個簡單的字符串編解碼器
1、概念
Netty 的編解碼器是其網絡編程框架中的核心組件,負責處理網絡通信中最基本也最關鍵的字節與消息對象之間的相互轉換。
核心概念:
-
解碼器:?將接收到的原始字節流?(
ByteBuf
) 轉換成有意義的應用程序級消息對象。這個過程發生在數據入站時(從網絡到應用)。 -
編碼器:?將應用程序級的消息對象轉換成字節流?(
ByteBuf
),以便通過網絡發送。這個過程發生在數據出站時(從應用到網絡)。 -
編解碼器:?同時實現了編碼器和解碼器功能的組件。
解決的問題:
-
解碼:?解決如何從連續的、可能被 TCP 粘包/拆包的字節流中,正確地切割、識別并還原出發送方發送的一個個完整消息。
-
編碼:?解決如何將結構化的消息對象高效地、符合協議規范地序列化成字節序列,以便發送。
2、Netty提供的編解碼器類型
2.1 解碼器
Netty 的解碼器都實現了?ChannelInboundHandler
?接口,處理入站數據。需要將解碼器放在ChannelPipeline中。常見的抽象基類和實現:
2.1.1?ByteToMessageDecoder
-
功能:?將入站的?
ByteBuf
?累積到內部的緩沖區中,直到有足夠的數據可以解碼成一個完整的消息對象。需要檢查緩沖區是否有足夠的字節。 -
關鍵方法:?
decode(ChannelHandlerContext ctx, ByteBuf in, List out)
-
in
: 當前累積的、可讀的輸入?ByteBuf
。 -
out
: 用于存放解碼成功的完整消息對象的列表。每添加一個對象,就表示成功解碼了一條消息。
-
-
職責:?開發者繼承此類,實現?
decode
?方法:-
檢查?
in
?中是否有足夠的數據構成一個完整消息。 -
如果足夠,從?
in
?中讀取數據,構造出消息對象。 -
將構造好的消息對象添加到?
out
?列表中。 -
重復步驟 1-3 直到?
in
?中不再有足夠的數據構成完整消息。
-
-
內部緩沖:?它會自動管理累積的字節,處理粘包問題。當?
in
?被讀取后,已讀取的字節會被丟棄,保留未讀取的字節供下次解碼。 -
示例:?
LineBasedFrameDecoder
?(基于換行符拆包),?FixedLengthFrameDecoder
?(固定長度拆包),?LengthFieldBasedFrameDecoder
?(基于長度字段拆包 -?極其重要和常用)。
2.1.2?ReplayingDecoder
-
特點:?簡化了解碼邏輯。它假設在調用?
decode
?方法時,ByteBuf
?in
?中總是有足夠的數據可供讀取下一個需要的字段。如果數據不足,它會拋出特殊異常(由框架捕獲),并等待更多數據到來后重試解碼。 -
優點:?代碼更簡潔,不需要手動檢查每個字段是否有足夠數據。
-
缺點:?性能可能略低于?
ByteToMessageDecoder
?(因為需要異常機制),且?in
?的某些操作受限(如?readableBytes()
),不是所有的ByteBuff都支持。 -
適用:?協議簡單或對極致性能要求不苛刻的場景。
2.1.3?MessageToMessageDecoder
-
核心:?將一種消息對象解碼成另一種消息對象。
-
功能:?用于消息處理管道中的消息轉換階段。輸入和輸出都是對象,不再是原始的?
ByteBuf
。 -
關鍵方法:?
decode(ChannelHandlerContext ctx, I msg, List out)
-
msg
: 輸入的上一個 Handler 傳遞下來的消息對象(類型?I
)。 -
out
: 存放轉換后的消息對象(類型?O
)的列表。
-
-
示例:?將?
ByteBuf
?解碼成的?String
?對象再解碼成自定義的?LoginRequest
?對象;將 Protobuf 的?ByteBuf
?解碼成生成的?MessageLite
?對象。
2.2 編碼器
Netty 的編碼器都實現了?ChannelOutboundHandler
?接口,處理出站數據。常見的抽象基類和實現:
2.2.1?MessageToByteEncoder
-
功能:?將應用程序的消息對象編碼成字節流?(
ByteBuf
)。 -
關鍵方法:?
encode(ChannelHandlerContext ctx, I msg, ByteBuf out)
-
msg
: 要編碼的消息對象(類型?I
)。 -
out
: 用于寫入編碼后字節的目標?ByteBuf
。
-
-
職責:?開發者繼承此類,實現?
encode
?方法:-
將?
msg
?對象包含的信息(如字段值)按照協議規范寫入到?out
?ByteBuf
?中。 -
通常需要處理長度字段(如果需要)、序列化等。
-
-
示例:?將?
String
?編碼成?ByteBuf
?(通常使用?ctx.alloc().buffer()
?創建并寫入字節),將自定義的?LoginResponse
?對象編碼成協議規定的字節格式。
2.2.2?MessageToMessageEncoder
-
核心:?將一種消息對象編碼成另一種消息對象。
-
功能:?用于消息處理管道中的消息轉換階段,輸出對象通常最終會被下游的?
MessageToByteEncoder
?處理。 -
關鍵方法:?
encode(ChannelHandlerContext ctx, I msg, List out)
-
msg
: 要轉換的消息對象(類型?I
)。 -
out
: 存放轉換后的消息對象(類型?O
)的列表。
-
-
示例:?將自定義的?
LoginResponse
?對象轉換成 Protobuf 生成的?MessageLite
?對象;將?String
?轉換成包含該字符串的?TextWebSocketFrame
。
2.3 編解碼器
編碼解碼器: 同時具有編碼與解碼功能,特點同時實現了ChannelInboundHandler和ChannelOutboundHandler接口,因此在數據輸入和輸出時都能進行處理。Netty提供提供了一個ChannelDuplexHandler適配器類,編碼解碼器的抽象基類。
2.3.1?ByteToMessageCodec
結合了?ByteToMessageDecoder
?和?MessageToByteEncoder
。
2.3.2?MessageToMessageCodec
結合了?MessageToMessageDecoder
?和?MessageToMessageEncoder
。
2.3.3?CombinedChannelDuplexHandler
一個更靈活的通用組合方式,允許你將任意一個解碼器 (ChannelInboundHandler
) 和任意一個編碼器 (ChannelOutboundHandler
) 組合在一起,作為一個編解碼器添加到 pipeline 中。這是推薦的方式來組合獨立的編解碼邏輯,避免多重繼承可能帶來的問題。
public class MyCodec extends CombinedChannelDuplexHandler<MyDecoder, MyEncoder> {public MyCodec() {super(new MyDecoder(), new MyEncoder());}
}
3、使用注意事項
-
處理 TCP 粘包/拆包:?這是解碼器的核心職責之一。務必使用?
ByteToMessageDecoder
?或其子類(如?LengthFieldBasedFrameDecoder
)來自動累積和拆包。不要試圖在?channelRead
?中直接處理原始?ByteBuf
?來解析完整消息。 -
使用 LengthFieldBasedFrameDecoder:?對于自定義二進制協議,這是最常用、最可靠的拆包解碼器。它通過協議頭中明確指定的長度字段來確定一個完整幀的邊界,完美解決粘包拆包問題。
-
資源管理:?
ByteBuf
?是 Netty 管理的內存。在編碼器?encode
?方法中創建的?ByteBuf
,Netty 框架會在寫入完成后負責釋放。在解碼器?decode
?方法中,不要釋放傳入的?ByteBuf
?in
,ByteToMessageDecoder
?會自動管理其生命周期。處理?out
?列表中的對象時,確保它們被正確傳遞下去或被釋放(如果需要)。遵循 Netty 的引用計數規則。 -
可重用性:?編解碼器通常是線程安全的和無狀態的,可以被多個 Channel 共享(添加時使用?
@Sharable
?注解并確保確實無狀態)。 -
Pipeline 順序:
-
解碼器 (
ChannelInboundHandler
) 應該添加在靠近?SocketChannel
?的一端(Pipeline 的前端)。 -
編碼器 (
ChannelOutboundHandler
) 應該添加在靠近業務邏輯 Handler 的一端(Pipeline 的后端)。 -
一個典型的 Pipeline 順序可能是:
LengthFieldBasedFrameDecoder
?->?MyProtocolDecoder
?(ByteToMessageDecoder
) ->?MyBusinessLogicHandler
?->?MyProtocolEncoder
?(MessageToByteEncoder
) ->?...
?(其他出站處理器如日志、加密)。
-
-
利用現有實現:?優先使用 Netty 內置的編解碼器(如 HTTP、WebSocket、Protobuf、Marshalling、Base64 等),它們經過充分測試和優化。
-
性能:?編解碼通常是性能關鍵路徑。避免在編解碼方法中進行阻塞操作或創建過多臨時對象。考慮使用對象池(如 Recycler)復用消息對象。
4、示例:一個簡單的字符串編解碼器
// 編碼器 (出站:Object -> ByteBuf)
@Sharable
public class StringEncoder extends MessageToByteEncoder<String> {@Overrideprotected void encode(ChannelHandlerContext ctx, String msg, ByteBuf out) throws Exception {// 將字符串寫入ByteBuf (使用UTF-8編碼)out.writeCharSequence(msg, StandardCharsets.UTF_8);}
}// 解碼器 (入站:ByteBuf -> String)
@Sharable
public class StringDecoder extends ByteToMessageDecoder {@Overrideprotected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {// 簡單示例:假設每個ByteBuf都包含一個完整的UTF-8字符串// 實際中需要處理粘包拆包!這里僅作演示。out.add(in.toString(StandardCharsets.UTF_8));in.readerIndex(in.writerIndex()); // 標記所有字節已讀 (僅用于此簡單示例)}
}// 或者使用更健壯的 LineBasedFrameDecoder + StringDecoder 組合來處理行分隔的消息
public class ServerInitializer extends ChannelInitializer<SocketChannel> {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ChannelPipeline p = ch.pipeline();// 入站 Pipeline (從網絡到應用)p.addLast(new LineBasedFrameDecoder(1024)); // 按行拆包,最大行長度1024p.addLast(new StringDecoder(StandardCharsets.UTF_8)); // 將一行字節轉成Stringp.addLast(new MyStringHandler()); // 業務邏輯處理器,處理String對象// 出站 Pipeline (從應用到網絡)p.addLast(new StringEncoder(StandardCharsets.UTF_8)); // 將String轉成字節}
}