Netty(3) 分隔符和定长解码器的应用
正确使用逗号、句号和分隔符 #生活技巧# #沟通技巧# #邮件礼仪#
分隔符和定长解码器的应用
TCP以流的方式进行数据传输。上层的应用协议为了对消息进行区分,往往采用如下4种方式:
消息长度固定,累计读取到长度总和为定长LEN的报文后,就认定读取到了一个完成的消息;将计数器置位,重新开始下一个数据报;将回车换行符作为消息的结束符。例如FTP协议,这种方式在文本协议中应用比较广泛;将特殊的分隔符作为消息的结束标志,回车换行符就是一种特殊的结束分隔符;通过在消息头中定义长度字段来表示消息的总长度;Netty对上面的4种应用做了统一的抽象,提供了四种解码器来解决对应的问题,使用起来非常方便。有了这些解码器,用户便不需要自己对读取报文进行人工解码,也不需要考虑TCP粘包和拆包。
前面我们学习了LineBasedFrameDecoder解决TCP粘包问题,在这一章我们继续学习另外两个——DelimiterBasedFrameDecoder和FixedLengthFrameDecoder,前者可以完成以分隔符做结束标志的消息的解码,后者可以自动完成对定长消息的解码,它们都能解决TCP粘包/拆包导致的读半包问题。
一、DelimiterBasedFrameDecoder应用开发
通过对DelimiterBasedFrameDecoder的使用,我们可以自动完成以分隔符作为码流结束标识的消息的解码,下面通过经典的Echo服务为例进行演示。EchoServer接收到EchoClient的请求消息后,将其打印出来,然后将原始消息返回个客户端,消息以”$_“作为分隔符。
1.1 服务端开发EchoServer代码:
public class EchoServer { public void bind(int port) throws InterruptedException { NioEventLoopGroup bossGroup = new NioEventLoopGroup(); NioEventLoopGroup workerGroup = new NioEventLoopGroup(); try{ ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG,100) .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected 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(); //等待服务端监听端口关闭 f.channel().closeFuture().sync(); }finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } public static void main(String[] args) throws InterruptedException { int port = 8080; if(args!=null && args.length>0){ try{ port = Integer.parseInt(args[0]); }catch (NumberFormatException e){ // } } new EchoServer().bind(port); } }
12345678910111213141516171819202122232425262728293031323334353637383940414243EchoServerHandler代码:
public class EchoServerHandler extends ChannelHandlerAdapter { int count = 0; @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { String body = (String) msg; System.out.println("The is " + ++count + "times receive client:[" + body + "]"); body += "$_"; ByteBuf echo = Unpooled.copiedBuffer(body.getBytes()); ctx.writeAndFlush(echo); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
123456789101112131415161718 1.2客户端开发EchoClient代码:
public class EchoCilent { public void connect(int port, String host) throws InterruptedException { NioEventLoopGroup group = new NioEventLoopGroup(); try{ Bootstrap b = new Bootstrap(); b.group(group) .channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) .handler(new ChannelInitializer<SocketChannel>() { @Override protected 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()); } }); //发起异步连接 ChannelFuture f = b.connect(host, port).sync(); //等待客户端链路关闭 f.channel().closeFuture().sync(); }finally { group.shutdownGracefully(); } } public static void main(String[] args) throws InterruptedException { int port = 8080; try{ if(args!=null && args.length>0){ port = Integer.parseInt(args[0]); } }catch(NumberFormatException e){ } new EchoCilent().connect(port,"127.0.0.1"); } }
123456789101112131415161718192021222324252627282930313233343536373839EchoClientHandler代码:
public class EchoClientHandler extends ChannelHandlerAdapter { private int count = 0; static final String ECHO_REQ = "Hi,Welcome to Netty.$_"; public EchoClientHandler() { } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { for (int i = 0; i < 10; i++) { ctx.writeAndFlush(Unpooled.copiedBuffer(ECHO_REQ.getBytes())); } } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { System.out.println("This is " + ++count + "times receive server : [" + msg + "]"); } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
12345678910111213141516171819202122232425262728293031 1.3运行代码二、FixedLengthFrameDecoder应用开发
这是一个固定长度解码器,它能够按照指定的长度对消息进行自动解码,开发者吧u需要考虑TCP的粘包/拆包问题。非常实用。
2.1 服务端开发在服务端的ChannelPipeline中新增FixedLengthFrameDecoder,长度设置为20,然后再一次增加字符串解码器和EchoServerHandler,代码如下:
EchoServer代码:
public class EchoServer { public void bind(int port) throws InterruptedException { NioEventLoopGroup bossGroup = new NioEventLoopGroup(); NioEventLoopGroup workerGroup = new NioEventLoopGroup(); try{ ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG,100) .childHandler(new LoggingHandler(LogLevel.INFO)) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new FixedLengthFrameDecoder(20)); ch.pipeline().addLast(new StringDecoder()); ch.pipeline().addLast(new EchoServerHandler()); } }); //绑定端口 ChannelFuture f = bootstrap.bind(port).sync(); //等待关闭 f.channel().closeFuture().sync(); }finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } public static void main(String[] args) throws InterruptedException { int port = 8080; try { if(args!=null && args.length>0){ port = Integer.parseInt(args[0]); } }catch (NumberFormatException e){ } new EchoServer().bind(port); } }
1234567891011121314151617181920212223242526272829303132333435363738394041EchoServerHandler代码:
public class EchoServerHandler extends ChannelHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { System.out.println("Receive client : [" + msg + "]"); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } } 123456789101112 2.2利用telnet 命令模拟客户端连接
win+r打开终端:
输入cmd然后回车,就能打开终端:
在终端输入:telnet localhost 8080,然后回车:
进入这个页面,说明连接成功了,我们按下·ctrl+l快捷键打开telnet回显功能,否则你看不到自己输入的东西:
出现Microsoft Telnet,我们直接回车:
重新回到空白的页面,然后输入信息,此刻面板出现我们输入的信息,无论我们输入多少内容,服务端都会接收,当长度满足定长解码器设置的长度,那么服务端就会将此消息当成一次消息:
服务端打印消息:
后面的信息省略了,因为超出了我们设置的20的长度。
网址:Netty(3) 分隔符和定长解码器的应用 https://www.yuejiaxmz.com/news/view/261228
相关内容
请使用千位分隔符(逗号)表示web网页中的大数字 « 张鑫旭文本更改分隔符
Vector容器重载运算符源码解析
在线摩斯密码翻译器
分隔器ALFALAVAL食品和饮料分离器
保持架/分隔器
信号隔离器,隔离栅,安全栅,隔离分配器
抽屉收纳分隔板片塑料分割板隔断自由组合家用整理格子隔断分隔盒 侧边固定器 8片装 10.1元
umbra 佩吉抽屉分隔器家用厨房神器碗盘分隔收纳置物架可伸缩碗架 105.38元(需买3件,共316.14元)
抽屉分隔器.pdf