Netty-编解码处理器

什么是编解码处理器?
编码处理器是将消息转换为适合于传输的格式网络字节流;而对应的解码处理器则是将网络字节流转换回应用程序的消息格式。

比如LengthFieldPrepender就是一个编码器,该编码器会给消息加上一个长度域。从继承关系,可以看出编码器实际上是一种出站处理器。

e4cf18307e6770322ef0a1b755e56149824.jpg

533755f26def02137bcaef4cbbe058fee7c.jpg

而LengthFieldBasedFrameDecoder就是一个解码处理器,参数配置正确,它就会解掉长度域,得到我们真正想要的数据。从继承关系,可以看出解码处理器实际上是一种入站处理器。

fc962e3260472f22dd163b84239a5f834ab.jpg

 

解码处理器介绍:

1、将字节解码为消息ByteToMessageDecoder

将字节解码为消息(或者另一个字节序列)是一项如此常见的任务,以至于Netty 为它提供了一个抽象的基类:ByteToMessageDecoder。由于你不可能知道远程节点是否会一次性地发送一个完整的消息,所以这个类会对入站数据进行缓冲。

decode()方法:

3aea89c4d6ea7ddf55f6c505bfd2c6ac286.jpg

这是你必须实现的唯一抽象方法。decode()方法被调用时将会传入一个包含了传入数据的ByteBuf,以及一个用来添加解码消息的List。对这个方法的调用将会重复进行,直到确定没有新的元素被添加到该List,或者该ByteBuf 中没有更多可读取的字节时为止。然后,如果该List 不为空,那么它的内容将会被传递给ChannelPipeline 中的下一个ChannelInboundHandler。

比如DelimiterBasedFrameDecoder解码处理器,我们发送消息的时候会加上一个分隔符,该解码处理器就会根据分割符把数据切分成多个ByteBuf添加到out中。

然后会循环遍历out,调用ctx.fireChannelRead(msgs.getUnsafe(i)); 传递给下一个ChannelInboundHandlerAdapter 入站处理器。

311b3ea918d9b41d91adb871dcbb3fd1bbc.jpg

3a942bdc2cab797a344a1d0d80d82233b3f.jpg

 

2、将一种消息类型解码为另一种——MessageToMessageDecoder<I>,I代表源数据的类型。

在两个消息格式之间进行转换(例如,从Integer->String)


public class IntToStringDecoder extends MessageToMessageDecoder<Integer> {
    @Override
    protected void decode(ChannelHandlerContext ctx, Integer msg, List<Object> out) throws Exception {
        out.add(String.valueOf(msg));//转为String 并将它添加到输出的list中
    }
}

decode方法:对于每个需要被解码为另一种格式的入站消息来说,该方法都将会被调用。解码消息随后会被传递给ChannelPipeline中的下一个ChannelInboundHandler。

 a613e59c53f7b418aa9b6080d510281838c.jpg

 

编码处理器介绍:

1、将消息编码为字节:只需要继承MessageToByteEncoder,然后实现encode()方法。

public abstract class MessageToByteEncoder<I>

encode()方法是你需要实现的唯一抽象方法,在该方法里将消息写入out中。该ByteBuf 随后将会被转发给 ChannelPipeline中的下一个 ChannelOutboundHandler。

133b292ef99b6044593d784a94a3a8e7c5b.jpg

举个例子:spring转为网络字节数据

public class StringToByteEncoder extends MessageToByteEncoder<String>{
    @Override
    protected void encode(ChannelHandlerContext ctx, String msg, ByteBuf out) throws Exception {
        out.writeBytes(msg.getBytes(CharsetUtil.UTF_8));
    }
}

 

2、将消息编码为消息:只需要继承MessageToMessageEncoder,然后实现encode()方法。

public abstract class MessageToMessageEncoder<I> extends ChannelOutboundHandlerAdapter {

实现该方法可以将一种格式的消息转为另一种,然后添加到out中。随后,会被转发给 ChannelPipeline中的下一个 ChannelOutboundHandler

d29c9ef38c8c8c14805de5dbbfaae52a8d7.jpg

int 转为 string

public class IntToStringEncoder extends MessageToMessageEncoder<Integer> {

    @Override
    protected void encode(ChannelHandlerContext ctx, Integer msg, List<Object> out) throws Exception {
        out.add(String.valueOf(msg));
    }
}

 

实战:

发送消息(消息类型为User对象)----》将User对象转为Json格式的字节数组----》发送到网络上

接收数据-----》将字节数组转为User对象。

编解码处理器:

JsonDecoder 该类将Bytebuf缓存区里的字节数据转为json字符串,然后将json对象转为User对象,最后放到输出list中,转到下一个

ChannelInboundHandlerAdapter。

public class JsonDecoder extends MessageToMessageDecoder<ByteBuf>{

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception {
        String msgStr = in.toString(CharsetUtil.UTF_8);
        System.out.println("JsonDecoder:"+msgStr);
        //将json字符串转为对象
        User user = JSON.parseObject(msgStr,new TypeReference<User>(){});
        //将对象放到list中
        out.add(user);
    }

}

JsonEncoder 编码器将User对象转为Json字符串,然后将字符串转为网络字节数据发送到网络上。

public class JsonEncoder extends MessageToByteEncoder<User> {

    @Override
    protected void encode(ChannelHandlerContext ctx, User msg, ByteBuf out) throws Exception {
        System.out.println("JsonEncoder:"+msg);
        String userJson = JSON.toJSONString(msg);
        //将消息写入ByteBuf中
        out.writeBytes(userJson.getBytes(CharsetUtil.UTF_8));
    }

}

 

服务端启动类:

服务端添加了LengthFieldBasedFrameDecoder和LengthFieldPrepender来解决粘包和半包问题。客户端发送过来的消息首先经过LengthFieldBasedFrameDecoder解掉长度域,然后经由JsonDecoder将ByteBuf里的字节数据转为User对象。最后经由CustomEchoServerInHandler进行处理。

public class CustomEchoServer {
    private int port;
    private EventLoopGroup bossGroup;
    private EventLoopGroup workGroup;
    private ServerBootstrap b;

