Netty自定义私有协议栈
自定义私有协议栈开发,其实就是自己封装一套符合自定义数据包结构的编码器和解码器,从而满足我们的业务需求。
通常我们数据包拆分,一部分为包头,一部分为包体,一个数据包就有两部分构成。
如图所示
对于数据包,我们进行细化,每个部分都有很多基本元素组成,利用这些基本元素,我们能够实现通过解析数据包和封装数据包,能轻松的实现
自定义协议栈的开发。
在包头中我们用
一个short类型来表示魔法头,
一个byte类型的来表示版本号
一个int类型来表示数据包体的长度,
一个short类型的来表示消息的commid
一个int类型的来表示序列号
在包体中我们用:
一个byte数组来表示存储的数据,
具体如图所示
在代码上我们通过编码两个实体类来描述定义的代码,为了提高代码的复用性。我们将代码定义为顶级包结构,可扩展为tcp协议数据包和udp协议数据包以及http协议数据包
如下代码所示;
消息头
NettyNetMessageHead.java
public class NettyNetMessageHead {
public static final short MESSAGE_HEADER_FLAG = 0x2425;
/**
* 魔法头
*/
private short head;
/**
* 版本号
*/
private byte version;
/**
* 长度
*/
private int length;
/**
* 命令
*/
private short cmd;
/**
* 序列号
*/
private int serial;
public NettyNetMessageHead() {
this.head = MESSAGE_HEADER_FLAG;
}
public short getHead() {
return head;
}
public void setHead(short head) {
this.head = head;
}
public byte getVersion() {
return version;
}
public void setVersion(byte version) {
this.version = version;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
public short getCmd() {
return cmd;
}
public void setCmd(short cmd) {
this.cmd = cmd;
}
public int getSerial() {
return serial;
}
public void setSerial(int serial) {
this.serial = serial;
}
消息体
NettyNetMessageBody.java
public class NettyNetMessageBody {
/**
* 存储数据
*/
private byte[] bytes;
public byte[] getBytes() {
return bytes;
}
public void setBytes(byte[] bytes) {
this.bytes = bytes;
}
}
为了提高消息传输的效率,我们采用protocolbuf来作为序列化编码方式,所以我们将消息抽象成一个抽象类,
为了提高代码复用性,我们创建一个接口。来实现消息的基本操作。
INettyMessage.java
public interface INettyMessage {
public NettyNetMessageHead getNetMessageHead();
public NettyNetMessageBody getNetMessageBody();
}
我们可以看到,消息接口中我们有连个方法,一个是getNetMessageHead()用来获取消息头。
一个是getNetMessageBody()用来获取消息体,
然后我们通过封装一个抽象类,AbstractNettyNetMessage.java.
public abstract class AbstractNettyNetMessage implements INettyMessage {
public NettyNetMessageHead nettyNetMessageHead;
public NettyNetMessageBody nettyNetMessageBody;
/**
* 增加默认属性(附带逻辑调用需要的属性)
*/
private final ConcurrentHashMap<Object, Object> attributes = new ConcurrentHashMap<Object, Object>(3);
public NettyNetMessageHead getNettyNetMessageHead() {
return nettyNetMessageHead;
}
public void setNettyNetMessageHead(NettyNetMessageHead nettyNetMessageHead) {
this.nettyNetMessageHead = nettyNetMessageHead;
}
public NettyNetMessageBody getNettyNetMessageBody() {
return nettyNetMessageBody;
}
public void setNettyNetMessageBody(NettyNetMessageBody nettyNetMessageBody) {
this.nettyNetMessageBody = nettyNetMessageBody;
}
public ConcurrentHashMap<Object, Object> getAttributes() {
return attributes;
}
public Object getAttribute(Object key){
return attributes.get(key);
}
public void remove(Object key){
attributes.remove(key);
}
}
AbstractNettyNetMessage.java.来实现消息的基本操作。这样,我们的代码复用性得到大大的提高,不管是tcp消息还是其他的消息.
我们都可以继承这个类来进行扩展当然,我们最终的目的是为了进行google 的protobuf消息进行编解码。所以我们封装了另一个抽象类
AbstractNettyNetProtoBufMessage.java
**
* Created by twjitm on 2017/11/15.
* 基础protobuf协议消息
*/
public abstract class AbstractNettyNetProtoBufMessage extends AbstractNettyNetMessage {
public AbstractNettyNetProtoBufMessage() {
setNettyNetMessageHead(new NettyNetMessageHead());
setNettyNetMessageBody(new NettyNetMessageBody());
}
@Override
public NettyNetMessageHead getNetMessageHead() {
return getNettyNetMessageHead();
}
@Override
public NettyNetMessageBody getNetMessageBody() {
return getNettyNetMessageBody();
}
protected void initHeadCommId() {
MessageCommandAnntation messageCommandAnntation = this.getClass().getAnnotation(MessageCommandAnntation.class);
if(messageCommandAnntation!=null){
getNetMessageHead().setCmd((short) messageCommandAnntation.messagecmd().commId);
}
}
/*释放message的body*/
public void releaseMessageBody() throws CodecException, Exception{
getNetMessageBody().setBytes(null);
}
public abstract void release() throws CodecException;
public abstract void encodeNetProtoBufMessageBody() throws CodecException, Exception;
public abstract void decoderNetProtoBufMessageBody() throws CodecException, Exception;
public void setSerial(int serial){
getNetMessageHead().setSerial(serial);
}
}
这个抽象类 继承了上面的AbstractNettyNetMessage,所以有父类的方法。
关系图如图所示
最后我们封装成一个抽象的tcp消息或者udp消息等,如下代码就封装成一个tcp抽象消息。
public abstract class AbstractNettyNetProtoBufTcpMessage extends AbstractNettyNetProtoBufMessage {
public AbstractNettyNetProtoBufTcpMessage() {
super();
setNettyNetMessageHead(new NettyNetMessageHead());
setNettyNetMessageBody(new NettyNetProtoBufMessageBody());
initHeadCommId();
}
}
为了能够通过消息解耦的方式,我们通常采用自定义注解的形式来实现代理。在这就不介绍代理相关的知识了,还有反射等,
从代码中我们可以看到
一个tcp消息被创建的时候,就会执行initHeadCommId()方法,通过initHeadCommId()我们能够标记某个具体的消息上的注解的消息id,
t抽象消息类图结构.
我们通过自定义注解
MessageCommandAnntation.java 来注解一个消息id的枚举类
/**
* 消息分离注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MessageCommandAnntation {
MessageComm messagecmd();
}
MessageComm.java
public enum MessageComm {
MESSAGE_TRUE_RETURN(0),
PUBLIC_CHART_MESSAGE(1),
PRIVATE_CHAT_MESSAGE(2),
PLAYER_LOGIN_MESSAGE(3),
PLAYER_LOGOUT_MESSAGE(4),
DELETE_CHAT_MESSAGE(5),
HEART_MESSAGE(6);
public int commId;
MessageComm(int commId) {
this.commId = commId;
}
public static int getVaule(MessageComm messageComm) {
return messageComm.commId;
}
}
目前为止,我们只是为了实现编解码做好前提准备,下来我们就来看如何实现自定义编码器和解码器。
解码
同样,为了提高代码的复用,我们定义个一个抽象的解码工厂接口
INettyNetProtoBuffToMessageDecoderFactory.java
public interface INettyNetProtoBuffToMessageDecoderFactory {
public AbstractNettyNetProtoBufMessage praseMessage(ByteBuf byteBuf);
}
接口中就一个方法,将数据流解码成一个具体的消息实体。
然后我们将这个工厂类扩展为一个tcp协议解码工厂,
INettyNetProtoBuffTCPToMessageDecoderFactory.java ,
@Service
public interface INettyNetProtoBuffTCPToMessageDecoderFactory extends INettyNetProtoBuffToMessageDecoderFactory {
}
虽然目前没有任何方法,为了方便后来的扩充做好了前提准备。最后我们真正来编写一个tcp消息解码器工厂的具体实现。NettyNetProtoBuffTCPToMessageDecoderFactory.java
@Service
public class NettyNetProtoBuffTCPToMessageDecoderFactory implements INettyNetProtoBuffTCPToMessageDecoderFactory {
@Override
public AbstractNettyNetProtoBufMessage praseMessage(ByteBuf byteBuf) {
NettyNetMessageHead netMessageHead=new NettyUDPMessageHead();
//跳过2个字节
byteBuf.skipBytes(2);
//消息长度
netMessageHead.setLength(byteBuf.readInt());
//版本号
netMessageHead.setVersion(byteBuf.readByte());
//read message context
//读取内容
short cmd = byteBuf.readShort();
//消息id
netMessageHead.setCmd(cmd);
//序列号
netMessageHead.setSerial(byteBuf.readInt());
//通过spring管理容器
MessageRegistryFactory registryFactory =SpringServiceManager.springLoadService.getMessageRegistryFactory();
AbstractNettyNetProtoBufMessage nettyMessage = registryFactory.get(cmd);
nettyMessage.setNettyNetMessageHead(netMessageHead);
NettyNetMessageBody nettyNetMessageBody=new NettyNetMessageBody();
//数据域大小
int byteLength = byteBuf.readableBytes();
ByteBuf bodyByteBuffer = Unpooled.buffer(256);
byte[] bytes = new byte[byteLength];
bodyByteBuffer = byteBuf.getBytes(byteBuf.readerIndex(), bytes);
//保存数据到数据域
nettyNetMessageBody.setBytes(bytes);
nettyMessage.setNettyNetMessageBody(nettyNetMessageBody);
try {
//提交给具体的消息解码
nettyMessage.decoderNetProtoBufMessageBody();
} catch (Exception e) {
e.printStackTrace();
nettyMessage.release();
}
return nettyMessage;
}
}
入上述代码所示,在解码过程中我们利用到了spring整合Netty作为容器去管理一些bean对象,在这就不进行扩展描述了,
编码器
同样,为了提高代码的通用行,我们定义一个抽象的消息编码工厂接口INettyNetProtoBufMessageEncoderFactory.java
public interface INettyNetProtoBufMessageEncoderFactory {
public ByteBuf createByteBuf(AbstractNettyNetProtoBufMessage netMessage) throws Exception;
}
然后进行扩展,入进行tcp协议进行扩展,则编写一个INettyNetProtoBufTcpMessageEncoderFactory.java 的接口来继承这个接口。
/**
* Created by twjitm on
*/
public interface INettyNetProtoBufTcpMessageEncoderFactory extends INettyNetProtoBufMessageEncoderFactory {
}
同理,我们编译一个NettyNetProtoBufTcpMessageEncoderFactory.java
的实现类来实现消息编码。如下代码所示。
@Service
public class NettyNetProtoBufTcpMessageEncoderFactory implements INettyNetProtoBufTcpMessageEncoderFactory {
@Override
public ByteBuf createByteBuf(AbstractNettyNetProtoBufMessage netMessage) throws Exception {
ByteBuf byteBuf = Unpooled.buffer(256);
//编写head
NettyNetMessageHead netMessageHead = netMessage.getNetMessageHead();
byteBuf.writeShort(netMessageHead.getHead());
//长度
byteBuf.writeInt(netMessageHead.getLength());
//设置内容
byteBuf.writeByte(netMessageHead.getVersion());
//设置消息id
byteBuf.writeShort(netMessageHead.getCmd());
//设置系列号
byteBuf.writeInt(netMessageHead.getSerial());
//编写body
netMessage.encodeNetProtoBufMessageBody();
NettyNetMessageBody netMessageBody = netMessage.getNetMessageBody();
byteBuf.writeBytes(netMessageBody.getBytes());
//重新设置长度
int skip = 6;
int length = byteBuf.readableBytes() - skip;
byteBuf.setInt(2, length);
byteBuf.slice();
return byteBuf;
}
}
到此编码器和解码器的对应工厂类编写完毕。接下来我们就要实现一个编码器和一个解码器了
不管是编码器还是解码器,我们都继承netty提供的消息转消息编解码器MessageToMessageDecoder
如下代码所示我们自定义的一个解码器。
NettyNetProtoBufMessageTCPDecoder.java
/**
*
* @author tjwitm
* @date 2017/11/16
*/
public class NettyNetProtoBufMessageTCPDecoder extends MessageToMessageDecoder<ByteBuf> {
Logger logger=LoggerUtils.getLogger(NettyNetProtoBufMessageTCPDecoder.class);
private final Charset charset;
private INettyNetProtoBuffTCPToMessageDecoderFactory iNettyNetProtoBuffTCPToMessageDecoderFactory;
public NettyNetProtoBufMessageTCPDecoder() {
this(CharsetUtil.UTF_8);
}
public NettyNetProtoBufMessageTCPDecoder(Class<? extends ByteBuf> inboundMessageType, Charset charset, INettyNetProtoBuffTCPToMessageDecoderFactory iNettyNetProtoBuffTCPToMessageDecoerFactory) {
super(inboundMessageType);
this.charset = charset;
this.iNettyNetProtoBuffTCPToMessageDecoderFactory = iNettyNetProtoBuffTCPToMessageDecoerFactory;
}
public NettyNetProtoBufMessageTCPDecoder(Charset charset) {
if (charset == null) {
throw new NullPointerException("charset");
}
this.charset = charset;
iNettyNetProtoBuffTCPToMessageDecoderFactory = SpringServiceManager.springLoadService.getNettyNetProtoBuffTCPToMessageDecoderFactory();
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
if (iNettyNetProtoBuffTCPToMessageDecoderFactory == null) {
logger.error("iNettyNetProtoBuffTCPToMessageDecoderFactory is null ");
} else {
out.add(iNettyNetProtoBuffTCPToMessageDecoderFactory.praseMessage(msg));
}
}
}
重写了decode方法,利用我们自定义的解码工厂来进行解码。。
同样的道理,我们来看编码器的编写
NettyNetProtoBufMessageTCPEncoder.java
public class NettyNetProtoBufMessageTCPEncoder extends MessageToMessageEncoder<AbstractNettyNetProtoBufMessage> {
private final Charset charset;
private INettyNetProtoBufTcpMessageEncoderFactory iNetMessageEncoderFactory;
public NettyNetProtoBufMessageTCPEncoder() {
this(CharsetUtil.UTF_8);
this.iNetMessageEncoderFactory = SpringServiceManager.springLoadService.getNettyNetProtoBufTcpMessageEncoderFactory();
}
public NettyNetProtoBufMessageTCPEncoder(Charset charset) {
if(charset == null) {
throw new NullPointerException("charset");
} else {
this.charset = charset;
}
}
@Override
protected void encode(ChannelHandlerContext ctx, AbstractNettyNetProtoBufMessage msg, List<Object> out) throws Exception {
ByteBuf netMessageBuf = iNetMessageEncoderFactory.createByteBuf(msg);
out.add(netMessageBuf);
}
}
细心的小伙伴可能会发现这一次我们的编码器是继承了MessageToMessageEncoder。当然,这也是netty提供的
编码器。重写encode方法,利用我们自定义的编码器工厂类来进行编码。
到此,netty的自定义编码器和解码器的编写介绍完毕,介于代码比较多,我们利用tcp协议来进行描述,下面我们就来看看
编写实例进行测试吧。
我们编写一个服务器引导程序StartTcpService和编写一个客户端引导实例。启动服务器,启动客户端就可以进行传输
想要看效果的小伙伴自己下载源码。
中间我们还有很多没有介绍,由于篇幅限定,我们将进行下一篇描述《Netty实战开发(4):Netty整合spring来管理bean对象》