![跟闪电侠学Netty:Netty即时聊天实战与底层原理](https://wfqqreader-1252317822.image.myqcloud.com/cover/384/43738384/b_43738384.jpg)
8.3 通信协议的实现
我们把Java对象根据协议封装成二进制数据包的过程称为编码,把从二进制数据包中解析出Java对象的过程称为解码。在学习如何使用Netty进行通信协议的编解码之前,我们先来定义一下客户端与服务端通信的Java对象。
8.3.1 Java对象
如下代码定义通信过程中的Java对象。
![](https://epubservercos.yuewen.com/6C3355/23020652909769306/epubprivate/OEBPS/Images/txt009_4.jpg?sign=1738843188-Xt00wpsEfdttcljNDV4kIdyo0Hnn2q09-0-5297fd8483650fd7fae7b5ea921ee0a2)
1.以上是通信过程中Java对象的抽象类。可以看到,我们定义了一个版本号(默认值为1),以及一个获取指令的抽象方法。所有的指令数据包都必须实现这个方法,这样我们就可以知道某种指令的含义。
2.@Data注解由lombok提供,它会自动帮我们生产getter、setter方法,减少大量重复代码,推荐使用。
接下来,以客户端登录请求为例,定义登录请求数据包。
![](https://epubservercos.yuewen.com/6C3355/23020652909769306/epubprivate/OEBPS/Images/txt009_5.jpg?sign=1738843188-nFSV0rbl2wI7LiifYbIucEMzNALrasn7-0-dbfe768bb528d7b446703690dbc8510e)
登录请求数据包继承自Packet定义了3个字段,分别是用户ID、用户名和密码。其中最为重要的就是覆盖了父类的getCommand()方法,值为常量LOGIN_REQUEST。
Java对象定义完成之后,我们需要定义一种规则,如何把一个Java对象转换成二进制数据,这个规则叫作Java对象的序列化。
8.3.2 序列化
如下代码定义序列化接口。
![](https://epubservercos.yuewen.com/6C3355/23020652909769306/epubprivate/OEBPS/Images/txt009_6.jpg?sign=1738843188-FP3ixSfI3zrdeu2Uz8Gkr6MyJtUfO4n6-0-445e9ed2f192804db595078baf29b0ca)
序列化接口有3个方法:getSerializerAlgorithm()方法获取具体的序列化算法标识,serialize()方法将Java对象转换成字节数组,deserialize()方法将字节数组转换成某种类型的Java对象。在本书中,我们使用最简单的JSON序列化方式,将阿里巴巴的Fastjson作为序列化框架。
![](https://epubservercos.yuewen.com/6C3355/23020652909769306/epubprivate/OEBPS/Images/txt009_7.jpg?sign=1738843188-vYSZhb4KRJumJw30xV56OQUTRGHDWl7c-0-7415f5a1a0497fe1d3e3542490eef913)
然后,我们定义一下序列化算法的类型,以及默认序列化算法。
![](https://epubservercos.yuewen.com/6C3355/23020652909769306/epubprivate/OEBPS/Images/txt009_8.jpg?sign=1738843188-lVGUTSMxyiOnouClWZORI5VJoJpRIdyI-0-ecc2c8dcd1f68c8f170f5cafc5a0d3b8)
这样,我们就实现了序列化相关的逻辑。如果想要实现其他序列化算法,则只需要继承Serializer,然后定义序列化算法的标识,再覆盖两个方法即可。
序列化定义了Java对象与二进制数据的互转过程。接下来,我们学习如何把这部分数据编码到通信协议的二进制数据包中去。
8.3.3 编码:封装成二进制数据的过程
PacketCodeC.java
![](https://epubservercos.yuewen.com/6C3355/23020652909769306/epubprivate/OEBPS/Images/txt009_9.jpg?sign=1738843188-yO6zfIWnaCOi9tLuFZkbqCRmv7s3PkTh-0-ce30e29bbe9c6b9aa996f6aa9e872014)
编码过程分为3个步骤。
1.我们需要创建一个ByteBuf,这里我们调用Netty的ByteBuf分配器来创建,ioBuffer()方法会返回适配IO读写相关的内存,它会尽可能创建一个直接内存。直接内存可以理解为不受JVM堆管理的内存空间,写到IO缓冲区的效果更高。
2.将Java对象序列化成二进制数据包。
3.我们对照本章开头的协议设计和上一章ByteBuf的API,逐个往ByteBuf写入字段,即实现了编码过程。到此,编码过程结束。
一端实现编码之后,Netty会将此ByteBuf写到另一端。另一端获得的也是一个ByteBuf对象。基于这个ByteBuf对象,就可以反解出在对端创建的Java对象,这个过程被称作解码,下面我们就来分析这个过程。
8.3.4 解码:解析Java对象的过程
PacketCodeC.java
![](https://epubservercos.yuewen.com/6C3355/23020652909769306/epubprivate/OEBPS/Images/txt009_10.jpg?sign=1738843188-ZT1FOfjcfGKmLzWx14gKg3CSWXNwdo9w-0-801039b522d8f2197ae4539fa6e2c942)
解码的流程如下。
1.我们假定decode方法传递进来的ByteBuf已经是合法的(后面我们再来实现校验),即首个4字节是我们定义的魔数0x12345678,这里我们调用skipBytes跳过这个4字节。
2.我们暂时不关注协议版本,通常在没有遇到协议升级的时候,暂时不处理这个字段。因为在绝大多数情况下,几乎用不着这个字段,但仍然需要暂时保留。
3.我们调用ByteBuf的API分别获得序列化算法标识、指令、数据包的长度。
4.我们根据获得的数据包的长度取出数据,通过指令获得该数据包对应的Java对象的类型,根据序列化算法标识获得序列化对象,将字节数组转换为Java对象。至此,解码过程结束。
由此可以看到,解码过程与编码过程正好是相反的过程。