专栏首页猿天地高性能NIO框架Netty-对象传输

高性能NIO框架Netty-对象传输

上篇文章高性能NIO框架Netty入门篇我们对Netty做了一个简单的介绍,并且写了一个入门的Demo,客户端往服务端发送一个字符串的消息,服务端回复一个字符串的消息,今天我们来学习下在Netty中怎么使用对象来传输数据。

上篇文章中传输字符串我们用的是框架自带的StringEncoder,StringDecoder编解码器,现在想要通过对象来传输数据,该怎么弄呢?

既然StringEncoder和StringDecoder可以传输字符串,我们来看看这2个类的源码不就知道它们到底做了一些什么工作。

StringEncoder

public class StringEncoder extends MessageToMessageEncoder<CharSequence> {
    // TODO Use CharsetEncoder instead.
    private final Charset charset;
    /**
     * Creates a new instance with the current system character set.
     */
    public StringEncoder() {
        this(Charset.defaultCharset());
    }
    /**
     * Creates a new instance with the specified character set.
     */
    public StringEncoder(Charset charset) {
        if (charset == null) {
            throw new NullPointerException("charset");
        }
        this.charset = charset;
    }
    @Override
    protected void encode(ChannelHandlerContext ctx, CharSequence msg, List<Object> out) throws Exception {
        if (msg.length() == 0) {
            return;
        }
        out.add(ByteBufUtil.encodeString(ctx.alloc(), CharBuffer.wrap(msg), charset));
    }
}

通过继承MessageToMessageEncoder,重写encode方法来进行编码操作,就是将字符串进行输出即可。

StringDecoder

public class StringDecoder extends MessageToMessageDecoder<ByteBuf> {
    // TODO Use CharsetDecoder instead.
    private final Charset charset;
    /**
     * Creates a new instance with the current system character set.
     */
    public StringDecoder() {
        this(Charset.defaultCharset());
    }
    /**
     * Creates a new instance with the specified character set.
     */
    public StringDecoder(Charset charset) {
        if (charset == null) {
            throw new NullPointerException("charset");
        }
        this.charset = charset;
    }
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
        out.add(msg.toString(charset));
    }
}

继承MessageToMessageDecoder,重写decode方法,将ByteBuf数据直接转成字符串进行输出,解码完成。

通过上面的源码分析,我们发现编解码的原理无非就是在数据传输前进行一次处理,接收后进行一次处理,在网络中传输的数据都是字节,我们现在想要传PO对象,那么必然需要进行编码和解码2个步骤,我们可以自定义编解码器来对对象进行序列化,然后通过ByteBuf的形式进行传输, 传输对象需要实现java.io.Serializable接口。

首先我们定义一个传输对象,实现序列化接口,暂时先定义2个字段,一个ID,用来标识客户端,一个内容字段,代码如下:

public class Message implements Serializable {
    private static final long serialVersionUID = -7543514952950971498L;
    private String id;
    private String content;
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getContent() {
        return content;
    }
    public void setContent(String content) {
        this.content = content;
    }
}

传输对象定好后,定义对象的编解码器。

对象编码器

将对象序列化成字节,通过ByteBuf形式进行传输,ByteBuf是一个byte存放的缓冲区,提供了读写操作。

public class MessageEncoder extends MessageToByteEncoder<Message> {
    @Override
    protected void encode(ChannelHandlerContext ctx, Message message, ByteBuf out) throws Exception {
        byte[] datas = ByteUtils.objectToByte(message);
        out.writeBytes(datas);
        ctx.flush();
    }
}

对象解码器

接收ByteBuf数据,将ByteBuf反序列化成对象

public class MessageDecoder extends ByteToMessageDecoder {
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        Object obj = ByteUtils.byteToObject(ByteUtils.read(in));
        out.add(obj);
    }
}

将上篇文章中服务端的编解码器改成对象编解码器:

public class ImServer {
    public void run(int port) {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<SocketChannel>() { 
                    @Override
                    public void initChannel(SocketChannel ch) throws Exception {
                        //实体类传输数据,jdk序列化
                        ch.pipeline().addLast("decoder", new MessageDecoder());
                        ch.pipeline().addLast("encoder", new MessageEncoder());
                        ch.pipeline().addLast(new ServerPoHandler());
                        //字符串传输数据
                        /*ch.pipeline().addLast("decoder", new StringDecoder());
                        ch.pipeline().addLast("encoder", new StringEncoder());
                        ch.pipeline().addLast(new ServerStringHandler());*/
                    }
                })
                .option(ChannelOption.SO_BACKLOG, 128)
                .childOption(ChannelOption.SO_KEEPALIVE, true);
        try {
            ChannelFuture f = bootstrap.bind(port).sync();
             f.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }
}

