网络通信从编解码开始,前面的第一篇文章中,介绍过数据包的结构,这篇文章就要介绍一下拆包和组包的过程。
1. 包头字段的设计目的
A. 起始分隔符:标明一个数据包的开始部分(里面还隐含了小端模式的信息,这个小端模式可以忽略);
B. 协议的版本:当前版本是明文传输的,考虑到后期升级可能要采用密文传输包体,所以,设计了一个版本字段,当然,也可以用于协议内容的扩充,添加新的数据包类型时增加版本号的即可;
C. 频道号:这个字段的作用不明显,目前是想将包信息按照业务分类,比如:网络连接,权限认证,聊天信息等;
D. 命令字:与频道号、版本号联合起来作为包体类型的ID,参与Json串和Java对象的转换操作;
2. 包体
要传输的Json字符串,二进制的形式,采用UTF-8的编码形式;
3. 包尾
结束分隔符:标明一个数据包的结束部分,如果按照包长度解析错误之后,可以通过结束分隔符丢弃碎包,目前Demo没有完成这个功能。
package houlei.net.tcp.codec;
import houlei.net.tcp.pkg.PackageVersion;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageCodec;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.List;
public class GenericPackageCodec extends ByteToMessageCodec {
public static final int HEADER_TAIL_LENGTH = 16;
public static final int PKG_MAX_LENGTH = 2048;
// 编码过程
@Override
protected void encode(ChannelHandlerContext ctx, GenericPackage pkg, ByteBuf out) throws Exception {
switch (PackageVersion.valueOf(pkg.getVersion())) {
case V10:
encodeV10(ctx, pkg, out);
return;
default:
encodeVX(ctx, pkg, out);
return;
}
}
private void encodeVX(ChannelHandlerContext ctx, GenericPackage pkg, ByteBuf out) {
// 其他版本的编码过程没有实现,默认V10版本的处理过程
encodeV10(ctx, pkg, out);
}
private void encodeV10(ChannelHandlerContext ctx, GenericPackage pkg, ByteBuf out) {
String data = pkg.getData();
byte[] body = data==null ? new byte[0] : data.getBytes(Charset.forName("UTF-8"));
pkg.setLength((short)body.length);
if (out.isWritable((HEADER_TAIL_LENGTH + body.length))) {
out.writeBytes(GenericPackage.PKG_PREFIX);
out.writeShort(pkg.getVersion()).writeShort(pkg.getChannel()).writeShort(pkg.getCommand()).writeShort(body.length);
out.writeBytes(body);
out.writeBytes(GenericPackage.PKG_SUFFIX);
ctx.flush();
}
}
// 解码过程
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List
package houlei.net.tcp.codec;
import houlei.net.tcp.pkg.PackageFactory;
import houlei.net.tcp.pkg.PackageType;
import houlei.net.tcp.utils.JsonUtil;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageCodec;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
@Component
public class GenericPackageClassifierCodec extends MessageToMessageCodec {
@Resource
private PackageFactory packageFactory;
@Override
protected void encode(ChannelHandlerContext ctx, Object msg, List out) throws Exception {
PackageType type = packageFactory.getPackageType(msg.getClass());
if (type != null) {
String json = JsonUtil.toString(msg);
GenericPackage genericPackage = new GenericPackage();
genericPackage.setVersion(type.getVersion());
genericPackage.setChannel(type.getChannel());
genericPackage.setCommand(type.getCommand());
genericPackage.setData(json);
out.add(genericPackage);
} else {
//TODO 未知类型的对象,无法进行编码操作,应该抛出异常。
}
}
@Override
protected void decode(ChannelHandlerContext ctx, GenericPackage pg, List out) throws Exception {
Class> klass = packageFactory.getClass(pg.getVersion(), pg.getChannel(), pg.getCommand());
if (klass != null) {
String json = pg.getData();
Object pkg = JsonUtil.fromString(json, klass);
out.add(pkg);
} else {
//TODO 未知类型的对象,无法进行解码操作,应该进行特殊处理。
}
}
}
package houlei.net.tcp.codec;
import houlei.net.tcp.pkg.PackageType;
import houlei.net.tcp.utils.JsonUtil;
public class GenericPackage {
public final static byte[] PKG_PREFIX = new byte[]{(byte)0xFF,(byte)0xFE,0x06,0x08};
public final static byte[] PKG_SUFFIX = new byte[]{(byte)0xFF,(byte)0xFE,0x08,0x06};
private short version;
private short channel;
private short command;
private short length;
private String data;
public GenericPackage(){}
public GenericPackage(PackageType type) {
this.version = type.getVersion();
this.channel = type.getChannel();
this.command = type.getCommand();
}
@Override
public String toString() {
return JsonUtil.toString(this);
}
public short getVersion() {
return version;
}
public void setVersion(short version) {
this.version = version;
}
public short getChannel() {
return channel;
}
public void setChannel(short channel) {
this.channel = channel;
}
public short getCommand() {
return command;
}
public void setCommand(short command) {
this.command = command;
}
public short getLength() {
return length;
}
public void setLength(short length) {
this.length = length;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
}