Netty学习(五)-DelimiterBasedFrameDecoder

原创 2017年03月29日 23:23:55

上一节我们说了LineBasedframeDecoder来解决粘包拆包的问题,TCP以流的方式进行数据传输,上层应用协议为了对消息进行区分,一般采用如下4种方式:

  1. 消息长度固定,累计读取到消息长度总和为定长Len的报文之后即认为是读取到了一个完整的消息。计数器归位,重新读取。
  2. 将回车换行符作为消息结束符。
  3. 将特殊的分隔符作为消息分隔符,回车换行符是他的一种。
  4. 通过在消息头定义长度字段来标识消息总长度。

LineBasedframeDecoder属于第二种,今天我们要说的DelimiterBasedFrameDecoder和FixedLengthFrameDecoder属于第三种和第一种。DelimiterBasedFrameDecoder用来解决以特殊符号作为消息结束符的粘包问题,FixedLengthFrameDecoder用来解决定长消息的粘包问题。下面首先来用DelimiterBasedFrameDecoder来写一个例子,我们看一下效果然后接着分析用法。

1. DelimiterBasedFrameDecoder使用

服务端:

public class HelloWordServer {
    private int port;

    public HelloWordServer(int port) {
        this.port = port;
    }

    public void start(){
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workGroup = new NioEventLoopGroup();

        ServerBootstrap server = new ServerBootstrap().group(bossGroup,workGroup)
                                    .channel(NioServerSocketChannel.class)
                                    .childHandler(new ServerChannelInitializer());

        try {
            ChannelFuture future = server.bind(port).sync();
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) {
        HelloWordServer server = new HelloWordServer(7788);
        server.start();
    }
}

服务端ServerChannelInitializer:

public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();

        ByteBuf delimiter = Unpooled.copiedBuffer("\t".getBytes());
        pipeline.addLast("framer", new DelimiterBasedFrameDecoder(2048,delimiter));    
        // 字符串解码 和 编码
        pipeline.addLast("decoder", new StringDecoder());
        pipeline.addLast("encoder", new StringEncoder());

        // 自己的逻辑Handler
        pipeline.addLast("handler", new ServerHandler());
    }
}

服务端handler:

public class ServerHandler extends ChannelInboundHandlerAdapter {
    private int counter;

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        String body = (String)msg;
        System.out.println("server receive order : " + body + ";the counter is: " + ++counter);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        super.exceptionCaught(ctx, cause);
    }
}

客户端:

public class HelloWorldClient {
    private  int port;
    private  String address;

    public HelloWorldClient(int port,String address) {
        this.port = port;
        this.address = address;
    }

    public void start(){
        EventLoopGroup group = new NioEventLoopGroup();

        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(group)
                .channel(NioSocketChannel.class)
                .handler(new ClientChannelInitializer());

        try {
            ChannelFuture future = bootstrap.connect(address,port).sync();
            future.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            group.shutdownGracefully();
        }

    }

    public static void main(String[] args) {
        HelloWorldClient client = new HelloWorldClient(7788,"127.0.0.1");
        client.start();
    }
}

客户端ClientChannelInitializer:

public class ClientChannelInitializer extends  ChannelInitializer<SocketChannel> {

    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();

        /*
         * 这个地方的 必须和服务端对应上。否则无法正常解码和编码
         *
         *
         */       
        ByteBuf delimiter = Unpooled.copiedBuffer("\t".getBytes());
        pipeline.addLast("framer", new DelimiterBasedFrameDecoder(2048,delimiter)); 
        pipeline.addLast("decoder", new StringDecoder());
        pipeline.addLast("encoder", new StringEncoder());

        // 客户端的逻辑
        pipeline.addLast("handler", new ClientHandler());
    }
}

客户端handler:

public class ClientHandler extends ChannelInboundHandlerAdapter {
    private byte[] req;
    private int counter;