接下来编写服务端的消息处理类:

public class ServerPoHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        Message message = (Message) msg;
        System.err.println("server:" + message.getId());
        ctx.writeAndFlush(message);
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

服务端改造好了之后,就要改造客户端了,同样的道理,客户端和服务端的编解码器都要一致才行。

客户端连接时指定对象编解码器和对象消息处理类,代码如下:

public class ImConnection {
    private Channel channel;
    public Channel connect(String host, int port) {
        doConnect(host, port);
        return this.channel;
    }
    private void doConnect(String host, int port) {
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(workerGroup);
            b.channel(NioSocketChannel.class);
            b.option(ChannelOption.SO_KEEPALIVE, true);
            b.handler(new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel ch) throws Exception {
                    //实体类传输数据,jdk序列化
                    ch.pipeline().addLast("decoder", new MessageDecoder());
                    ch.pipeline().addLast("encoder", new MessageEncoder());
                    ch.pipeline().addLast(new ClientPoHandler());
                    //字符串传输数据
                    /*ch.pipeline().addLast("decoder", new StringDecoder());
                    ch.pipeline().addLast("encoder", new StringEncoder());
                    ch.pipeline().addLast(new ClientStringHandler());*/
                }
            });
            ChannelFuture f = b.connect(host, port).sync();
            channel = f.channel();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
}

客户端消息处理类:

/**
 * 当编解码器为实体对象时时用来接收数据
 * @author yinjihuan
 *
 */
public class ClientPoHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        Message message = (Message) msg;
        System.out.println("client:" + message.getContent());
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

客户端启动类也需要改造,将发送字符串的消息变成对象消息

public class ImClientApp {
    public static void main(String[] args) {
        String host = "127.0.0.1";
        int port = 2222;
        Channel channel = new ImConnection().connect(host, port);
        //对象传输数据
        Message message = new Message();
        message.setId(UUID.randomUUID().toString().replaceAll("-", ""));
        message.setContent("hello yinjihuan");
        channel.writeAndFlush(message);
        //字符串传输数据
        //channel.writeAndFlush("yinjihuan");
    }
}

源码参考:https://github.com/yinjihuan/netty-im

本文分享自微信公众号 - 猿天地(cxytiandi),作者:尹吉欢

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-03-03

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Netty 断线重连解决方案

    本篇文章是Netty专题的第七篇,前面六篇文章如下: 高性能NIO框架Netty入门篇 高性能NIO框架Netty-对象传输 高性能NIO框架Netty-整合k...

    猿天地
  • Netty4自带编解码器详解

    前言 本篇文章是Netty专题的第五篇,前面四篇文章如下: 高性能NIO框架Netty入门篇 高性能NIO框架Netty-对象传输 高性能NIO框架Netty-...

    猿天地
  • 高性能NIO框架Netty入门篇

    Netty介绍 Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服...

    猿天地
  • java编程_socket_套接字_网络编程_简易的GUI多线程聊天程序

    ===============================================================

    Hongten
  • 常见设计模式面试必备

    1、单例类只有一个实例对象; 2、该单例对象必须由单例类自行创建; 3、单例类对外提供一个访问该单例的全局访问点;

    挨踢小子部落阁
  • SpringBoot2.0 整合 Shiro 框架,实现用户权限管理

    Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。作为一款安全框架Shiro的设计相当巧妙。Shiro的应用不依赖...

    知了一笑
  • Java 字符流操作

         上篇文章Java 字节流操作介绍了java中基本的字节流操作,但是我们常常对于字符操作,如果使用字节流来实现输入输出就显得麻烦,我们可以使用字符流...

    Single
  • 《你必须知道的.NET》读书笔记二:小OO有大原则

    此篇已收录至《你必须知道的.Net》读书笔记目录贴,点击访问该目录可以获取更多内容。

    Edison Zhou
  • Java外观模式(门面模式Facade)

      外观模式也叫门面模式,是开发过程中使用频率非常高的一种设计模式,但非常容易理解。

    用户4919348
  • AIDL(续)

    http://blog.csdn.net/lxj1137800599/article/details/50998757 这篇文章讲的是在不同的工程文件中实现...

    提莫队长

扫码关注云+社区

领取腾讯云代金券