日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

你了解Netty的编解码器吗?史上最通俗易懂的Netty解码器应用案例带你解开Netty解码器的神秘面纱

發(fā)布時(shí)間:2023/12/29 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 你了解Netty的编解码器吗?史上最通俗易懂的Netty解码器应用案例带你解开Netty解码器的神秘面纱 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

Netty解碼器也是非常重要的一個(gè)模塊, 服務(wù)端接收到客戶端發(fā)送過來的消息, 準(zhǔn)確說是字節(jié)數(shù)組, Netty底層已經(jīng)將它們讀取成ByteBuf了, 但是這些ByteBuf是沒有任何含義的,需要我們根據(jù)業(yè)務(wù)來對(duì)字節(jié)數(shù)組進(jìn)行解碼。本文中我們將介紹Netty中常見的兩種解碼器DelimiterBasedFrameDecoder和FixedLengthFrameDecoder。

Netty解碼器

  • 1. 前言
    • 1.1 Netty解碼器
    • 1.2 ByteToMessageDecoder
  • 2. DelimiterBasedFrameDecoder解碼器應(yīng)用
    • 2.1 服務(wù)端代碼
    • 2.2 客戶端代碼
    • 2.3 運(yùn)行結(jié)果
  • 3. FixedLengthFrameDecoder解碼器應(yīng)用
    • 3.1 服務(wù)端代碼
    • 3.2 telnet客戶端測(cè)試

1. 前言

TCP以流的方式進(jìn)行傳輸數(shù)據(jù),上層的應(yīng)用協(xié)議為了對(duì)消息進(jìn)行區(qū)分,通常會(huì)采用以下4種方式:

  • 消息長(zhǎng)度固定:累計(jì)讀取到長(zhǎng)度總和為定長(zhǎng)LEN的報(bào)文后,就認(rèn)為讀取到了一個(gè)完整的消息;然后會(huì)將計(jì)數(shù)器重置,重新開始讀取下一個(gè)數(shù)據(jù)報(bào);
  • 回車換行符作為消息結(jié)束符:以回車換行符來標(biāo)記一個(gè)數(shù)據(jù)報(bào)j結(jié)束;
  • 特殊字符標(biāo)識(shí):自定義一個(gè)特殊字符,來作為數(shù)據(jù)報(bào)結(jié)束的標(biāo)識(shí);
  • 消息頭定義:通過在消息頭種定義長(zhǎng)度字段來標(biāo)識(shí)消息的總長(zhǎng)度。

Netty對(duì)上述4種應(yīng)用做了統(tǒng)一的抽象,提供了4種解碼器來解決相應(yīng)的問題。在本文中我們將詳細(xì)介紹DelimiterBasedFrameDecoder和FixedLengthFrameDecoder兩種解碼器,兩者分別可以完成上述第3和第1種功能。

1.1 Netty解碼器

在對(duì)這兩種解碼器介紹之前,我們簡(jiǎn)單了解一下Netty解碼器的工作原理。下面首先給出一個(gè)簡(jiǎn)單的示例:

上述圖片種展示是一個(gè)ToIntegerDecoder解碼器的工作過程,從字面上我們可以了解,該解碼器是將一個(gè)字節(jié)數(shù)組轉(zhuǎn)化為Integer類型數(shù)據(jù)。decoder 負(fù)責(zé)將“入站”數(shù)據(jù)從一種格式轉(zhuǎn)換到另一種格式,Netty的解碼器是一種 ChannelInboundHandler 的抽象實(shí)現(xiàn)。實(shí)踐中使用解碼器很簡(jiǎn)單,就是將入站數(shù)據(jù)轉(zhuǎn)換格式后傳遞到 ChannelPipeline 中的下一個(gè)ChannelInboundHandler 進(jìn)行處理;這樣的處理是很靈活的,我們可以將解碼器放在 ChannelPipeline 中,重用邏輯。

