Dubbo数据包
dubbo协议采用固定长度的消息头(16字节)和不定长度的消息体来进行数据传输,消息头定义了底层
框架(netty)在IO线程处理时需要的信息
协议详情
-
Magic - Magic High & Magic Low (16 bits)
标识协议版本号,Dubbo 协议:0xdabb -
Serialization ID (5 bit)
标识序列化类型:比如 fastjson 的值为6 -
Event (1 bit)
标识是否是事件消息,例如,心跳事件。如果这是一个事件,则设置为1。 -
2 Way (1 bit)
仅在 Req/Res 为1(请求)时才有用,标记是否期望从服务器返回值。如果需要来自服务器
的返回值,则设置为1 -
Req/Res (1 bit)
标识是请求或响应。请求: 1; 响应: 0 -
tatus (8 bits)
仅在 Req/Res 为0(响应)时有用,用于标识响应的状态
-
Request ID (64 bits) 标识唯一请求。类型为long。
-
Data Length (32 bits)
序列化后的内容长度(可变部分),按字节计数。int类型 -
Variable Part
被特定的序列化类型(由序列化 ID 标识)序列化后,每个部分都是一个 byte [] 或者 byte
对于(Variable Part)变长部分,当前版本的Dubbo 框架使用json序列化时,在每部分内容间
额外增加了换行符作为分隔,请在Variable Part的每个part后额外增加换行符
Dubbo version bytes (换行符)
Service name bytes (换行符)
*优点
协议设计上很紧凑,能用 1 个 bit 表示的,不会用一个 byte 来表示,比如 boolean 类型的标识。
请求、响应的 header 一致,通过序列化器对 content 组装特定的内容,代码实现起来简单
数据协议ExchangeCodec
public class ExchangeCodec extends TelnetCodec {
// header length. 消息头的长度
protected static final int HEADER_LENGTH = 16;
// magic header. 标示为0-15位
protected static final short MAGIC = (short) 0xdabb;
protected static final byte MAGIC_HIGH = Bytes.short2bytes(MAGIC)[0];
protected static final byte MAGIC_LOW = Bytes.short2bytes(MAGIC)[1];
// message flag. 消息头中的内容
protected static final byte FLAG_REQUEST = (byte) 0x80;
protected static final byte FLAG_TWOWAY = (byte) 0x40;
protected static final byte FLAG_EVENT = (byte) 0x20;
protected static final int SERIALIZATION_MASK = 0x1f;
private static final Logger logger = LoggerFactory.getLogger(ExchangeCodec.class);
encode方法
public void encode(Channel channel, ChannelBuffer buffer, Object msg) throws IOException {
//处理请求对象
if (msg instanceof Request) {
encodeRequest(channel, buffer, (Request) msg);
} else if (msg instanceof Response) {
//处理响应
encodeResponse(channel, buffer, (Response) msg);
} else {
//其他的交给上级处理,用于telnet模式
super.encode(channel, buffer, msg);
}
}
encodeRequest
protected void encodeRequest(Channel channel, ChannelBuffer buffer, Request req) throws IOException {
//获取序列化方式
Serialization serialization = getSerialization(channel);
// header. 写入header信息
byte[] header = new byte[HEADER_LENGTH];
// set magic number. 魔数0-15位
Bytes.short2bytes(MAGIC, header);
//标记为请求
// set request and serialization flag.
header[2] = (byte) (FLAG_REQUEST | serialization.getContentTypeId());
//是否是单向还是双向的(异步)
if (req.isTwoWay()) {
header[2] |= FLAG_TWOWAY;
}
//是否为事件(心跳)
if (req.isEvent()) {
header[2] |= FLAG_EVENT;
}
//写入当前的请求ID
// set request id.
Bytes.long2bytes(req.getId(), header, 4);
//保存当前写入的位置,将其写入的位置往后面偏移,保留出写入内容大小的位置,先进行写入body内容
// encode request data.
int savedWriteIndex = buffer.writerIndex();
//
buffer.writerIndex(savedWriteIndex + HEADER_LENGTH);
ChannelBufferOutputStream bos = new ChannelBufferOutputStream(buffer);
ObjectOutput out = serialization.serialize(channel.getUrl(), bos);
//按照数据内容的不同,来写入不同的内容
if (req.isEvent()) {
encodeEventData(channel, out, req.getData());
} else {
encodeRequestData(channel, out, req.getData(), req.getVersion());
}
out.flushBuffer();
if (out instanceof Cleanable) {
((Cleanable) out).cleanup();
}
bos.flush();
bos.close();
//记录写入BODY的长度
int len = bos.writtenBytes();
checkPayload(channel, len);
//将其写入到header中的位置中
Bytes.int2bytes(len, header, 12);
//发送到buffer中
// write
buffer.writerIndex(savedWriteIndex);
buffer.writeBytes(header); // write header.
buffer.writerIndex(savedWriteIndex + HEADER_LENGTH + len);
}
真正的 encodeRequestData 在子类 DubboCodec 中
protected void encodeRequestData(Channel channel, ObjectOutput out, Object data, String version) throws IOException {
RpcInvocation inv = (RpcInvocation) data;
//写入版本
out.writeUTF(version);
// https://github.com/apache/dubbo/issues/6138
//接口全名
String serviceName = inv.getAttachment(INTERFACE_KEY);
if (serviceName == null) {
serviceName = inv.getAttachment(PATH_KEY);
}
out.writeUTF(serviceName);
//接口版本号
out.writeUTF(inv.getAttachment(VERSION_KEY));
//方法名
out.writeUTF(inv.getMethodName());
//参数描述
out.writeUTF(inv.getParameterTypesDesc());
//参数
Object[] args = inv.getArguments();
if (args != null) {
for (int i = 0; i < args.length; i++) {
out.writeObject(encodeInvocationArgument(channel, inv, i));
}
}
//写入所有的附加信息
out.writeAttachments(inv.getObjectAttachments());
}
encodeResponseData与上面的方法encodeRequestData 类似
protected void encodeResponseData(Channel channel, ObjectOutput out, Object data, String version) throws IOException {
//
Result result = (Result) data;
// currently, the version value in Response records the version of Request
// 是否支持返回attachment参数
boolean attach = Version.isSupportResponseAttachment(version);
Throwable th = result.getException();
if (th == null) {
//如果没有异常信息,则直接写入内容
Object ret = result.getValue();
if (ret == null) {
out.writeByte(attach ? RESPONSE_NULL_VALUE_WITH_ATTACHMENTS : RESPONSE_NULL_VALUE);
} else {
out.writeByte(attach ? RESPONSE_VALUE_WITH_ATTACHMENTS : RESPONSE_VALUE);
out.writeObject(ret);
}
} else {
//否则的话则将异常信息序列化
out.writeByte(attach ? RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS : RESPONSE_WITH_EXCEPTION);
out.writeThrowable(th);
}
//支持写入attachment,则写入
if (attach) {
// returns current version of Response to consumer side.
result.getObjectAttachments().put(DUBBO_VERSION_KEY, Version.getProtocolVersion());
out.writeAttachments(result.getObjectAttachments());
}
}