当服务端读取客户端发送过来的消息时,会执行AbstractPollingIoProcessor里面的read方法,因为之前在我们对MINA的源码分析中就已经知道了对于每个Session的请求来说,实际上的执行者是IoProcessor对象,在read方法中会获取到当前IoSession所对应的IoFilter责任链,接着就会调用责任链的fireMessageReceived方法,而在fireMessageReceived里面又会调用callNextMessageReceived方法,在callNextMessageReceived方法里面就会执行IoFilter的messageReceived方法了,这个方法的真正实现是在ProtocolCodecFilter里面的,我们来看看他的具体实现:
ProtocolCodecFilter$messageReceived()
public void messageReceived(NextFilter nextFilter, IoSession session, Object message) throws Exception {
LOGGER.debug("Processing a MESSAGE_RECEIVED for session {}", session.getId());
if (!(message instanceof IoBuffer)) {
nextFilter.messageReceived(session, message);
return;
}
IoBuffer in = (IoBuffer) message;
ProtocolDecoder decoder = factory.getDecoder(session);
ProtocolDecoderOutput decoderOut = getDecoderOut(session, nextFilter);
// Loop until we don't have anymore byte in the buffer,
// or until the decoder throws an unrecoverable exception or
// can't decoder a message, because there are not enough
// data in the buffer
while (in.hasRemaining()) {
int oldPos = in.position();
try {
synchronized (session) {
// Call the decoder with the read bytes
decoder.decode(session, in, decoderOut);
}
// Finish decoding if no exception was thrown.
decoderOut.flush(nextFilter, session);
} catch (Exception e) {
ProtocolDecoderException pde;
if (e instanceof ProtocolDecoderException) {
pde = (ProtocolDecoderException) e;
} else {
pde = new ProtocolDecoderException(e);
}
if (pde.getHexdump() == null) {
// Generate a message hex dump
int curPos = in.position();
in.position(oldPos);
pde.setHexdump(in.getHexDump());
in.position(curPos);
}
// Fire the exceptionCaught event.
decoderOut.flush(nextFilter, session);
nextFilter.exceptionCaught(session, pde);
// Retry only if the type of the caught exception is
// recoverable and the buffer position has changed.
// We check buffer position additionally to prevent an
// infinite loop.
if (!(e instanceof RecoverableProtocolDecoderException) || (in.position() == oldPos)) {
break;
}
}
}
}
这个方法第4行首先判断当前的message是不是IoBuffer,如果不是的话,则会调用nextFilter的messageReceived,由下一个IoFilter进行处理,同时结束当前IoFilter处理,这里的nextFilter是EntryImpl里面的一个对象,他是DefaultIoFilterChain里面的一个内部类,我们来看看他里面的messageReceived方法:
DefaultIoFilterChain#EntryImpl
public void messageReceived(IoSession session, Object message) {
Entry nextEntry = EntryImpl.this.nextEntry;
callNextMessageReceived(nextEntry, session, message);
}
可以看到它实际上调用的是
callNextMessageReceived方法,而
callNextMessageReceived方法又会调用
IoFilter的messageReceived方法,继续判断当前的message对象是不是IoBuffer,直到是为止,相当于是一个间接的递归操作了;
如果当前message是IoBuffer的话,则在第10行通过编解码器工厂获取到我们的解码器ProtocolDecoder,我们可以通过实现ProtocolDecoder接口来实现自己的解码器,第17行判断当前IoBuffer中是否存在需要被解码的内容,存在的话执行第22行解码器的decode方法,如果解码器是我们自己实现的话,那么这个方法将是我们自己实现的,并且在我们自己实现的decode方法将该消息解码出来之后,会调用ProtocolDecoderOutput的write方法,将解码出来的信息添加到队列中去:
AbstractProtocolDecoderOutput$write()
public void write(Object message) {
if (message == null) {
throw new IllegalArgumentException("message");
}
messageQueue.add(message);
}
在执行完write方法之后
ProtocolCodecFilter$messageReceived()的25行执行了flush方法,这个方法会将当前已经解析好的消息传递下去,由下面的IoFilter来进行处理,flush方法的具体实现是在ProtocolCodecFilter的静态内部类ProtocolDecoderOutputImpl里面的:
ProtocolDecoderOutputImpl$flush
public void flush(NextFilter nextFilter, IoSession session) {
Queue
其实执行的还是nextFilter的messageReceived方法,这样一直执行下去,直到到达责任链的尾部TailFilter,执行TailFilter的
这就是解码过程,那么编码过程是什么样子的呢?实际上就是逆向的解码过程了:
我们知道,客户端想要通过MINA向服务端传递数据的话,首先需要获得IoSession对象,执行他的write方法,具体实现是在AbstractIoSession里面实现的,在write方法中获得当前IoSession对应的IoFilter链,并且调用IoFilter链的fireFilterWrite方法,实际上执行的是DefaultIoFilterChain里面的fireFilterWrite方法,在fireFilterWrite方法里面会执行callPreviousFilterWrite方法,在callPreviousFilterWrite里面就会执行IoFilter的filterWrite方法了,这个方法就是我们要讲的重点了,他的实现是在ProtocolCodecFilter里面的:
ProtocolCodecFilter$filterWrite
public void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception {
Object message = writeRequest.getMessage();
// Bypass the encoding if the message is contained in a IoBuffer,
// as it has already been encoded before
if ((message instanceof IoBuffer) || (message instanceof FileRegion)) {
nextFilter.filterWrite(session, writeRequest);
return;
}
// Get the encoder in the session
ProtocolEncoder encoder = factory.getEncoder(session);
ProtocolEncoderOutput encoderOut = getEncoderOut(session, nextFilter, writeRequest);
if (encoder == null) {
throw new ProtocolEncoderException("The encoder is null for the session " + session);
}
try {
// Now we can try to encode the response
encoder.encode(session, message, encoderOut);
// Send it directly
Queue
和解码一样,编码过程首先也是判断当前message是否是
IoBuffer,如果不是的话,则直接调用nextFilter的filterWrite方法,这里的
filterWrite实际上调用的是
DefaultIoFilterChain内部类EntryImpl中的filterWrite方法;
DefaultIoFilterChain$EntryImpl
public void filterWrite(IoSession session, WriteRequest writeRequest) {
Entry nextEntry = EntryImpl.this.prevEntry;
callPreviousFilterWrite(nextEntry, session, writeRequest);
}
可以看到这个方法实际上执行的是callPreviousFilterWrite方法,而在callPreviousFilterWrite方法里面又会去执行IoFilter的filterWrite方法,这样子形成了间接递归操作,直到当前message是IoBuffer对象为止,继续向下执行第12行获取到实现ProtocolEncoder接口的编码器,第22行调用编码器的encode方法进行编码,这个方法可以是我们自己实现编码器中的encode方法,我们会在自己实现的encode方法里面调用ProtocolEncoderOutput的write方法,将编码结果写到队列里面,最后调用nextFilter的filterWrite方法,将当前结果传递给他的下一个过滤器中,这就是整个编码过程;
讲完了编解码过程,还有一个问题就是MINA是怎么解决传递数据的过程中出现的粘包和断包现象的呢?
先来说说粘包的概念:指TCP协议中,发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。造成的可能原因:发送端需要等缓冲区满才发送出去,造成粘包接收方不及时接收缓冲区的包,造成多个包接收;
断包的概念:也就是数据不全,比如包太大,就把包分解成多个小包,多次发送,导致每次接收数据都不全;
消息格式有两种,一种是消息长度+消息头+消息体,即前N个字节用于存储消息的长度,用于判断当前消息什么时候结束。一种是消息头+消息体,即固定长度的消息,前几个字节为消息头,后面的是消息头。在MINA中使用的是前者实现的,具体来讲的话就是通过4个字节来存储消息的长度,用于判断当前消息什么时候结束;
在MINA中解决粘包和断包问题的话,我们的解码器就需要实现CumulativeProtocolDecoder抽象类,我们之前实现解码器的话是实现ProtocolDecoder接口的,单独实现这个接口是不能解决粘包和断包问题的,CumulativeProtocolDecoder抽象类继承了ProtocolDecoderAdapter抽象类,这个类实现了ProtocolDecoder接口,在CumulativeProtocolDecoder中为我们增添了一个叫doDecode的方法,这个方法是我们在实现CumulativeProtocolDecoder抽象类的时候自己实现的,这个方法是由返回值的,正是因为这个返回值的存在,我们才能解决断包问题,返回true的话,表示已经是一个完整的包了,我们可以进行解码了,返回false的话表示是断包,我们需要将其缓存下来,等到是组成完整的包之后对其进行解码;那么doDecode是谁调用的呢?当然是decode啦,具体我们看看decode里面做了些什么事情:
public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
if (!session.getTransportMetadata().hasFragmentation()) {
while (in.hasRemaining()) {
if (!doDecode(session, in, out)) {
break;
}
}
return;
}
boolean usingSessionBuffer = true;
IoBuffer buf = (IoBuffer) session.getAttribute(BUFFER);
// If we have a session buffer, append data to that; otherwise
// use the buffer read from the network directly.
if (buf != null) {
boolean appended = false;
// Make sure that the buffer is auto-expanded.
if (buf.isAutoExpand()) {
try {
buf.put(in);
appended = true;
} catch (IllegalStateException e) {
// A user called derivation method (e.g. slice()),
// which disables auto-expansion of the parent buffer.
} catch (IndexOutOfBoundsException e) {
// A user disabled auto-expansion.
}
}
if (appended) {
buf.flip();
} else {
// Reallocate the buffer if append operation failed due to
// derivation or disabled auto-expansion.
buf.flip();
IoBuffer newBuf = IoBuffer.allocate(buf.remaining() + in.remaining()).setAutoExpand(true);
newBuf.order(buf.order());
newBuf.put(buf);
newBuf.put(in);
newBuf.flip();
buf = newBuf;
// Update the session attribute.
session.setAttribute(BUFFER, buf);
}
} else {
buf = in;
usingSessionBuffer = false;
}
for (;;) {
int oldPos = buf.position();
boolean decoded = doDecode(session, buf, out);
if (decoded) {
if (buf.position() == oldPos) {
throw new IllegalStateException("doDecode() can't return true when buffer is not consumed.");
}
if (!buf.hasRemaining()) {
break;
}
} else {
break;
}
}
// if there is any data left that cannot be decoded, we store
// it in a buffer in the session and next time this decoder is
// invoked the session buffer gets appended to
if (buf.hasRemaining()) {
if (usingSessionBuffer && buf.isAutoExpand()) {
buf.compact();
} else {
storeRemainingInSession(buf, session);
}
} else {
if (usingSessionBuffer) {
removeSessionBuffer(session);
}
}
}
查看CumulativeProtocolDecoder会发现它里面存在一个AttributeKey类型的属性对象BUFFER,这个BUFFER实际上就是用于存放断包数据的;
private final AttributeKey BUFFER = new AttributeKey(getClass(), "buffer");
首先第13行获取到上次的断包数据,第16行判断断包数据是否存在,如果存在的话进入if语句块,接着第19行判断当前IoBuffer是否允许扩充,允许的话进入if语句块中,在第21行将断包数据和当前数据进行拼接,同时修改appended标志,接着第31行判断appended的值,如果等于true的话,执行第31行,将IoBuffer置为读模式,否则的话表示当前的IoBuffer是不允许扩充的,那么我们需要在第37行创建一个新的IoBuffer对象对断包数据和当前输入的数据进行拼接,最后在第42行将拼接完成的IoBuffer赋给原先的IoBuffer,随后进入52行的死循环,在第54行调用我们实现抽象类CumulativeProtocolDecoder的doDecode方法进行解码并且获取他的返回值,如果返回值为false的话,则直接退出循环,表示这次是断包,我们需要存储这个断包里面的内容,随后的第71到81行就是将断包内容存储到IoSession,下次在调用decode方法的时候,我们便可以获取到这次存储到IoSession里面的断包内容了,这就是断包处理过程了;
那么粘包是怎么处理的呢?上面我们对52行到66行的讲解有点粗糙,其实这部分就是对粘包的处理,你想想为什么使用死循环就明白了,粘包的意思是有若干的数据在接收方粘成了一块,那么我们就需要多次处理这个消息了,因为我们自己实现的doDecode方法是由解码规则的,粘包中完整数据可能不止一个,我们当然需要循环处理了,直到剩下的消息数据不再是完整的消息或者IoBuffer中不存在数据为止,也就是在第61行和64行我们看到的两个break退出循环;
以上就是MINA中编解码以及粘包、断包的处理啦!