Netty序列化深入理解与使用

Netty序列化深入理解与使用

简介

Netty是基于NIO实现的Java网络编程框架,它也是目前使用最为广泛的Java网络编程框架之一。在进行远程通信时,往往需要将对象进行序列化、反序列化后再进行传输,Netty自带了一些序列化方式,比如JDK序列化、Protobuf等,使用Netty内置的序列化方式可以方便地实现对象的传输。本文将详细讲解Netty序列化的使用、原理以及如何自定义序列化方式。

Netty内置的序列化方式

JDK序列化

JDK序列化是最简单、最常用的序列化方式之一。在序列化时,Java对象会被转换为二进制流,可以在网络中进行传输,收到数据后再将二进制流转换为Java对象。使用JDK序列化时,需要将对象类实现Serializable接口。

public class User implements Serializable {

    private String name;
    private int age;
    private List<String> hobbies;

    // Getter 和 Setter 省略
}

在Netty中使用JDK序列化,需要在ChannelPipeline中加入后续的处理器(如编码器和解码器)。

public class MyServerInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast(new ObjectEncoder());
        pipeline.addLast(new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
        pipeline.addLast(new MyServerHandler());
    }
}

Protobuf序列化

Protobuf是谷歌公司开源的一种高效的序列化协议。相比于JDK序列化,Protobuf序列化更加高效,可读性更强,编写方式也更加友好。使用Protobuf序列化,需要定义.proto文件并使用插件生成对应的Java文件。

proto文件示例:

syntax = "proto3";

message User {
    string name = 1;
    int32 age = 2;
    repeated string hobbies = 3;
}

生成的Java文件中包含了定义的message类。

在Netty中使用Protobuf序列化需要进行两个步骤:

  1. 定义将要序列化的类以及对应的.proto文件
  2. ChannelPipeline中加入后续的处理器(如编码器和解码器)
public class MyServerInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();

        // 定义将要序列化的类以及对应的proto文件路径
        pipeline.addLast(new ProtobufEncoder());
        pipeline.addLast(new ProtobufDecoder(UserProto.User.getDefaultInstance()));

        pipeline.addLast(new MyServerHandler());
    }
}

自定义序列化方式

Netty提供了自定义序列化方式的接口,用户可以根据自己的需求编写自己的序列化、反序列化的逻辑。

编写Encoder

我们需要编写一个实现了MessageToByteEncoder接口的编码器,将Java对象转换为二进制数据。

public class CustomEncoder extends MessageToByteEncoder<User> {

    @Override
    protected void encode(ChannelHandlerContext ctx, User msg, ByteBuf out) throws Exception {
        byte[] nameBytes = msg.getName().getBytes(Charset.forName("utf-8"));
        out.writeInt(nameBytes.length);
        out.writeBytes(nameBytes);
        out.writeInt(msg.getAge());
        out.writeInt(msg.getHobbies().size());
        for (String hobby : msg.getHobbies()) {
            byte[] hobbyBytes = hobby.getBytes(Charset.forName("utf-8"));
            out.writeInt(hobbyBytes.length);
            out.writeBytes(hobbyBytes);
        }
    }
}

编写Decoder

我们需要编写一个实现了ByteToMessageDecoder接口的解码器,将二进制数据还原为Java对象。

public class CustomDecoder extends ByteToMessageDecoder {

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        int nameLength = in.readInt();
        byte[] nameBytes = new byte[nameLength];
        in.readBytes(nameBytes);
        String name = new String(nameBytes, Charset.forName("utf-8"));

        int age = in.readInt();

        int hobbyCount = in.readInt();
        List<String> hobbies = new ArrayList<>(hobbyCount);
        for (int i = 0; i < hobbyCount; i++) {
            int hobbyLength = in.readInt();
            byte[] hobbyBytes = new byte[hobbyLength];
            in.readBytes(hobbyBytes);
            hobbies.add(new String(hobbyBytes, Charset.forName("utf-8")));
        }

        out.add(new User(name, age, hobbies));
    }
}

使用自定义的序列化方式

public class MyServerInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();

        // 使用我们上面编写的自定义Encoder和Decoder
        pipeline.addLast(new CustomEncoder());
        pipeline.addLast(new CustomDecoder());

        pipeline.addLast(new MyServerHandler());
    }
}

示例

以下是一个简单的使用自定义序列化方式的示例。

1. 客户端发送User对象

public class MyClient {

    public static void main(String[] args) throws Exception {
        EventLoopGroup group = new NioEventLoopGroup();

        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new MyClientInitializer());