Netty 提供了豐富的解碼器抽象基類,我們可以很容易的實(shí)現(xiàn)這些基類來自定義解碼器。主要分兩類:

  • 解碼字節(jié)到消息(ByteToMessageDecoder 和 ReplayingDecoder)
  • 解碼消息到消息(MessageToMessageDecoder)

由于常用的幾種解碼器都是解碼字節(jié)到消息,那么下面簡(jiǎn)單了解ByteToMessageDecoder。

1.2 ByteToMessageDecoder

ByteToMessageDecoder 是用于將字節(jié)轉(zhuǎn)為消息(或其他字節(jié)序列)。你不能確定遠(yuǎn)端是否會(huì)一次發(fā)送完一個(gè)完整的“信息”,因此這個(gè)類會(huì)緩存入站的數(shù)據(jù),直到準(zhǔn)備好了用于處理。ByteToMessageDecoder抽象類中有兩個(gè)最重要的方法,如下表所示:

方法名稱描述
Decode需要實(shí)現(xiàn)的唯一抽象方法。 通過具有輸入字節(jié)的ByteBuf和添加了已解碼消息的List來調(diào)用它。 反復(fù)調(diào)用decode(),直到列表返回時(shí)為空。 然后將List的內(nèi)容傳遞到管道中的下一個(gè)處理程序。
decodeLast所提供的默認(rèn)實(shí)現(xiàn)只調(diào)用了decode()。當(dāng)Channel變?yōu)榉腔顒?dòng)狀態(tài)時(shí),此方法被調(diào)用一次。

下面我們依舊使用上面的ToIntegerDecoder作為示例。假設(shè)我們接收一個(gè)包含簡(jiǎn)單整數(shù)的字節(jié)流,每個(gè)都單獨(dú)處理。在本例中,我們將從入站 ByteBuf 讀取每個(gè)整數(shù)并將其傳遞給 pipeline 中的下一個(gè)ChannelInboundHandler。“解碼”字節(jié)流成整數(shù)我們將擴(kuò)展ByteToMessageDecoder,實(shí)現(xiàn)類為“ToIntegerDecoder”,如下圖所示。

每次從入站的 ByteBuf 讀取四個(gè)字節(jié),解碼成整形,并添加到一個(gè) List (本例是指 Integer),當(dāng)不能再添加數(shù)據(jù)到 list 時(shí),它所包含的內(nèi)容就會(huì)被發(fā)送到下個(gè) ChannelInboundHandler

讀者如果想要詳細(xì)了解解碼器的源碼設(shè)計(jì)可以閱讀博客。

2. DelimiterBasedFrameDecoder解碼器應(yīng)用

DelimiterBasedFrameDecoder解碼器是通用的分隔符解碼器,可支持多個(gè)分隔符,每個(gè)分隔符可為一個(gè)或多個(gè)字符。如果定義了多個(gè)分隔符,并且可解碼出多個(gè)消息幀,則選擇產(chǎn)生最小幀長(zhǎng)的結(jié)果。下面我們使用$_作為分隔符來演示。

2.1 服務(wù)端代碼

