服务端与客户端的通讯协议
服务端与客户端使用协议进行通讯的过程大致如下:
- 客户端将一个Java对象按照通讯协议转换成二进制数据包;
- 通过网络将二进制数据包发送到服务端,传输过程由TCP/IP协议负责,与应用层无关;
- 服务端接收到数据包后,按照协议取出二进制数据包中的相应字段,然后根据取出的数据生成Java对象,然后再由业务逻辑处理层解析Java对象;
- 待服务端处理完成后,如果要反馈响应信息,则重复123步;
通讯协议的简单设计
简单协议示意图:
-
头部标志位
头部标志位用于区分是否是目标数据包,通常为固定的几个字节的字段;
-
序列化算法
表示此数据包应该使用何种序列化算法转化为Java对象;
-
指令
业务相关字段,表示此数据包属于何种指令;
-
数据体长度
表示数据体的长度,用于获取正确的数据体;
-
数据体
信息;
通讯协议的实现
用于保存指令集常量的Command.java
public interface Command {
/**
* 登录请求
*/
byte LOGIN_REQUEST = 1;
}
用于保存序列化算法常量的SerializerAlgorithm.java
public interface SerializerAlgorithm {
/**
* 使用JSON的序列化方式
*/
byte JSON = 1;
}
基础数据包实体类BaseFrame.java
/**
* @program: learnnetty
* @description: 基础类
* @create: 2020-05-06 10:57
**/
public abstract class BaseFrame {
/**
* 版本号
*/
private Byte version = 1;
/**
* 获取指令
* @return 指令
*/
public abstract Byte getCommand();
}
用于处理登录请求的LoginFrame.java
/**
* @program: learnnetty
* @description: 登录用
* @create: 2020-05-06 10:58
**/
public class LoginFrame extends BaseFrame {
/**
* 用户ID
*/
private Integer userId;
/**
* 用户名
*/
private String userName;
/**
* 密码
*/
private String password;
@Override
public Byte getCommand() {
return Command.LOGIN_REQUEST;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
序列化方法基础接口Serializer.java
public interface Serializer {
byte JSON_SERIALIZER = 1;
/**
* 获取序列化算法
* @return 算法编号
*/
byte gerSerializerAlgorithm();
/**
* 对象序列化
* @param o 对象
* @return 序列化后
*/
byte[] serialize(Object o);
/**
* 反序列化
* @param clazz 目标对象类型
* @param bytes 数据包
* @param <T> 类型
* @return 对象
*/
<T> T deserialize(Class<T> clazz, byte[] bytes);
/**
* 默认序列化方式
*/
Serializer DEFAULT = new JSONSerializer();
}
用于Java对象与数据包互换的ParseFrame.java
/**
* @program: learnnetty
* @description: 数据包的转换
* @create: 2020-05-06 11:09
**/
public class ParseFrame {
private static final int HEAD_FLAG = 0x990718;
/**
* 数据包的序列化
* @param frame 对象
* @return 完成序列化
*/
public ByteBuf encode(BaseFrame frame){
//此处使用IO读写相关的内存,直接内存不受JVM管理,写到IO缓冲区效率更高
ByteBuf buf = ByteBufAllocator.DEFAULT.ioBuffer();
byte[] data = Serializer.DEFAULT.serialize(frame);
//组装报文
buf.writeInt(HEAD_FLAG);
buf.writeByte(Serializer.DEFAULT.gerSerializerAlgorithm());
buf.writeByte(frame.getCommand());
buf.writeInt(data.length);
buf.writeBytes(data);
return buf;
}
/**
* 逆序列化
* @param byteBuf 数据
* @return 对象
*/
public BaseFrame decode(ByteBuf byteBuf){
//跳过头部标志位
byteBuf.skipBytes(4);
//获取序列化算法
byte serializerAlgorithm = byteBuf.readByte();
//获取指令
byte command = byteBuf.readByte();
//获取数据长度
int length = byteBuf.readInt();
//获取数据
byte[] data = new byte[length];
byteBuf.readBytes(data);
Class<? extends BaseFrame> requestType = RequestUtil.getRequestType(command);
Serializer serializer = SerializerUtil.getSerializer(serializerAlgorithm);
if (requestType != null && serializer != null){
return serializer.deserialize(requestType, data);
}
return null;
}
}
根据请求获取对应Java对象Class的RequestUtil.java
/**
* @program: learnnetty
* @description: 获取请求的类型
* @create: 2020-05-06 11:28
**/
public class RequestUtil {
/**
* 获取指定的类型
* @param command 命令序号
* @return 类型
*/
public static Class<? extends BaseFrame> getRequestType(byte command){
switch (command){
case Command.LOGIN_REQUEST:
return LoginFrame.class;
default:
return null;
}
}
}
用于获取序列化算法的SerializerUtil.java
/**
* @program: learnnetty
* @description: 序列化工具类
* @create: 2020-05-06 11:31
**/
public class SerializerUtil {
/**
* 获取序列化方式
* @param serializeAlgorithm 序列化标志位
* @return 序列化方式
*/
public static Serializer getSerializer(byte serializeAlgorithm ){
switch (serializeAlgorithm){
case Serializer.JSON_SERIALIZER:
return new JSONSerializer();
default:
return null;
}
}
}