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日

相关文章

  • 利用Java代码实现区块链技术

    利用Java代码实现区块链技术的攻略 什么是区块链? 区块链是一种去中心化的分布式数据库技术,它以区块为基本单位,每个区块包含了一些交易数据和前一个区块的哈希值。 实现区块链的基本原理 实现区块链至少要考虑以下几个方面: 加密方法 区块结构 工作量证明 网络通信 加密方法 区块链技术中,加密方法包括了哈希算法和公钥加密算法。其中,哈希算法是对区块数据的摘要算…

    Java 2023年5月19日
    00
  • Spring MVC学习笔记之json格式的输入和输出

    下面就为你讲解“Spring MVC学习笔记之json格式的输入和输出”的完整攻略。 什么是JSON格式? JSON是一种轻量级的数据交换格式,其全称为“JavaScript Object Notation”,它采用文本格式来表示数据对象。JSON格式可用于数据的序列化和反序列化,具有易读性、易编写和易于解析的特点,被广泛用于Web应用程序间的数据传输。 S…

    Java 2023年6月15日
    00
  • JS定时刷新页面及跳转页面的方法

    下面是JS定时刷新页面及跳转页面的方法攻略。 定时刷新页面 使用setInterval() 在JS中使用setInterval()函数可以实现定时刷新页面的效果。setInterval()函数有两个参数,第一个参数是要执行的函数,第二个参数是执行该函数的间隔时间,单位是毫秒。 下面是一个示例代码: setInterval(function(){ locati…

    Java 2023年6月15日
    00
  • java 中Spring task定时任务的深入理解

    对于Java中Spring task定时任务的深入理解,我们可以通过以下步骤来进行实现: 1. 添加依赖 首先,我们需要在项目中添加Spring task的相关依赖,该依赖包括: <dependency> <groupId>org.springframework</groupId> <artifactId>sp…

    Java 2023年6月15日
    00
  • 关于RequestMapping注解的作用说明

    关于@RequestMapping注解的作用说明 @RequestMapping注解是Spring框架中最常用的注解之一,它可以用来映射URL和处理HTTP请求,是控制器中的一个方法级别的注解。下面将详细介绍@RequestMapping的作用和使用说明。 基本作用 @RequestMapping注解用于将指定的URL映射到处理请求的控制器方法上。当请求UR…

    Java 2023年6月15日
    00
  • spring-boot-plus V1.4.0发布 集成用户角色权限部门管理(推荐)

    Spring Boot Plus V1.4.0发布 Spring Boot Plus是一个基于SpringBoot的项目快速开发脚手架,版本 V1.4.0 提供了用户角色权限部门管理的集成,方便用户快速搭建管理后台。 安装 首先,我们需要安装Java和Maven,参考:- Java 安装教程- Maven 安装教程 Spring Boot Plus 是通过M…

    Java 2023年5月20日
    00
  • java睡眠排序算法示例实现

    Java睡眠排序算法示例实现是一种非常特殊的排序算法,它通过将数组中每个元素对应的线程让其进入睡眠状态,进而实现了对数组内元素的排序。下面我将详细介绍Java睡眠排序算法的完整攻略,并附上两个示例,希望对您有所帮助。 Java睡眠排序算法的基本原理 Java睡眠排序算法的基本原理是,对于一个包含n个元素的待排序数组,我们可以创建n个线程,每个线程对应数组中的…

    Java 2023年5月19日
    00
  • SpringBoot整合mybatis/mybatis-plus实现数据持久化的操作

    下面是 SpringBoot 整合 MyBatis/MyBatis-Plus 实现数据持久化的操作攻略: 环境准备 SpringBoot 2.x MyBatis/MyBatis-Plus MySQL 数据库 Maven 依赖配置 首先需要在 pom.xml 文件中配置 MyBatis/MyBatis-Plus 以及 MySQL 数据库依赖: <depe…

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