package netty.frame.delimiter;import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.DelimiterBasedFrameDecoder; import io.netty.handler.codec.string.StringDecoder;/*** created by LMR on 2020/5/20*/ public class EchoServer {public void bind(int port) throws Exception {// 配置服務(wù)端的NIO線程組EventLoopGroup bossGroup = new NioEventLoopGroup();EventLoopGroup workerGroup = new NioEventLoopGroup();try {ServerBootstrap b = new ServerBootstrap();b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 100).childHandler(new ChannelInitializer<SocketChannel>() {@Overridepublic void initChannel(SocketChannel ch)throws Exception {ByteBuf delimiter = Unpooled.copiedBuffer("$_".getBytes());ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024,delimiter));ch.pipeline().addLast(new StringDecoder());ch.pipeline().addLast(new EchoServerHandler());}});// 綁定端口,同步等待成功ChannelFuture f = b.bind(port).sync();// 等待服務(wù)端監(jiān)聽端口關(guān)閉f.channel().closeFuture().sync();} finally {// 優(yōu)雅退出,釋放線程池資源bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}public static void main(String[] args) throws Exception {int port = 8080;new EchoServer().bind(port);} }

在initChannel方法中,我們首先創(chuàng)建分隔符緩沖對(duì)象ByteBuf,然后使用該分隔符緩沖對(duì)象創(chuàng)建DelimiterBasedFrameDecoder編碼器對(duì)象,并加入到ChannelPipeline中。其中第二個(gè)參數(shù)白哦是消息的最大長(zhǎng)度,如果超過這個(gè)長(zhǎng)度還沒有找到分隔符,則認(rèn)為消息出錯(cuò),報(bào)出異常,這是為了防止異常數(shù)據(jù)導(dǎo)致內(nèi)存溢出,提高編碼器的可靠性,最后還添加了字符串解碼器和服務(wù)端處理類實(shí)例。

package netty.frame.delimiter;import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; /*** created by LMR on 2020/5/20*/ public class EchoServerHandler extends ChannelInboundHandlerAdapter {int counter = 0;@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg)throws Exception {String body = (String) msg;System.out.println("This is " + ++counter + " times receive client : ["+ body + "]");body += "$_";ByteBuf echo = Unpooled.copiedBuffer(body.getBytes());ctx.writeAndFlush(echo);}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {cause.printStackTrace();ctx.close();// 發(fā)生異常,關(guān)閉鏈路} }

channelRead方法非常簡(jiǎn)單,由于我們?cè)贑hannelPipeline中添加了多個(gè)編碼器,那個(gè)在這里接收到的消息直接就是完整的消息數(shù)據(jù)字符串,由于我們使用DelimiterBasedFrameDecoder解碼器過濾掉了分隔符,在這里我們重新添加分隔符,以便于客戶端識(shí)別,再發(fā)送給客戶端。

2.2 客戶端代碼

package netty.frame.delimiter;import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.DelimiterBasedFrameDecoder; import io.netty.handler.codec.string.StringDecoder;/*** created by LMR on 2020/5/20*/ public class EchoClient {public void connect(int port, String host) throws Exception {// 配置客戶端NIO線程組EventLoopGroup group = new NioEventLoopGroup();try {Bootstrap b = new Bootstrap();b.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true).handler(new ChannelInitializer<SocketChannel>() {@Overridepublic void initChannel(SocketChannel ch)throws Exception {ByteBuf delimiter = Unpooled.copiedBuffer("$_".getBytes());ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024,delimiter));ch.pipeline().addLast(new StringDecoder());ch.pipeline().addLast(new EchoClientHandler());}});// 發(fā)起異步連接操作ChannelFuture f = b.connect(host, port).sync();// 當(dāng)代客戶端鏈路關(guān)閉f.channel().closeFuture().sync();} finally {// 優(yōu)雅退出,釋放NIO線程組group.shutdownGracefully();}}public static void main(String[] args) throws Exception {int port = 8080;new EchoClient().connect(port, "127.0.0.1");} }

同樣在客戶端我們也添加相應(yīng)的解碼器,然后創(chuàng)建客戶端處理類對(duì)象EchoClientHandler。

package netty.frame.delimiter;import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter;/*** created by LMR on 2020/5/20*/ public class EchoClientHandler extends ChannelInboundHandlerAdapter {private int counter;static final String ECHO_REQ = "This is a example by LMRZero.$_";@Overridepublic void channelActive(ChannelHandlerContext ctx) {for (int i = 0; i < 10; i++) {ctx.writeAndFlush(Unpooled.copiedBuffer(ECHO_REQ.getBytes()));}}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg)throws Exception {System.out.println("This is " + ++counter + " times receive server : ["+ msg + "]");}@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) throws Exception {ctx.flush();}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {cause.printStackTrace();ctx.close();} }

2.3 運(yùn)行結(jié)果

服務(wù)端結(jié)果:

客戶端結(jié)果:

3. FixedLengthFrameDecoder解碼器應(yīng)用

FixedLengthFrameDecoder是按照固定長(zhǎng)度frameLength解碼出消息幀。在本節(jié)中我們使用一個(gè)應(yīng)用實(shí)例對(duì)其用法進(jìn)行介紹。

3.1 服務(wù)端代碼

我們?cè)诜?wù)端ChannelPipeline中添加FixedLengthFrameDecoder,設(shè)置其長(zhǎng)度為20,然后同樣添加字符串解碼器和服務(wù)端處理類實(shí)例。

