分割符和定长解码

来源:互联网 发布:看板设计软件 编辑:程序博客网 时间:2024/06/05 03:39

0 概述

TCP以流的方式进行数据传输,上层的应用协议为了对消息进行区分,往往采用以下四种方式

  • 消息的长度固定,累计读取到长度总和为定长的len的报文之后,都认为读取到了一个完整的消息。

  • 将回车换行符作为消息的结束符,例如FTP协议,这种方式在文本协议中应用的比较广泛。

  • 将特殊的分隔符作为消息的结束标志,回车换行符本身就是一种特殊的符号。

  • 通过在消息头中定义长度字段来标识消息的总长度。

    netty 对上面的几种做了统一 的抽象,提供了4种解码器来解决对应的问题。

1 特定分割符解码

netty 提供了DelimiterBasedFrameDecoder作为分割符解码器。

公共类

public class DelimiterConstant {    //分割符    public final static String  DELIMITER_STR="$*";    public final static ByteBuf DELIMITER_BUF = Unpooled.copiedBuffer(DELIMITER_STR.getBytes());    public final static Integer MAX_FRAME_LEN=1000;}

服务端实现

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.DelimiterBasedFrameDecoder;/** * Created by hsc on 17/10/14. */public class NettyServer {    private static void startServer(final int port) throws Exception {        final EventLoopGroup boss = new NioEventLoopGroup(1);        EventLoopGroup worker = new NioEventLoopGroup();        ServerBootstrap serverBootstrap = new ServerBootstrap();        try {            serverBootstrap.channel(NioServerSocketChannel.class)                    .group(boss, worker)                    .childOption(ChannelOption.SO_BACKLOG, 128)                    .childOption(ChannelOption.SO_KEEPALIVE, true)                    .childHandler(new ChannelInitializer<SocketChannel>() {                        @Override                        protected void initChannel(SocketChannel ch) throws Exception {                            //注意DelimiterBasedFrameDecoder和ServerHandler顺序                            ch.pipeline().addLast(new DelimiterBasedFrameDecoder(DelimiterConstant.MAX_FRAME_LEN, DelimiterConstant.DELIMITER_BUF));                            ch.pipeline().addLast(new ServerHandler());                        }                    });            ChannelFuture channelFuture = serverBootstrap.bind(port).sync();            //等待服务端监听端口关闭            channelFuture.channel().closeFuture().sync();        } finally {            boss.shutdownGracefully();            worker.shutdownGracefully();        }    }    public static void main(String[] args) throws Exception {        startServer(8080);    }}

SeverHandler 实现

public class ServerHandler extends ChannelInboundHandlerAdapter {    @Override    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {        if (msg instanceof ByteBuf) {            ByteBuf byteBuf = (ByteBuf) msg;            byte[] data = new byte[byteBuf.readableBytes()];            byteBuf.readBytes(data);            System.out.println(new String(data));            String responseMsg="收到"+ DelimiterConstant.DELIMITER_STR;            ByteBuf sendMsg= Unpooled.copiedBuffer(responseMsg.getBytes());            ctx.writeAndFlush(sendMsg);        }    }}

客户端实现

import com.hsc.study.codec.DelimiterConstant;import io.netty.bootstrap.Bootstrap;import io.netty.channel.*;import io.netty.channel.nio.NioEventLoopGroup;import io.netty.channel.socket.nio.NioSocketChannel;import io.netty.handler.codec.DelimiterBasedFrameDecoder;import io.netty.handler.codec.LineBasedFrameDecoder;import io.netty.handler.codec.string.StringDecoder;/** * Created by apple on 17/10/1. */public class NettyClient {    public static void main(String[] args) {        startClient();    }    private static void startClient() {        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();        Bootstrap bootstrap = new Bootstrap();        try {            bootstrap.channel(NioSocketChannel.class)                    .group(eventLoopGroup)                    .option(ChannelOption.SO_KEEPALIVE, true)                    .handler(new ChannelInitializer<NioSocketChannel>() {                        protected void initChannel(NioSocketChannel ch) throws Exception {                            ch.pipeline().addLast(new DelimiterBasedFrameDecoder(DelimiterConstant.MAX_FRAME_LEN, DelimiterConstant.DELIMITER_BUF));                            ch.pipeline().addLast(new ClientHandler());                        }                    });            //发起异步连接操作           ChannelFuture future= bootstrap.connect("127.0.0.1", 8080).sync();           //等待客户端链路关闭           future.channel().closeFuture().sync();        } catch (Exception ex) {            ex.printStackTrace();        } finally {            eventLoopGroup.shutdownGracefully();        }    }}

ClientHandler 实现

import com.hsc.study.codec.DelimiterConstant;import io.netty.buffer.ByteBuf;import io.netty.buffer.Unpooled;import io.netty.channel.ChannelHandlerContext;import io.netty.channel.ChannelInboundHandlerAdapter;/** * Created by apple on 17/10/1. */public class ClientHandler extends ChannelInboundHandlerAdapter {    @Override    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {        System.out.println("客户端开始读数据");        ByteBuf buf = (ByteBuf) msg;        byte[] data = new byte[buf.readableBytes()];        buf.readBytes(data);        System.out.println(new String(data));    }    @Override    public void channelActive(ChannelHandlerContext ctx) throws Exception {        //向服务端发送100 个hello world        String message = "hello world" + DelimiterConstant.DELIMITER_STR;        for (int i = 0; i < 10; i++) {            ByteBuf buf = Unpooled.copiedBuffer(message.getBytes());            ctx.writeAndFlush(buf);        }    }}

不难发现当我们 DelimiterBasedFrameDecoder maxFrameLength设置比较小时候比如为8,也就是说在八字节无法读取到分割符,netty就会抛如下异常。

io.netty.handler.codec.TooLongFrameException: frame length exceeds 8: 11 - discarded    at io.netty.handler.codec.DelimiterBasedFrameDecoder.fail(DelimiterBasedFrameDecoder.java:300)    at io.netty.handler.codec.DelimiterBasedFrameDecoder.decode(DelimiterBasedFrameDecoder.java:266)    at io.netty.handler.codec.DelimiterBasedFrameDecoder.decode(DelimiterBasedFrameDecoder.java:216)    at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:489)    at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:428)    at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:265)    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)    at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1359)    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)    at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:935)    at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:134)    at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:645)    at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:580)    at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:497)    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:459)    at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:858)    at io.netty.util.concurrent.DefaultThreadFactory$DefaultRunnableDecorator.run(DefaultThreadFactory.java:138)    at java.lang.Thread.run(Thread.java:748)

2 定长解码

netty 中提供了FixedLengthFrameDecoder 作为定长解码器,使用方式和前面类似,这里就不在赘述。

  ch.pipeline().addLast(new FixedLengthFrameDecoder(1024));

参考文献
[1] Netty 权威指南(第二版),李林峰著

原创粉丝点击