Netty(5)-自定义通信协议包的编码解码

一.何为通信协议

这里指的通信协议并不是狭义的TCP、UDP这类【标准通信协议】,而是指的Netty用于客户端与服务端之间数据交互的【自定义通信协议】。无论是使用 Netty 还是原始的 Socket 编程,基于 TCP 通信的数据包格式均为二进制,协议指的就是客户端与服务端事先商量好的,每一个二进制数据包中每一段字节分别代表什么含义的规则。如下图所示为一个简单的登陆所用的协议:
在这里插入图片描述

在这个数据包中,第一个字节为 1 表示这是一个登录指令,接下来是用户名和密码,这两个值以 \0 分割,客户端发送这段二进制数据包到服务端,服务端就能根据这个协议来取出用户名密码,进行登录逻辑。
在这里插入图片描述
如何设计一个通信协议包?

  1. 首先,第一个字段是魔数,通常情况下为固定的几个字节(我们这边规定为4个字节)。 为什么需要这个字段,而且还是一个固定的数?假设我们在服务器上开了一个端口,比如 80 端口,如果没有这个魔数,任何数据包传递到服务器,服务器都会根据自定义协议来进行处理,包括不符合自定义协议规范的数据包。例如,我们直接通过 http://服务器ip 来访问服务器(默认为 80 端口), 服务端收到的是一个标准的 HTTP 协议数据包,但是它仍然会按照事先约定好的协议来处理 HTTP 协议,显然,这是会解析出错的。而有了这个魔数之后,服务端首先取出前面四个字节进行比对,能够在第一时间识别出这个数据包并非是遵循自定义协议的,也就是无效数据包,为了安全考虑可以直接关闭连接以节省资源。在 Java 的字节码的二进制文件中,开头的 4 个字节为0xcafebabe 用来标识这是个字节码文件,亦是异曲同工之妙。

  2. 接下来一个字节为版本号,通常情况下是预留字段,用于协议升级的时候用到,有点类似 TCP 协议中的一个字段标识是 IPV4 协议还是 IPV6 协议,大多数情况下,这个字段是用不到的,不过为了协议能够支持升级,我们还是先留着。

  3. 第三部分,序列化算法表示如何把 Java 对象转换二进制数据以及二进制数据如何转换回 Java 对象,比如 Java 自带的序列化,json,hessian 等序列化方式。

  4. 第四部分的字段表示指令,关于指令相关的介绍,我们在前面已经讨论过,服务端或者客户端每收到一种指令都会有相应的处理逻辑,这里,我们用一个字节来表示,最高支持256种指令,对于我们这个 IM 系统来说已经完全足够了。

  5. 接下来的字段为数据部分的长度,占四个字节。

  6. 最后一个部分为数据内容,每一种指令对应的数据是不一样的,比如登录的时候需要用户名密码,收消息的时候需要用户标识和具体消息内容等等。

在这里插入图片描述

二.代码实现

  • 项目目录

在这里插入图片描述

  • utils/command
/*
 * @Author ARong
 * @Description 定义指令集
 * @Date 2020/2/4 8:58 下午
 **/
public interface Command {
    byte LOGIN = 1;
}
  • utils/MySerializer

/*
 * @Author ARong
 * @Description 定义序列化器
 * @Date 2020/2/4 9:06 下午
 **/
public interface MySerializer {

    /*
     * @Author ARong
     * @Description 将Java对象转化为二进制字节流
     * @Date 2020/2/4 9:07 下午
     * @Param object
     * @return byte[]
     **/
    byte[] serialize(Object object);


    /*
     * @Author ARong
     * @Description 二进制转换成 java 对象
     * @Date 2020/2/4 9:08 下午
     * @Param [clazz, bytes]
     * @return T
     **/
    <T> T deserialize(Class<T> clazz, byte[] bytes);
}
  • utils/Serialize
/*
 * @Author ARong
 * @Description 定义序列化方式
 * @Date 2020/2/4 9:05 下午
 **/
public interface Serialize {
    byte JSON = 1;
}
  • utils/SerializerFactory
/*
 * @Author ARong
 * @Description 序列化器工厂
 * @Date 2020/2/4 8:56 下午
 **/
public class SerializerFactory {
    /*
     * @Author ARong
     * @Description 通过序列化名称获取相应的序列化器
     * @Date 2020/2/4 9:17 下午
     * @Param [serName]
     * @return io_learn.netty.part4_protocol.utils.MySerializer
     **/
    public static MySerializer getSerializer(byte serName) {
        if (serName == Serialize.JSON) {
            return new MyJsonSerializer();
        }
        return null;
    }
}

  • utils/MyJsonSerializer
/**
 * @Auther: ARong
 * @Date: 2020/2/4 9:09 下午
 * @Description:
 */
public class MyJsonSerializer implements MySerializer {
    /*
     * @Author ARong
     * @Description 将Java对象转化为二进制字节流
     * @Date 2020/2/4 9:07 下午
     * @Param object
     * @return byte[]
     **/
    @Override
    public byte[] serialize(Object object) {
        return JSON.toJSONBytes(object);
    }

    /*
     * @Author ARong
     * @Description 二进制转换成 java 对象
     * @Date 2020/2/4 9:08 下午
     * @Param [clazz, bytes]
     * @return T
     **/
    @Override
    public <T> T deserialize(Class<T> clazz, byte[] bytes) {
        return JSON.parseObject(bytes, clazz);
    }
}
  • utils/PacketUtil
/**
 * @Auther: ARong
 * @Date: 2020/2/4 9:14 下午
 * @Description: 用于包编码和解码
 */
