Netty 编解码器

190 阅读3分钟

Netty 编解码

在网络中数据是以字节码二进制的形式传输的,所以我们在使用 Netty 传输数据的时候,需要将我们传输的数据转换为 二进制的形式 进行传输,所以不管是我们要传输字符串,还是对象,都需要将其序列化成二进制的形式。而使用编解码器可以省去我们每次传输数据手动进行编码和解码的步骤。

相关组件

在聊编解码器之前,我们先来看看相关的组件,ChannelHandler、ChannelPipeLine

ChannelHandler

ChannelHandler 是处理 入站数据 出站数据 的容器。我们可以继承实现该接口或(ChannelInboundHandlerAdapter、ChannelOutboundHandlerAdapter),就可以接受入站 OR 出站事件和数据,这些数据会被后续的处理器链进行处理

那么其实在我们开发 Netty 相关代码时最主要的就是编写各类的 Handler ,那么编解码器也是属于 Handler 的一种。

在说 ChannelPipeline 前我们先解释下什么是 入站 和 出站,这是一个相对的概念

站在 Server 服务端的角度 出发,入站是数据流向 从 客户端 到 服务端出站 反之,从 服务端 到 客户端

站在 Client 客户端的角度 出发,入站是数据流向 从 服务端 到 客户端出站 反之,从 客户端 到 服务端

ChannelPipeline 在这里插入图片描述 ChannelPipeline 如上图所示,是承载 ChannelHandler 的一个链路,是双向链表结构,出站事件的调用是从 头到尾部 即 tail -> head 链路中仅 outbound 生效 。入站则为 head -> tail 链路中仅 inbound 生效

编解码器

编解码器同样也属于 ChannelHandler 的一员,编码器继承实现 ChannelOutboundHandler解码器继承实现 ChannelInboundHandler 其关键作用就是省去我们自行手动转换二进制字节流的过程。处理出站 tail -> head 最后编码,处理入站 head->tail 需要先解码,所以在 pipline 中需要先 add 编解码器后再添加你需要处理业务逻辑的 handler ,。

Netty 提供的编解码器: 编解码字符串 StringEncoder 和 StringDecoder 编解码对象 ObjectEncoder 和 ObjectDecoder

在实际开发中为了实现更加高效的编解码我们会选择使用 protobuf 是由谷歌开发出品的一种数据传输格式。 但是 protobuf 需要维护大量的 proto 文件比较麻烦,推荐可以使用 protostuff。 protostuff 是一个基于 protobuf 实现的序列化方法,它较于 protobuf 最明显的好处是,在几乎不损耗性能的情况下做到了不用我们写 .proto 文件来实现序列化。

使用前先引入 maven 依赖

<dependency>
    <groupId>com.dyuproject.protostuff</groupId>
    <artifactId>protostuff-api</artifactId>
    <version>1.0.10</version>
</dependency>
<dependency>
    <groupId>com.dyuproject.protostuff</groupId>
    <artifactId>protostuff-core</artifactId>
    <version>1.0.10</version>
</dependency>
<dependency>
    <groupId>com.dyuproject.protostuff</groupId>
    <artifactId>protostuff-runtime</artifactId>
    <version>1.0.10</version>
</dependency>
import com.dyuproject.protostuff.LinkedBuffer;
import com.dyuproject.protostuff.ProtostuffIOUtil;
import com.dyuproject.protostuff.Schema;
import com.dyuproject.protostuff.runtime.RuntimeSchema;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * protostuff 序列化工具类,基于protobuf封装
 */
public class ProtostuffUtil {

    private static Map<Class<?>, Schema<?>> cachedSchema = new ConcurrentHashMap<Class<?>, Schema<?>>();

    private static <T> Schema<T> getSchema(Class<T> clazz) {
        @SuppressWarnings("unchecked")
        Schema<T> schema = (Schema<T>) cachedSchema.get(clazz);
        if (schema == null) {
            schema = RuntimeSchema.getSchema(clazz);
            if (schema != null) {
                cachedSchema.put(clazz, schema);
            }
        }
        return schema;
    }

    /**
     * 序列化
     *
     * @param obj
     * @return
     */
    public static <T> byte[] serializer(T obj) {
        @SuppressWarnings("unchecked")
        Class<T> clazz = (Class<T>) obj.getClass();
        LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
        try {
            Schema<T> schema = getSchema(clazz);
            return ProtostuffIOUtil.toByteArray(obj, schema, buffer);
        } catch (Exception e) {
            throw new IllegalStateException(e.getMessage(), e);
        } finally {
            buffer.clear();
        }
    }

    /**
     * 反序列化
     *
     * @param data
     * @param clazz
     * @return
     */
    public static <T> T deserializer(byte[] data, Class<T> clazz) {
        try {
            T obj = clazz.newInstance();
            Schema<T> schema = getSchema(clazz);
            ProtostuffIOUtil.mergeFrom(data, obj, schema);
            return obj;
        } catch (Exception e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
    }

    public static void main(String[] args) {
        byte[] userBytes = ProtostuffUtil.serializer(new User(1998, "dyingGQ"));
        User user = ProtostuffUtil.deserializer(userBytes, User.class);
        System.out.println(user);
    }
}

写在最后:

本文带你聊了聊 Netty 编解码器的主要作用,和最基础的实现,对编解码器做了一个基本的阐述,下篇文章会带大家聊聊 数据流传输中必须面对的粘包拆包的问题,同时配合来讲解如何自定义一个工业级的编解码器。

我是 dying 搁浅 我看了一场 95 小时的电影也没能等来你的点赞关注收藏,我想这不是你不够喜欢我,而是我看的电影不够长……

在这里插入图片描述