package netty.frame.fixedLen;import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.FixedLengthFrameDecoder; import io.netty.handler.codec.string.StringDecoder;/*** created by LMR on 2020/5/20*/ public class EchoServer {public void bind(int port) throws Exception {// 配置服務(wù)端的NIO線程組EventLoopGroup bossGroup = new NioEventLoopGroup();EventLoopGroup workerGroup = new NioEventLoopGroup();try {ServerBootstrap b = new ServerBootstrap();b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 100).childHandler(new ChannelInitializer<SocketChannel>() {@Overridepublic void initChannel(SocketChannel ch)throws Exception {ch.pipeline().addLast(new FixedLengthFrameDecoder(20));ch.pipeline().addLast(new StringDecoder());ch.pipeline().addLast(new EchoServerHandler());}});// 綁定端口,同步等待成功ChannelFuture f = b.bind(port).sync();// 等待服務(wù)端監(jiān)聽端口關(guān)閉f.channel().closeFuture().sync();} finally {// 優(yōu)雅退出,釋放線程池資源bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}public static void main(String[] args) throws Exception {int port = 8080;new EchoServer().bind(port);} }

下面看看服務(wù)端處理類EchoServerHandler的實(shí)現(xiàn)代碼:

package netty.frame.fixedLen;import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; /*** created by LMR on 2020/5/20*/ public class EchoServerHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg)throws Exception {System.out.println("Receive client : [" + msg + "]");}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {cause.printStackTrace();ctx.close();// 發(fā)生異常,關(guān)閉鏈路} }

在本例中,我們?cè)诜?wù)端接收到消息之后直接進(jìn)行打印,不進(jìn)行任何的操作。利用FixedLengthFrameDecoder解碼器,無論一次接收到多少數(shù)據(jù)報(bào),都會(huì)按照固定長(zhǎng)度來進(jìn)行解碼。下面我們通過telnet命令行來測(cè)試服務(wù)端能否按照預(yù)期進(jìn)行工作。

3.2 telnet客戶端測(cè)試

在這里我們通過telnet命令來對(duì)服務(wù)端進(jìn)行測(cè)試,下面介紹具體步驟:
(1)啟動(dòng)服務(wù)端
(2)開啟本地回顯功能,便于觀察

(3)打開命令行窗口,輸入telnet localhost 8080

(3)在命令行窗口輸入需要傳輸?shù)臄?shù)據(jù)內(nèi)容:


(4)服務(wù)端查看結(jié)果

可以看出每次都是收到20個(gè)字節(jié)的數(shù)據(jù)。

————————————————————————————————————————
參考博客和書籍:
https://www.w3cschool.cn/essential_netty_in_action/essential_netty_in_action-x7mn28bx.html
https://blog.csdn.net/usagoole/article/details/87389182
https://www.cnblogs.com/yuanrw/p/9866356.html
https://blog.csdn.net/mascf/article/details/60478539
https://www.cnblogs.com/ZhuChangwu/p/11225158.html
《Netty 權(quán)威指南》

如果喜歡的話希望點(diǎn)贊收藏,關(guān)注我,將不間斷更新博客。

希望熱愛技術(shù)的小伙伴私聊,一起學(xué)習(xí)進(jìn)步

來自于熱愛編程的小白

總結(jié)

以上是生活随笔為你收集整理的你了解Netty的编解码器吗?史上最通俗易懂的Netty解码器应用案例带你解开Netty解码器的神秘面纱的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。