Netty的分隔符解码器的使用

TCP以流的方式进行数据传输,上层的应用协议为了对消息进行区分,采用消息长度固定、以回车换行符作为结束标志、特殊分隔符作为结束标志、消息头中定义长度等方式。Netty对于这几种方式做了统一的抽象,分别提供四种解码器解决。
在这里,我使用DelimiterBasedFrameDecoder解码器来解决以分隔符作为结束标志的消息的解码。
PS:FixedLengthFrameDecoder是定长消息的解码。

程序演示echo服务,服务端收到客户端的请求消息后,将其打印出来,并将原消息返回给客户端。程序以“$$”为分隔符。

服务端:

EchoServer类

public class EchoServer {

    public void bind(int port) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        ServerBootstrap bootstrap = new ServerBootstrap();
        try {
            bootstrap.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 1024)
                .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 future = bootstrap.bind(port).sync();
            future.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        int port = 443;
        new EchoServer().bind(port);
    }
}

EchoServerHandler类

public class EchoServerHandler extends ChannelInboundHandlerAdapter {

    private int count = 0;
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg)
            throws Exception {
        String body = (String) msg;
        System.out.println("This is : " + ++count + " times receive client , body is " + body);
        body += "$$";
        ByteBuf echo = Unpooled.copiedBuffer(body.getBytes());
        ctx.writeAndFlush(echo);
    }
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        ctx.close();
    }
}

客户端

EchoClient类

public class EchoClient {

    public void connect(int port, String host) throws Exception {
        // 配置客户端NIO 线程组
        EventLoopGroup group = new NioEventLoopGroup();
        Bootstrap client = new Bootstrap();
        try {
            client.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 future = client.connect(host, port).sync();
            future.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args) {
        int port = 443;
        EchoClient client = new EchoClient();
        try {
            client.connect(port, "127.0.0.1");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

EchoClientHandler类

public class EchoClientHandler extends ChannelInboundHandlerAdapter {

    private int count;
    static final String ECHO_REQ = "hello, welcome to Netty,$$";

    @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 {

        String body = (String) msg;
        System.out.println("This is : " + ++count
                + " times receive client , body is " + body);
    }

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

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }
}

运行结果,客户端和服务器都会打印如下的内容,说明使用DelimiterBasedFrameDecoder可以完成以分隔符为结束标志的解码。

This is : 1 times receive client , body is hello, welcome to Netty,
This is : 2 times receive client , body is hello, welcome to Netty,
This is : 3 times receive client , body is hello, welcome to Netty,
This is : 4 times receive client , body is hello, welcome to Netty,
This is : 5 times receive client , body is hello, welcome to Netty,
This is : 6 times receive client , body is hello, welcome to Netty,
This is : 7 times receive client , body is hello, welcome to Netty,
This is : 8 times receive client , body is hello, welcome to Netty,
This is : 9 times receive client , body is hello, welcome to Netty,
This is : 10 times receive client , body is hello, welcome to Netty,

如果想测试一下没有使用分隔符的解码器,其实只需要注释掉服务端一下两条语句即可。

ByteBuf delimiter = Unpooled.copiedBuffer("$$".getBytes());
                        ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, delimiter));

然后服务端的结果为:

This is : 1 times receive client , body is hello, welcome to Netty,$$hello, welcome to Netty,$$hello, welcome to Netty,$$hello, welcome to Netty,$$hello, welcome to Netty,$$hello, welcome to Netty,$$hello, welcome to Netty,$$hello, welcome to Netty,$$hello, welcome to Netty,$$hello, welcome to Netty,$$

客户端的结果为:

This is : 1 times receive client , body is hello, welcome to Netty,
This is : 2 times receive client , body is hello, welcome to Netty,
This is : 3 times receive client , body is hello, welcome to Netty,
This is : 4 times receive client , body is hello, welcome to Netty,
This is : 5 times receive client , body is hello, welcome to Netty,
This is : 6 times receive client , body is hello, welcome to Netty,
This is : 7 times receive client , body is hello, welcome to Netty,
This is : 8 times receive client , body is hello, welcome to Netty,
This is : 9 times receive client , body is hello, welcome to Netty,
This is : 10 times receive client , body is hello, welcome to Netty,
This is : 11 times receive client , body is 

根据结果可以清楚的看到,没有分隔符解码器导致服务端一次读取了客户端发的所有消息,这也是典型的没有考虑TCP粘包问题导致的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值