public class PacketUtil {
    /*
     * @Author ARong
     * @Description 将Packet按照约定序列化方式编码成ByteBuf
     * @Date 2020/2/4 9:15 下午
     * @Param [packet]
     * @return io.netty.buffer.ByteBuf
     **/
    public static ByteBuf encode(Packet packet) {
        // 创建ByteBuf对象
        ByteBuf byteBuf = ByteBufAllocator.DEFAULT.ioBuffer();
        // 获取序列化器序列化对象
        MySerializer serializer = SerializerFactory.getSerializer(packet.getSerMethod());
        byte[] data = serializer.serialize(packet);
        // 按照通信协议填充ByteBUf
        byteBuf.writeInt(packet.getMagic());// 魔数
        byteBuf.writeByte(packet.getVersion()); // 版本号
        byteBuf.writeByte(packet.getSerMethod()); // 序列化方式
        byteBuf.writeByte(packet.getCommand()); // 指令
        byteBuf.writeInt(data.length);// 数据长度
        byteBuf.writeBytes(data); // 数据
        return byteBuf;
    }

    /*
     * @Author ARong
     * @Description 将ByteBuf按照约定序列化方式解码成Packet
     * @Date 2020/2/4 9:21 下午
     * @Param [byteBuf]
     * @return io_learn.netty.part4_protocol.packet.Packet
     **/
    public static Packet decode(ByteBuf byteBuf) {
        // 暂不判断魔数,跳过
        byteBuf.skipBytes(4);
        // 暂不判断魔数版本号,跳过
        byteBuf.skipBytes(1);
        // 获取序列化方式
        byte serMethod = byteBuf.readByte();
        // 获取指令
        byte command = byteBuf.readByte();
        // 获取数据包长度
        int length = byteBuf.readInt();
        // 获取存储数据的字节数组
        byte[] data = new byte[length];
        byteBuf.readBytes(data);
        // 反序列化数据,获取Packet
        Class<? extends Packet> packetType = getPacketType(command);
        Packet res = SerializerFactory.getSerializer(serMethod).deserialize(packetType, data);
        return res;
    }


    /*
     * @Author ARong
     * @Description 通过指令获取相应的Packet
     * @Date 2020/2/4 9:31 下午
     * @Param [commond]
     * @return io_learn.netty.part4_protocol.packet.Packet
     **/
    public static Class<? extends Packet> getPacketType(byte commond) {
        if (commond == Command.LOGIN) {
            return LoginPacket.class;
        }
        return null;
    }
}

  • packet/Packet
/**
 * @Auther: ARong
 * @Date: 2020/2/4 8:44 下午
 * @Description: 自定义通信协议的抽象包
 */
@Data
public abstract class Packet {
    private int magic; // 魔数
    private byte version; // 版本号
    private byte serMethod; // 的序列化/反序列化方式
    private byte command; // 指令
}
  • packet/LoginPacket
/**
 * @Auther: ARong
 * @Date: 2020/2/4 8:52 下午
 * @Description: 用以登陆的登陆协议包
 */
@Data
public class LoginPacket extends Packet {
    private int magic = 20202020; // 魔数
    private byte version = 1; // 版本号
    private byte serMethod = Serialize.JSON; // 序列化反序列化方式为Json
    private byte command = Command.LOGIN; // 登陆指令
    // ---------以下为登陆数据----------
    private long userId;
    private String name;
    private String password;


    @Override
    public String toString() {
        return "LoginPacket{" +
                "magic=" + magic +
                ", version=" + version +
                ", serMethod=" + serMethod +
                ", command=" + command +
                ", userId=" + userId +
                ", name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}
  • server/NettyServer
/**
 * @Auther: ARong
 * @Date: 2020/2/4 9:13 下午
 * @Description: Netty Server
 */
public class NettyServer {
    public static void main(String[] args) {
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();
        serverBootstrap.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(
                        new ChannelInitializer<SocketChannel>() {
                            @Override
                            // 初始化channel
                            protected void initChannel(SocketChannel ch) throws Exception {
                                ch.pipeline().addLast(new MyServerHandler());
                            }
                        }
                ).bind(8000);
    }
}

  • server/NettyClient
/**
 * @Auther: ARong
 * @Date: 2020/2/5 1:28 下午
 * @Description: NettyClient
 */
public class NettyClient {
    public static void main(String[] args) throws InterruptedException {
        Bootstrap bootstrap = new Bootstrap();
        NioEventLoopGroup group = new NioEventLoopGroup();

        bootstrap.group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                // 责任链模式,添加第一次连接的客户端处理逻辑
//                ch.pipeline().addLast(new FirstClientHandler());
            }
        });

        Channel channel = bootstrap.connect("127.0.0.1", 8000).channel();
        LoginPacket packet = new LoginPacket();
        packet.setUserId(123l);
        packet.setName("ARong");
        packet.setPassword("123");
        ByteBuf byteBuf = PacketUtil.encode(packet);
        System.out.println("客户端正在发送包");
        channel.writeAndFlush(byteBuf);
    }
}

  • handler/MyServerHandler
/**
 * @Auther: ARong
 * @Date: 2020/2/5 1:17 下午
 * @Description:
 */
public class MyServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("服务端接收到消息,正在解码");
        Packet packet = PacketUtil.decode((ByteBuf) msg);
        if (packet.getCommand() == Command.LOGIN) {
            packet = (LoginPacket)packet;
        }
        System.out.println(packet);
    }
}

在这里插入图片描述

发布了309 篇原创文章 · 获赞 205 · 访问量 30万+

猜你喜欢

转载自blog.csdn.net/pbrlovejava/article/details/104186101