Programming ideas: how to design a good network communication protocol

When the two processes need to communicate with the network, we tend to use Socketto achieve. SocketI am not familiar with. When the three-way handshake is successful, the client will be able to communicate with the server, and the data packet format for communication between each other are binary, the TCP/IPprotocol is responsible for transmission.

When the client and server to obtain binary data packet, we often need to "extract" the desired data, so as to better perform the business logic. So, we need to define a data structure to describe these binary data format, which is a communication network protocol. Simply speaking, a good agreement is the need for binary byte packets in each segment of meaning, such as starting with the first byte n m core length data, after With this agreement, we will be able to decode the data you want, perform business logic, so that we can communicate unimpeded up.

Network protocol design

Summary divided

A basic network protocol must contain

  • Length of the data
  • data

Understanding TCPagreement students must have heard 粘包、拆包these two terms. Because the TCPprotocol is a protocol data stream, its underlying divided packet according to the actual binary buffer. So, there will be inevitable 粘包,拆包phenomenon. To solve them, our network protocols tend to use a 4-byte inttype to represent the size of the data. For example, Nettyit provides us with LengthFieldBasedFrameDecoderthe decoder, it can effectively use a custom-length frame to solve the above problems.

At the same time a good network protocol, and also the traffic data separating operation. Imagine HTTPinto agreements request headers, request bodies -

  • Request header: Defines the interface address Http Method, HTTPversion
  • Request body: need to pass data defining

This is an idea separation of concerns. So a custom network protocol may also include:

  • Operation command: such as defining codedifferent categories to represent business logic
  • Sequence algorithm: describes the JAVAformat conversion between binary and objects, providing a variety of serialization / deserialization mode. For example json, protobufand so on, and even custom algorithm. For example: rocketmq and so on.

At the same time, the beginning of the protocol can define a convention 魔数. Fixed value (4 bytes), generally used to determine whether the current packet is valid. For example, when we use telneta transmission error packets, it is clear that it is illegal, it can cause decoding failure. Therefore, in order to alleviate the pressure on the server, we can be removed before the packet 4bytes of the immobilized 魔数contrast, if it is illegal format, close the connection, not continue decoding.

Network protocol structure is shown below :

+--------------+-----------+------------+-----------+----------+
| 魔数(4)       | code(1)   |序列化算法(1) |数据长度(4) |数据(n)   |
+--------------+-----------+------------+-----------+----------+ 

Communication network protocol implemented RocketMQ

RocketMQ network protocol

This section, we from RocketMQthe analysis to achieve excellent communication network protocol. RocketMQProject, the client and server communication is based on Netty built. Meanwhile, in order to more effective communication, often require custom network protocol messages sent.

RocketMQ Network protocol, data from the classification point of view, can be divided into two categories

  • Data message header (Header Data)
  • Data message body (Body Data)

From left to right

  • First stage: 4-byte integers, equal to the sum of the length of 2,3,4

  • Second paragraph: 4-byte integers, equal to the length of 3. Special byte[0]representative of the serialization algorithm, byte[1~3]is the real length

  • Third paragraph: Representative header data, the following structure

{
    "code":0,
    "language":"JAVA",
    "version":0,
    "opaque":0,
    "flag":1,
    "remark":"hello, I am respponse /127.0.0.1:27603",
    "extFields":{
        "count":"0",
        "messageTitle":"HelloMessageTitle"
    }
}
  • Fourth paragraph: data representative of a message body

RocketMQ protocol message header in detail as follows:

Header field name Types of Request Response
code Integer Request operation code, request the recipient to do different actions depending on different code Response result code, 0 on success, nonzero various error codes
language String Request originator implementation language, the default JAVA Answer receiver implementation language
version Integer Request Initiator version Answer receiver version
opaque Integer Different connection request originator identification code in the same requests, multiplexing using multi-threaded connector The responder will not be modified directly back
flag Integer Flag communication layer Flag communication layer
remark String Transmission custom text message Detailed description of the error message
extFields HashMap<String,String> Request custom field Answer custom fields

Encoding process

RocketMQCommunications module is based on Nettythe. By defining NettyEncoderachieved each Channela stack data is encoded as follows:

@ChannelHandler.Sharable
public class NettyEncoder extends MessageToByteEncoder<RemotingCommand> {
    @Override
    public void encode(ChannelHandlerContext ctx, RemotingCommand remotingCommand, ByteBuf out)
        throws Exception {
        try {
            ByteBuffer header = remotingCommand.encodeHeader();
            out.writeBytes(header);
            byte[] body = remotingCommand.getBody();
            if (body != null) {
                out.writeBytes(body);
            }
        } catch (Exception e) {
           ...
        }
    }
}

Wherein the encoding process is located in the core of the RemotingCommandobject, encodeHeaderstage, need to count the total length of the message, namely:

  • Definition of the header length, an integer: 4 bytes

  • Define the message header data, and calculating the length

  • Define the message body data, and calculating the length

  • 4 plus the additional need to be added because the total length of the message, an integer: 4 bytes