            ChannelFuture f = b.connect("localhost", 8888).sync();
            f.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }
}
public class MyClientHandler extends SimpleChannelInboundHandler<User> {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        List<String> hobbies = new ArrayList<>();
        hobbies.add("reading");
        hobbies.add("coding");
        ctx.writeAndFlush(new User("Alice", 18, hobbies));
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, User msg) throws Exception {
        // do nothing
    }
}
public class MyClientInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();

        // 使用自定义编码器和解码器
        pipeline.addLast(new CustomEncoder());
        pipeline.addLast(new CustomDecoder());

        pipeline.addLast(new MyClientHandler());
    }
}

2. 服务端接收User对象

public class MyServer {

    public static void main(String[] args) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new MyServerInitializer())
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true);

            ChannelFuture f = b.bind(8888).sync();
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }
}
public class MyServerHandler extends SimpleChannelInboundHandler<User> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, User msg) throws Exception {
        System.out.println("Received user: " + msg);
    }

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

总结

以上是Netty序列化深入理解与使用的完整攻略。本文讲解了Netty内置的序列化方式、自定义序列化方式的实现以及两个示例。希望能够对读者对Netty序列化有更深的理解和掌握。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Netty序列化深入理解与使用 - Python技术站

(0)
上一篇 2023年5月20日
下一篇 2023年5月20日

相关文章

  • 详解如何在Spring Security中自定义权限表达式

    在对Spring Security中自定义权限表达式的攻略之前,先简单介绍一下权限表达式的作用: Spring Security中的权限表达式用于在方法调用或请求访问时,判断当前用户是否具有访问权限。Spring Security提供了很多默认的权限表达式,例如”hasRole()”和”hasAnyRole()”等。但是,我们有时需要根据不同的业务需求来自定…

    Java 2023年5月20日
    00
  • 一步步带你入门Java中File类

    一步步带你入门Java中File类 什么是File类? File类是Java中用来表示文件或目录的类,它可以用来创建、删除、重命名、检查文件或目录是否存在等操作。 如何创建File对象? 我们可以通过以下两种方式来创建File对象: 方法1:使用文件路径字符串创建File对象 File file = new File("path/to/file&q…

    Java 2023年6月1日
    00
  • Java SpringBoot容器注入对象详解

    Java SpringBoot容器注入对象详解 在Java SpringBoot应用程序中,我们可以通过将对象注入到容器中来实现对象之间的依赖关系,这样就能够实现更好的代码复用和测试。 什么是容器注入 容器注入是一种通过容器来管理Java对象之间依赖关系的技术,也称为依赖注入(DI)。通过依赖注入,我们可以将一个对象或多个对象自动注入到另一个对象中,从而避免…

    Java 2023年5月19日
    00
  • Struts2中Action三种接收参数形式与简单的表单验证功能

    在Struts2中,Action可以通过三种形式接收参数: 在Action类中定义属性,并提供setter方法,在Action的execute方法中使用属性接收参数。 使用ActionContext.getContext().getParameters()方法获取请求参数Map,以键值对的形式接收参数。 实现ParameterAware接口,可以通过参数Ma…

    Java 2023年5月20日
    00
  • Java 时间格式转换之impleDateFormat与Data API解析与使用

    Java 时间格式转换之 SimpleDateFormat 与 Date API 解析与使用 一、简介 在Java中,日期时间格式化和解析的主要API是 SimpleDateFormat 与 Date。 SimpleDateFormat 类可以将 Java 的日期对象转换为指定格式的字符串,也可以将格式化后的日期字符串转换成 Java 的日期对象。 Date…

    Java 2023年5月20日
    00
  • Mybatis如何使用动态语句实现批量删除(delete结合foreach)

    下面是Mybatis如何使用动态语句实现批量删除(delete结合foreach)的完整攻略。 前置知识 在了解如何使用动态语句实现批量删除之前,需要先掌握以下知识: Mybatis的基本操作 SqlSession对象 Mapper.xml配置文件 foreach标签的用法 1. 参数准备 我们假设有一个表user,里面存储了许多用户信息。我们需要批量删除其…

    Java 2023年5月20日
    00
  • Spring Boot 异步框架的使用详解

    SpringBoot异步框架的使用详解 Spring Boot提供了异步执行任务的能力。这样的好处是可以让Tomcat等容器可以释放当前线程,从而不会阻塞其他的请求,并且优化服务器资源,从而提供更好的性能。 异步框架概述 Spring Boot中异步框架主要包括异步调用和异步任务两方面。 异步调用 直接从控制器中异步执行一个函数。当这个异步函数执行完成之后,…

    Java 2023年5月15日
    00
  • Springboot集成GraphicsMagick

    Spring Boot集成GraphicsMagick的完整攻略 GraphicsMagick是一款开源的图像处理软件,可以用于处理各种图像格式。在Spring Boot中,我们可以集成GraphicsMagick来实现图像处理功能。本文将详细讲解Spring Boot集成GraphicsMagick的完整攻略,并提供两个示例。 1. 安装GraphicsM…

    Java 2023年5月15日
    00
合作推广
合作推广
分享本页
返回顶部