Netty实战开发(3):自定义私有协议栈

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/baidu_23086307/article/details/81303324

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对象》

阅读更多

扫码向博主提问

twjitm

非学,无以致疑;非问,无以广识
  • 擅长领域:
  • java
  • spring
  • mybatis
  • hibernate
  • javaweb
去开通我的Chat快问
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页