自定义简单协议

闲言:关于mesos的学习,因为某些原因(我的性能好的台式暂时不可用了)而搁置,搁置时间不定

最近在对接某个第三方的socket接口的时候,对与这种写socket服务完全不考虑粘包拆包的操作完全绝望了,所以写一下粗浅的关于如何自定义协议,以及编写可处理粘包拆包的编解码器。关于tcp粘包拆包的概念请查阅其它博客

个人认为想能解决拆包和粘包问题,根本的途径就是知道消息有多长。大致常用的方法就是消息有指定的分隔符,或者在前面固定大小的消息头里存储剩余消息主体的大小

示例如下,实例基于netty编写,大致想法相同,不过netty免去了自己管理缓存

package protocol;

import java.io.UnsupportedEncodingException;

/**
 * Created by tangjiaqi on 2018/5/17.
 */
public class Message {

    public enum MessageType{
        BASE(1),
        DISCONNECT(2);


        int value;

        MessageType(int i) {
            value = i;
        }

        public int getValue(){
            return value;
        }

        public static MessageType valueOf(int i){
            MessageType[] types =  MessageType.values();
            MessageType result = null;
            for (MessageType x: types){
                if (x.getValue() == i){
                    result = x;
                    break;
                }
            }
            return result;
        }
    }

//    协议头,标记协议从什么时候开始
    private final static int HEADER = 0x7788b;
//    消息类型
    private MessageType messageType;
//    字符集的字符串长度(考虑不同编码的数据)
    private int charsetLength;
//    内容长度
    private int contentLength;
//    内容
    private byte[] charset;
//    字符集
    private byte[] content;

    public Message() {
    }

    public Message(MessageType messageType, String content, String charset) throws UnsupportedEncodingException {
        this.messageType = messageType;
        this.content = content.getBytes(charset);
        this.charset = charset.getBytes();
        this.contentLength = this.content.length;
        this.charsetLength = this.charset.length;
    }

    public MessageType getMessageType() {
        return messageType;
    }

    public void setMessageType(MessageType messageType) {
        this.messageType = messageType;
    }

    public int getCharsetLength() {
        return charsetLength;
    }

    public int getContentLength() {
        return contentLength;
    }

    public byte[] getContent() {
        return content;
    }

    public void setContent(String content, String charset) throws UnsupportedEncodingException {
        this.content = content.getBytes(charset);
        this.charset = charset.getBytes();
        this.contentLength = this.content.length;
        this.charsetLength = this.charset.length;
    }

    public byte[] getCharset() {
        return charset;
    }

    public static int getHEADER() {
        return HEADER;
    }
}

Encoder:

package codce;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import protocol.Message;

/**
 * Created by tangjiaqi on 2018/5/17.
 */
public class MyProtocolEncoder extends MessageToByteEncoder<Message> {

    public MyProtocolEncoder(){

    }


    protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws Exception {
        out.writeInt(msg.getHEADER());
        out.writeInt(msg.getMessageType().getValue());
        out.writeInt(msg.getCharsetLength());
        out.writeInt(msg.getContentLength());
        out.writeBytes(msg.getCharset());
        out.writeBytes(msg.getContent());
    }
}

encoder较为简单,按顺序写入ByteBuf就好了

Decoder:

package codce;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import protocol.Message;
import protocol.Message.MessageType;

import java.util.List;

/**
 * Created by tangjiaqi on 2018/5/17.
 */
public class MyProtocolDecoder extends ByteToMessageDecoder {
    public MyProtocolDecoder(){
    }

//    协议头+消息类型+charsetLength+contentLength的长度
    private final static int BASE_LENGTH = 16;

    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        System.out.println("start decode");
        int beginIndex = in.readerIndex();
        int messageTypeValue = 0, charsetLength = 0, contentLength = 0;
        boolean flag = false;
        while (in.readableBytes() > BASE_LENGTH){
            beginIndex = in.readerIndex();
            int tmp = in.readInt();
//            协议开始
            if (tmp == Message.getHEADER()){
                messageTypeValue = in.readInt();
                charsetLength = in.readInt();
                contentLength = in.readInt();
                flag = true;
                break;
            }
        }
        if (!flag){
            return;
        }else {
//            剩余可读数据小于所需,返回等待下次数据的到来
            if (in.readableBytes() < (charsetLength + contentLength)){
//                记住将读索引归位
                in.readerIndex(beginIndex);
                return;
            }else {
                byte[] charset = new byte[charsetLength];
                byte[] content = new byte[contentLength];
                in.readBytes(charset);
                in.readBytes(content);
                String charsetStr = new String(charset);
                String contentStr = new String(content, charsetStr);
                Message message = new Message(MessageType.valueOf(messageTypeValue), contentStr, charsetStr);
                out.add(message);
            }
        }
        System.out.println("end decode");
    }
}

关于解码器部分其实就几个注意点

  1. 首先要读取到你自定义的包头的开始字段
  2. 一次性读取你定义的固定长度的头,从而能从头中获取到剩余字段的大小

达成以上两点大致就能做到解决拆包粘包问题了,关于这个协议还做了一个测试,测试大致就是一次性传输内容较大,然后查看接受到的内容以及解码过程

关于对这个自定义协议的demo应用详见: https://github.com/ncuwaln/protocol-demo

ps: 这个自定义协议没有考虑任何传输内容的减少的优化以及其它七七八八的各种细节,很简单的那种

扫描二维码关注公众号,回复: 1066574 查看本文章

猜你喜欢

转载自blog.csdn.net/tjq980303/article/details/80382879