Netty的分隔符解码器的使用

来源:互联网 发布:淘宝购物车营销 编辑:程序博客网 时间:2024/05/17 07:22

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粘包问题导致的。

阅读全文
1 0
原创粉丝点击