    public CustomEchoServer(int port) {
        this.port = port;
        //第一个线程组是用于接收Client连接的
        bossGroup = new NioEventLoopGroup(1);
        //第二个线程组是用于消息的读写操作
        workGroup = new NioEventLoopGroup(2);
        //服务端辅助启动类
        b = new ServerBootstrap();
        b.group(bossGroup, workGroup)
                //需要指定使用NioServerSocketChannel这种类型的通道
                .channel(NioServerSocketChannel.class)
                //向ChannelPipeline里添加业务处理handler
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(65535,
                                0,2,0,2));
                        //给发送出去的消息增加长度字段 2个字节
                        ch.pipeline().addLast(new LengthFieldPrepender(2));
                        ch.pipeline().addLast(new JsonDecoder());//自定义的json解码处理器
                        ch.pipeline().addLast(new JsonEncoder());//自定义的json编码处理器
                        ch.pipeline().addLast(new CustomEchoServerInHandler());
                    }
                });
    }

    /**
     * 启动
     * @throws InterruptedException
     */
    public void start() throws InterruptedException {
        try {
            //绑定到端口,阻塞等待直到完成
            b.bind(this.port).sync();
            System.out.println("服务器启动成功");
        } finally {

        }
    }

    /**
     * 资源优雅释放
     */
    public void close() {
        try {
            if (bossGroup != null)
            bossGroup.shutdownGracefully().sync();
            if (workGroup != null)
            workGroup.shutdownGracefully().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException, IOException {
        int port = 9999;
        CustomEchoServer echoServer = new CustomEchoServer(port);
        try {
            echoServer.start();
            //防止主程序退出
            System.in.read();
        } finally {
            echoServer.close();
        }

    }
}

服务端处理类:可以看到服务端的处理类直接接受user对象。然后给该对象设置好name和age,在写会客户端。消息会经由JsonEncoder进行编码,将对象转为json字符串,然后将字符串转为网络字节数据。最后经由LengthFieldPrepender加上数据的长度域。

public class CustomEchoServerInHandler extends SimpleChannelInboundHandler<User> {
    /**
     * 服务端读取到网络数据后的处理
     * @param ctx
     * @param user
     * @throws Exception
     */
    @Override
    public void channelRead0(ChannelHandlerContext ctx, User user)
            throws Exception {
        System.out.println("CustomEchoServerInHandler" + user);
        user.setName("hello");
        user.setAge(18);
        ctx.writeAndFlush(user);
    }

    /**
     * exceptionCaught()事件处理方法当出现Throwable对象才会被调用
     * 即当Netty由于IO错误或者处理器在处理事件时抛出的异常时
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

 

客户端的代码

public class CustomEchoClient {

    private final int port;
    private final String host;
    private Channel channel;
    private EventLoopGroup group;
    private Bootstrap b;

    public CustomEchoClient(String host, int port) {
        this.host = host;
        this.port = port;
        //客户端启动辅助类
        b = new Bootstrap();
        //构建线程组 处理读写
        group = new NioEventLoopGroup();
        b.group(group)
                //指明使用NIO进行网络通讯
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(65535,
                                0,2,0,2));
                        /*给发送出去的消息增加长度字段*/
                        ch.pipeline().addLast(new LengthFieldPrepender(2));
                        ch.pipeline().addLast(new JsonDecoder());//自定义的json解码处理器
                        ch.pipeline().addLast(new JsonEncoder());//自定义的json编码处理器
                        ch.pipeline().addLast(new CustomEchoClientInHandler());
                    }
                });
    }

    public void start() {
        try {
            //连接到远程节点,阻塞等待直到连接完成
            ChannelFuture f = b.connect(new InetSocketAddress(host, port)).sync();
            //同步获取channel(通道,实际上是对socket的封装支持读取和写入)
            channel = f.sync().channel();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 发送消息
     * @param user
     * @return
     */
    public boolean send(User user) {
        channel.writeAndFlush(user);
        return true;
    }

    /**
     * 关闭释放资源
     */
    public void close(){
        try {
            if (group != null)
            group.shutdownGracefully().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException, IOException {
        CustomEchoClient client = new CustomEchoClient("127.0.0.1", 9999);
        try {
            client.start();
            for (int i = 0; i < 1; i++){
                User user = new User();
                user.setId(i);
                client.send(user);
            }
            //防止程序退出
            System.in.read();
        }finally {
            client.close();
        }
    }
}

 

public class CustomEchoClientInHandler extends SimpleChannelInboundHandler<User> {
    /**
     * 客户端读取到数据
     * @param ctx
     * @param user
     * @throws Exception
     */
    @Override
    public void channelRead0(ChannelHandlerContext ctx, User user)
            throws Exception {
        System.out.println("CustomEchoClientInHandler" + user);
    }

    /**
     * exceptionCaught()事件处理方法当出现Throwable对象才会被调用
     * 即当Netty由于IO错误或者处理器在处理事件时抛出的异常时
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

 

启动服务端和客户端并发送消息,可以看到服务端收到了消息,客户端也收到了服务端返回的消息

1279d4fcd617129d280b6ff23b754a4817f.jpg

1de2b29b1910676c29720c3f5ec105034db.jpg

转载于:https://my.oschina.net/suzheworld/blog/3006473

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值