When the two processes need to communicate with the network, we tend to use Socket
to achieve. Socket
I 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/IP
protocol 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 TCP
agreement students must have heard 粘包、拆包
these two terms. Because the TCP
protocol 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 int
type to represent the size of the data. For example, Netty
it provides us with LengthFieldBasedFrameDecoder
the 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 HTTP
into agreements request headers, request bodies -
- Request header: Defines the interface address
Http Method
,HTTP
version - 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
code
different categories to represent business logic - Sequence algorithm: describes the
JAVA
format conversion between binary and objects, providing a variety of serialization / deserialization mode. For examplejson
,protobuf
and 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 telnet
a 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 4
bytes 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 RocketMQ
the analysis to achieve excellent communication network protocol. RocketMQ
Project, 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
RocketMQ
Communications module is based on Netty
the. By defining NettyEncoder
achieved each Channel
a 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 RemotingCommand
object, encodeHeader
stage, 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 encode
stage will be CommandCustomHeader
the 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, encode
the stage of the current RPC
type and headerData
length 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 & 0xFF
taking the low eight bits of data.
Therefore, the final length
length is equal to the sequence of the type + header length + header data + byte length of body data.
Decoding process
RocketMQ
By decoding NettyDecoder
is achieved, it inherits from LengthFieldBasedFrameDecoder
, which calls the parent class LengthFieldBasedFrameDecoder
constructor
super(FRAME_MAX_LENGTH, 0, 4, 0, 4);
These parameters 4
bytes represent length
the total length, while skipping the beginning of the time of decoding 4
bytes:
frame = (ByteBuf) super.decode(ctx, in);
Therefore, the resulting frame
SEQUENCE 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 24
place, get serializeType
:
public static SerializeType getProtocolType(int source) {
return SerializeType.valueOf((byte) ((source >> 24) & 0xFF));
}
getHeaderLength
0-24 to get representatives of headerData
length:
public static int getHeaderLength(int length) {
return length & 0xFFFFFF;
}
summary
For many middleware, the underlying network communication module is often used Netty
. Netty
Provides many codecs, you can quickly and easily get started. This article starts with how a network protocol design, final cut to RocketMQ
achieve 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
.