Cindy Message/MessageRecognizer interface
在用户对我的反馈中,其中最常见的就是对Message和MessageRecongizer接口的误用。
大家对Java里面的Serializable接口应该都不陌生吧,通过实现序列化接口,一个Java对象可以与字节流做双向转换。在网络传输中是没有对象的概念的,最基本的传输单位是字节,所以如果要通过网络来传输一个对象,就得按照如下三步来进行:
- 将对象转换为字节流
- 传输字节流
- 接收方接收字节流,并转换为对象
Cindy中Message接口就是这个转换的桥梁。Message接口的toByteBuffer方法就实现了该如何把这个对象转换为字节 流,readFromBuffer方法就实现了如何把字节流转换为该对象,至于传输,则由相应的Session来完成。这里要指出的Message和 Serializable接口不同的是,如果某类实现了Serializable接口,这个双向转换的过程可以由虚拟机来自动完成,而Message接口 则需要用户通过编程来明确指定转换方式。
假设有这个一个User对象需要在网络上传输:
public User {
private String name; //假设不超过255个字节
private byte age;
...get / set
}
我们可以定义若干种不同的方式将其转换为字节流,这取决于我们的业务需要。比如我们可以将其按如下的方式转换:
name UTF-8编码长度(byte) + name UTF-8编码(byte[]) + age(byte)
也可以按如下方式转换:
user对象总长度(4 byte) + age(byte) + name UTF-8编码(byte[])
如果是第一种方式,那么我们可以让这个User类实现Message接口,并且toByteBuffer方法可以这样写:
public ByteBuffer[] toByteBuffer() {
//转换为UTF-8编码
ByteBuffer nameBuffer = CharsetUtils.UTF-8.encode(name);
//第一个字节是长度,后面跟着UTF-8编码,再后面跟着age
ByteBuffer buffer = ByteBuffer.allocate(1 + nameBuffer.remaining() + 1);
buffer.put((byte)nameBuffer.remaining());
buffer.put(nameBuffer);
buffer.put(age);
return new ByteBuffer[] {buffer};
}
这样我们就定义好了如何将这个User对象转换为字节流了。
当从网络上读取这个User对象时,要注意的地方就要稍微多一点了。读取的时候不能假定所有的数据都已经传输完成了,很有可能这个User对象传输 到了一半的时候,就开始识别对象了,所以readFromBuffer方法返回值是boolean值,只有在该对象已经完全传输完毕的时候才返回 true,通知框架该对象已传输完毕。
比如这个User对象的readFromBuffer方法可以这样写:
public boolean readFromBuffer(ByteBuffer buffer) {
if (!buffer.hasRemaining())
return false;
byte length = buffer.get(buffer.position());
if (buffer.remaining() < 1 + length + 1) //消息没有接收完全,返回false
return false;
//消息已经全部接收完,读取消息
ByteBuffer nameBuffer = (ByteBuffer) buffer.slice().position(1).limit(length+1);
name = CharsetUtils.UTF-8.decode(nameBuffer).toString();
buffer.position(buffer.position+1+length);
age = buffer.get();
return true;
}
注意红色的代码,在这里并没有用buffer.get(),而是使用了buffer.get(buffer.position())。因为这个时候 还没有确定消息是不是接收完毕,如果直接通过buffer.get()读取后,buffer的position就会发生变化,如果发现消息没有接收完毕, 返回前就需要重新将position移回到原来的位置。
比如如果要接收的User对象字节流是 3 a b c 20,第一次触发readFromBuffer方法是由于接收到了3 a b,如果这时通过buffer.get()来读取,读完后缓冲区就变成了a b,然后返回false;第二次又接收到了c 20,这时触发readFromBuffer时,缓冲区就变成了a b c 20,这时再通过buffer.get()来读取就会发生异常了。所以消息没接收完毕前,不要移动缓冲区的指针。(在这里我发现了一个似乎可以改进的地方,即如果readFromBuffer返回false,框架可以将buffer的指针重置为原值。具体影响有待考量 )