java 自定义协议_Netty实现自定义协议

关于协议,使用最为广泛的是HTTP协议,但是在一些服务交互领域,其使用则相对较少,主要原因有三方面:

HTTP协议会携带诸如header和cookie等信息,其本身对字节的利用率也较低,这使得HTTP协议比较臃肿,在承载相同信息的情况下,HTTP协议将需要发送更多的数据包;

HTTP协议是基于TCP的短连接,其在每次请求和响应的时候都需要进行三次握手和四次挥手,由于服务的交互设计一般都要求能够承载高并发的请求,因而HTTP协议这种频繁的握手和挥手动作会极大的影响服务之间交互的效率;

服务之间往往有一些根据其自身业务特性所独有的需求,而HTTP协议无法很好的服务于这些业务需求。

基于上面的原因,一般的服务之间进行交互时都会使用自定义协议,常见的框架,诸如dubbo,kafka,zookeeper都实现了符合其自身业务需求的协议,本文主要讲解如何使用Netty实现一款自定义的协议。

1. 协议规定

所谓协议,其本质其实就是定义了一个将数据转换为字节,或者将字节转换为数据的一个规范。一款自定义协议,其一般包含两个部分:消息头和消息体。消息头的长度一般是固定的,或者说是可确定的,其定义了此次消息的一些公有信息,比如当前服务的版本,消息的sessionId,消息的类型等等;消息体则主要是此次消息所需要发送的内容,一般在消息头的最后一定的字节中保存了当前消息的消息体的长度。下面是我们为当前自定义协议所做的一些规定:

| 名称 | 字段 | 字节数 | 描述 | | :--: | :--: | :----: | :--- | | | | | | | 魔数 | magicNumber | 4 | 一个固定的数字,一般用于指定当前字节序列是当前类型的协议,比如Java生成的class文件起始就使用0xCAFEBABE作为其标识符,对于本服务,这里将其定义为0x1314 | | 主版本号 | mainVersion | 1 | 当前服务器版本代码的主版本号 | | 次版本号 | subVersion | 1 | 当前服务器版本的次版本号 | | 修订版本号 | modifyVersion | 1 | 当前服务器版本的修订版本号 | | 会话id | sessionId | 8 | 当前请求的会话id,用于将请求和响应串联到一起 | | 消息类型 | messageType | 1 | 请求:1,表示当前是一个请求消息;
响应:2,表示当前是一个响应消息;
Ping:3,表示当前是一个Ping消息;
Pong:4,表示当前是一个Pong消息;
Empty:5,表示当前是一个空消息,该消息不会写入数据管道中; | | 附加数据 | attachments | 不定 | 附加消息是字符串类型的键值对来表示的,这里首先使用2个字节记录键值对的个数,然后对于每个键和值,都首先使用4个字节记录其长度,然后是具体的数据,其形式如:键值对个数+键长度+键数据+值长度+值数据... | | 消息体长度 | length | 4字节 | 记录了消息体的长度 | | 消息体 | body | 不定 | 消息体,服务之间交互所发送或接收的数据,其长度有前面的length指定 |

上述协议定义中,我们除了定义常用的请求和响应消息类型以外,还定义了Ping和Pong消息。Ping和Pong消息的作用一般是,在服务处于闲置状态达到一定时长,比如2s时,客户端服务会向服务端发送一个Ping消息,则会返回一个Pong消息,这样才表示客户端与服务端的连接是完好的。如果服务端没有返回相应的消息,客户端就会关闭与服务端的连接或者是重新建立与服务端的连接。这样的优点在于可以防止突然会产生的客户端与服务端的大量交互。

2. 协议实现

通过上面的定义其实我们可以发现,所谓协议,就是定义了一个规范,基于这个规范,我们可以将消息转换为相应的字节流,然后经由TCP传输到目标服务,目标服务则也基于该规范将字节流转换为相应的消息,这样就达到了相互交流的目的。这里面最重要的主要是如何基于该规范将消息转换为字节流或者将字节流转换为消息。这一方面,Netty为我们提供了ByteToMessageDecoder和MessageToByteEncoder用于进行消息和字节流的相互转换。首先我们定义了如下消息实体:

public class Message {

private int magicNumber;

private byte mainVersion;

private byte subVersion;

private byte modifyVersion;

private String sessionId;

private MessageTypeEnum messageType;

private Map attachments = new HashMap<>();

private String body;

public Map getAttachments() {

return Collections.unmodifiableMap(attachments);

}

public void setAttachments(Map attachments) {

this.attachments.clear();

if (null != attachments) {

this.attachments.putAll(attachments);

}

}

public void addAttachment(String key, String value) {

attachments.put(key, value);

}

// getter and setter...

}

上述消息中,我们将协议中所规定的各个字段都进行了定义,并且定义了一个标志消息类型的枚举MessageTypeEnum,如下是该枚举的源码:

public enum MessageTypeEnum {

REQUEST((byte)1), RESPONSE((byte)2), PING((byte)3), PONG((byte)4), EMPTY((byte)5);

private byte type;

MessageTypeEnum(byte type) {

this.type = type;

}

public int getType() {

return type;

}

public static MessageTypeEnum get(byte type) {

for (MessageTypeEnum value : values()) {

if (value.type == type) {

return value;

<
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值