    public ClientHandler() {
         req = ("Unless required by applicable law or agreed to in writing, software\t" +
                "  distributed under the License is distributed on an \"AS IS\" BASIS,\t" +
                "  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\t" +
                "  See the License for the specific language governing permissions and\t" +
                "  limitations under the License.This connector uses the BIO implementation that requires the JSSE\t" +
                "  style configuration. When using the APR/native implementation, the\t" +
                "  penSSL style configuration is required as described in the APR/native\t" +
                "  documentation.An Engine represents the entry point (within Catalina) that processes\t" +
                "  every request.  The Engine implementation for Tomcat stand alone\t" +
                "  analyzes the HTTP headers included with the request, and passes them\t" +
                "  on to the appropriate Host (virtual host)# Unless required by applicable law or agreed to in writing, software\t" +
                "# distributed under the License is distributed on an \"AS IS\" BASIS,\t" +
                "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\t" +
                "# See the License for the specific language governing permissions and\t" +
                "# limitations under the License.# For example, set the org.apache.catalina.util.LifecycleBase logger to log\t" +
                "# each component that extends LifecycleBase changing state:\t" +
                "#org.apache.catalina.util.LifecycleBase.level = FINE\t"
                ).getBytes();
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ByteBuf message;

        message = Unpooled.buffer(req.length);
        message.writeBytes(req);
        ctx.writeAndFlush(message);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        String buf = (String)msg;
        System.out.println("Now is : " + buf + " ; the counter is : "+ (++counter));
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}

输出如下:

server receive order : Unless required by applicable law or agreed to in writing, software;the counter is: 1
server receive order :   distributed under the License is distributed on an "AS IS" BASIS,;the counter is: 2
server receive order :   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.;the counter is: 3
server receive order :   See the License for the specific language governing permissions and;the counter is: 4
server receive order :   limitations under the License.This connector uses the BIO implementation that requires the JSSE;the counter is: 5
server receive order :   style configuration. When using the APR/native implementation, the;the counter is: 6
server receive order :   penSSL style configuration is required as described in the APR/native;the counter is: 7
server receive order :   documentation.An Engine represents the entry point (within Catalina) that processes;the counter is: 8
server receive order :   every request.  The Engine implementation for Tomcat stand alone;the counter is: 9
server receive order :   analyzes the HTTP headers included with the request, and passes them;the counter is: 10
server receive order :   on to the appropriate Host (virtual host)# Unless required by applicable law or agreed to in writing, software;the counter is: 11
server receive order : # distributed under the License is distributed on an "AS IS" BASIS,;the counter is: 12
server receive order : # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.;the counter is: 13
server receive order : # See the License for the specific language governing permissions and;the counter is: 14
server receive order : # limitations under the License.# For example, set the org.apache.catalina.util.LifecycleBase logger to log;the counter is: 15
server receive order : # each component that extends LifecycleBase changing state:;the counter is: 16
server receive order : #org.apache.catalina.util.LifecycleBase.level = FINE;the counter is: 17

启动服务端和客户端,我们能看到服务端接收客户端发过来的消息一共分17次接收。那么为什么是17次呢?而且我们并没有使用在上一篇中解决拆包和粘包问题的LineBasedFrameDecoder,并且这次我们的消息每一行的末尾也换成了”\t”。下面就来讲解一下DelimiterBasedFrameDecoder的使用。

DelimiterBasedFrameDecoder是将特殊的字符作为消息的分隔符,本例中用到的是”\t”。而LineBasedFrameDecoder是默认将换行符”\n”作为消息分隔符。首先我们注意到在ServerChannelInitializer中我们在添加解码器时跟以前有点不一样:

ByteBuf delimiter = Unpooled.copiedBuffer("\t".getBytes());
pipeline.addLast("framer", new DelimiterBasedFrameDecoder(2048, delimiter));

这里我们添加DelimiterBasedFrameDecoder解码器并且手动指定消息分隔符为:”\t”。我们可以看一下DelimiterBasedFrameDecoder的构造方法:

public DelimiterBasedFrameDecoder(int maxFrameLength, boolean stripDelimiter, ByteBuf delimiter) {
        this(maxFrameLength, stripDelimiter, true, delimiter);
}

maxFrameLength:解码的帧的最大长度

stripDelimiter:解码时是否去掉分隔符

failFast:为true,当frame长度超过maxFrameLength时立即报TooLongFrameException异常,为false,读取完整个帧再报异常

delimiter:分隔符

这个时候大家应该明白了为什么服务端分17次收到消息。我们在消息的每一行都加了一个”\t”,自然解码器在度消息时遇到”\t”就会认为这是一条消息的结束。用这种方式我们可以把”\t”换成任何我们自定义的字符对象。换成”\n”也是可以的。

2. FixedLengthFrameDecoder使用

FixedLengthFrameDecoder是固定长度解码器,它能够按照指定的长度对消息进行自动解码。使用它也没有什么特别费力的事情,在ServerChannelInitializer类中添加:

pipeline.addLast(new FixedLengthFrameDecoder(23));//参数为一次接受的数据长度

即可,同时也别忘了把刚才使用的DelimiterBasedFrameDecoder注释掉啊,不然达不到效果。

版权声明:本文为博主原创文章,未经博主允许不得转载。

Netty (四) 分隔符和定长解码器的使用

TCP以流的形式进行数据传输,上层的应用协议为了对消息进行划分,往往采用如下的4种方式。 (1)消息长度固定,累计读到长度总和为定长len的报文后,就认为读取到了一个完整的消息;然后重新开始读取下一...
  • LANGZI7758521
  • LANGZI7758521
  • 2016年09月30日 16:24
  • 3301

[netty]--分隔符解码器DelimiterBasedFrameDecoder和定长解码器FixedLengthFrameDecoder

TCP以流的方式进行数据传输,上层应用协议为了对消息进行区分,往往采用如下4种方式。 (1)消息长度固定:累计读取到固定长度为LENGTH之后就认为读取到了一个完整的消息。然后将计数器复位,重新开始...
  • u010853261
  • u010853261
  • 2017年01月31日 17:51
  • 2009

Netty学习之分隔符解决TCP粘包

DelimiterBasedFrameDecoder解决TCP粘包
  • dfdsggdgg
  • dfdsggdgg
  • 2016年04月24日 16:03
  • 3347

Netty——基本使用介绍

1.为什么选择Netty 上一篇文章我们已经了解了Socket通信(IO/NIO/AIO)编程,对于通信模型已经有了一个基本的认识。其实上一篇文章中,我们学习的仅仅是一个模型,如果想把这些真正的用于实...
  • haoyuyang
  • haoyuyang
  • 2016年11月20日 19:51
  • 27575

netty源码分析之FrameDecoder(LengthFieldBasedFrameDecoder)

我们接下来看一个也是比较重要的的解码器LengthFieldBasedFrameDecoder,这个和DelimiterBasedFrameDecoder比起来没有那么难理解,所以我们简单的看一下。 ...
  • jbgtwang
  • jbgtwang
  • 2014年06月12日 10:58
  • 4848

Netty源码分析之DelimiterBasedFrameDecoder

DelimiterBasedFrameDecoder可以接受多个分隔符,比LineBasedFrameDecoder功能强大。实际上,DelimiterBasedFrameDecoder有一个成员变量...
  • shifeng2400
  • shifeng2400
  • 2015年08月26日 16:49
  • 730

Netty实现原理浅析

Netty实现原理浅析 2015/04/03 | 分类: 基础技术 | 0 条评论 | 标签: Netty 分享到:50 原文出处: kafka0102的博客 Nett...
  • hanyingzhong
  • hanyingzhong
  • 2017年03月07日 19:13
  • 409

spring+netty服务器搭建

游戏一般是长连接,自定义协议,不用http协议,BIO,NIO,AIO这些我就不说了,自己查资料 我现在用spring+netty搭起简单的游戏服 思路:1自定义协议和协议包;2spring+ne...
  • u012930316
  • u012930316
  • 2017年06月26日 18:18
  • 4235

系统间通信方式之(Java之Netty初步详解)(七)

上篇文章我们讨论了Netty的基本原理,重要概念,并使用java代码描述了Netty的基本使用。当然Netty的技术涵盖点远远不是那一篇基础代码就可以全部概括的,但是至少可以给读者一个切入点。让大家去...
  • u010963948
  • u010963948
  • 2017年11月13日 17:24
  • 233

netty:使用ChannelDuplexHandler 来接收、下发数据

项目背景 因为需求是和硬件对接,需要定时对硬件设备进行检查,因此决定使用netty作为通信中间件。使用netty的ChannelDuplexHandler 来接收、下发硬件数据。 硬件通过TCP长...
  • z199172177
  • z199172177
  • 2018年01月05日 11:43
  • 133
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Netty学习(五)-DelimiterBasedFrameDecoder
举报原因:
原因补充:

(最多只允许输入30个字)