我们接下来就看和业务息息相关的解码器,首先我们来看FrameDecoder,这个东西应该是所有的解码器都会实现这个,所以我们来重点看一下。
FrameDecoder产生的根源就是TCP/IP数据包的传输方式决定的,包在传输的过程中会分片和重组,
正如javadoc里面所说的:
客户端在发送的时候的序列如下:
+-----+-----+-----+
| ABC | DEF | GHI |
+-----+-----+-----+
服务器端在接受到后可能会变成下面的序列:
+----+-------+---+---+
| AB | CDEFG | H | I |
+----+-------+---+---+
FrameDecoder帮助我们将接受到的数据包整理成有意义的数据帧,例如,可以帮助我们将数据包整理成
下面的数据格式:
+-----+-----+-----+
| ABC | DEF | GHI |
+-----+-----+-----+
我们接下来就看看FrameDecoder的实现吧:
FrameDecoder是继承SimpleChannelUpstreamHandler的,我们首先来看一下messageReceived的实现:
@Override public void messageReceived( ChannelHandlerContext ctx, MessageEvent e) throws Exception { Object m = e.getMessage(); if (!(m instanceof ChannelBuffer)) { ctx.sendUpstream(e); return; } ChannelBuffer input = (ChannelBuffer) m; if (!input.readable()) { return; } ChannelBuffer cumulation = cumulation(ctx); if (cumulation.readable()) { cumulation.discardReadBytes(); cumulation.writeBytes(input); callDecode(ctx, e.getChannel(), cumulation, e.getRemoteAddress()); } else { callDecode(ctx, e.getChannel(), input, e.getRemoteAddress()); if (input.readable()) { cumulation.writeBytes(input); } } }
这个里面首先会检查input的可读性,这个比较好理解,关键是cumulation,
我们首先来看一下cumulation的实现吧:
private ChannelBuffer cumulation(ChannelHandlerContext ctx) { ChannelBuffer c = cumulation; if (c == null) { c = ChannelBuffers.dynamicBuffer( ctx.getChannel().getConfig().getBufferFactory()); cumulation = c; } return c; }
这个函数很简单,就是如果cumulation为空的时候初始化一下,如果不为空,就返回。我们得思考一下什么时候cumulation为空,什么时候不为空。我们再回过头来看一下上面的实现吧。如果cumulation可读,cumulation.discardReadBytes函数的作用是将0到readIndex之间的空间释放掉,将readIndex和writeIndex都重新标记一下。然后将读到的数据写到buffer里面。如果cumulation不可读,在调callDecode,如果发现从不可读状态到可读状态,则将读到的数据写到缓存区里面。
我们再来看callDecode的实现:
private void callDecode( ChannelHandlerContext context, Channel channel, ChannelBuffer cumulation, SocketAddress remoteAddress) throws Exception { while (cumulation.readable()) { int oldReaderIndex = cumulation.readerIndex(); Object frame = decode(context, channel, cumulation); if (frame == null) { if (oldReaderIndex == cumulation.readerIndex()) { // Seems like more data is required. // Let us wait for the next notification. break; } else { // Previous data has been discarded. // Probably it is reading on. continue; } } else if (oldReaderIndex == cumulation.readerIndex()) { throw new IllegalStateException( "decode() method must read at least one byte " + "if it returned a frame (caused by: " + getClass() + ")"); } unfoldAndFireMessageReceived(context, remoteAddress, frame); } if (!cumulation.readable()) { this.cumulation = null; } }
这个里面上面是一个循环,首先将读指针备份一下,decode方法是交个子类实现的一个抽象方这个用来实现具体数据分帧的算法,从这个里面看到如果子类没有读到一帧数据,则返回null所以下面有一个判断,是一点数据没有读呢,还是读了一点,如果一点都没有读,就不需要再检测了等下一次messageRecieved进行通知,如果发现读了一点数据,就调用下一次分帧。如果读了一帧数据就发送一个通知,unfold是针对读到的循环数据要不要打开的意思。到最后如果发现不是可读状态,
cumulation将会被设置成null。
最后来看一下cleanup的实现
private void cleanup(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { try { ChannelBuffer cumulation = this.cumulation; if (cumulation == null) { return; } else { this.cumulation = null; } if (cumulation.readable()) { // Make sure all frames are read before notifying a closed channel. callDecode(ctx, ctx.getChannel(), cumulation, null); } // Call decodeLast() finally. Please note that decodeLast() is // called even if there's nothing more to read from the buffer to // notify a user that the connection was closed explicitly. Object partialFrame = decodeLast(ctx, ctx.getChannel(), cumulation); if (partialFrame != null) { unfoldAndFireMessageReceived(ctx, null, partialFrame); } } finally { ctx.sendUpstream(e); } }
在这个里面一般来说是在socket断开的时候调用,这个时候如果发现buffer还是可读状态,还会努力的确保所有的数据已经被分帧,然后调用decodeLast