public ByteBuffer encodeHeader(final int bodyLength) {
    // 1> 消息头长度,一个整数表示:占4个字节
    int length = 4;

    // 2> 消息头数据
    byte[] headerData;
    headerData = this.headerEncode();
    // 再加消息头数据长度
    length += headerData.length;

    // 3> 再加消息体数据长度
    length += bodyLength;
    // 4> 额外加 4是因为需要加入消息总长度,一个整数表示:占4个字节
    ByteBuffer result = ByteBuffer.allocate(4 + length - bodyLength);

    // 5> 将消息总长度加入 ByteBuffer
    result.putInt(length);

    // 6> 将消息的头长度加入 ByteBuffer
    result.put(markProtocolType(headerData.length, serializeTypeCurrentRPC));

    // 7> 将消息头数据加入 ByteBuffer
    result.put(headerData);

    result.flip();

    return result;
}

Wherein the encodestage will be CommandCustomHeaderthe data conversion HashMap<String,String>, to facilitate serialization

public void makeCustomHeaderToNet() {
    if (this.customHeader != null) {
        Field[] fields = getClazzFields(customHeader.getClass());
        if (null == this.extFields) {
            this.extFields = new HashMap<String, String>();
        }

        for (Field field : fields) {
            if (!Modifier.isStatic(field.getModifiers())) {
                String name = field.getName();
                if (!name.startsWith("this")) {
                    Object value = null;
                    try {
                        field.setAccessible(true);
                        value = field.get(this.customHeader);
                    } catch (Exception e) {
                        log.error("Failed to access field [{}]", name, e);
                    }

                    if (value != null) {
                        this.extFields.put(name, value.toString());
                    }
                }
            }
        }
    }
}

In particular, the message header serialization support two algorithms:

  • JSON
  • RocketMQ
private byte[] headerEncode() {
    this.makeCustomHeaderToNet();
    if (SerializeType.ROCKETMQ == serializeTypeCurrentRPC) {
        return RocketMQSerializable.rocketMQProtocolEncode(this);
    } else {
        return RemotingSerializable.encode(this);
    }
}

It is worth noting here required, encodethe stage of the current RPCtype and headerDatalength to a coding byte[4]array, byte[0]the bit sequence types.

public static byte[] markProtocolType(int source, SerializeType type) {
    byte[] result = new byte[4];

    result[0] = type.getCode();
    result[1] = (byte) ((source >> 16) & 0xFF);
    result[2] = (byte) ((source >> 8) & 0xFF);
    result[3] = (byte) (source & 0xFF);
    return result;
}

Wherein the calculation by & 0xFFtaking the low eight bits of data.

Therefore, the final lengthlength is equal to the sequence of the type + header length + header data + byte length of body data.

Decoding process

RocketMQBy decoding NettyDecoderis achieved, it inherits from LengthFieldBasedFrameDecoder, which calls the parent class LengthFieldBasedFrameDecoderconstructor

super(FRAME_MAX_LENGTH, 0, 4, 0, 4);

These parameters 4bytes represent lengththe total length, while skipping the beginning of the time of decoding 4bytes:

frame = (ByteBuf) super.decode(ctx, in);

Therefore, the resulting frameSEQUENCE types + header length + header data + body data. Decoding as follows:

public static RemotingCommand decode(final ByteBuffer byteBuffer) {
    //总长度
    int length = byteBuffer.limit();
    //原始的 header length,4位
    int oriHeaderLen = byteBuffer.getInt();
    //真正的 header data 长度。忽略 byte[0]的 serializeType
    int headerLength = getHeaderLength(oriHeaderLen);

    byte[] headerData = new byte[headerLength];
    byteBuffer.get(headerData);

    RemotingCommand cmd = headerDecode(headerData, getProtocolType(oriHeaderLen));

    int bodyLength = length - 4 - headerLength;
    byte[] bodyData = null;
    if (bodyLength > 0) {
        bodyData = new byte[bodyLength];
        byteBuffer.get(bodyData);
    }
    cmd.body = bodyData;

    return cmd;
}

private static RemotingCommand headerDecode(byte[] headerData, SerializeType type) {
    switch (type) {
        case JSON:
            RemotingCommand resultJson = RemotingSerializable.decode(headerData, RemotingCommand.class);
            resultJson.setSerializeTypeCurrentRPC(type);
            return resultJson;
        case ROCKETMQ:
            RemotingCommand resultRMQ = RocketMQSerializable.rocketMQProtocolDecode(headerData);
            resultRMQ.setSerializeTypeCurrentRPC(type);
            return resultRMQ;
        default:
            break;
    }

    return null;
}

Where getProtocolType, right 24place, get serializeType:

public static SerializeType getProtocolType(int source) {
    return SerializeType.valueOf((byte) ((source >> 24) & 0xFF));
}

getHeaderLength0-24 to get representatives of headerDatalength:

public static int getHeaderLength(int length) {
    return length & 0xFFFFFF;
}

summary

For many middleware, the underlying network communication module is often used Netty. NettyProvides many codecs, you can quickly and easily get started. This article starts with how a network protocol design, final cut to RocketMQachieve the underlying network protocol. We can see, it's not complicated. Carefully read several times changed understand its profound meaning. With particular reference to class NettyEncoder, NettyDecoder, RemotingCommand.

Guess you like

Origin www.cnblogs.com/OceanEyes/p/protocol_design.html