在真正底层IO操作的时候我们只能发送ByteBuffer,在netty中,转换为ChannelBuffer,因此如果我们想发送字符串/对象或者自定义格式的数据,就需要编写自己的编码/.解码器,进行转换,这部分被netty统一抽象为pipeline&handler

Netty中的pipeline可以理解为连接两台计算机之间的管道,管道中有多个部分,即handler,假设A/B两台主机,当AB发送的时候,对A来说,表示数据往下流入管道,经过层层处理(encode)之后发送出去,对B来说,数据从管道网上层层处理(decode)之后,到达接收端,因此handler分两类,分别是UpStreamDownStream数据统一以event表示

(MessageEvent)

Handler的处理流程如下:

 

收到的消息走的是UpStream,表示数据是经过层层剥离以后上来的,发出去的消息走的是DownStream,表示数据是经过层层封装以后发出去的,比如下面的代码:

  1. pipeline.addLast(“framer”, new DelimiterBasedFrameDecoder(
  2.                 8192, Delimiters.lineDelimiter()));
  3.         pipeline.addLast(“decoder”, new StringDecoder());
  4.         pipeline.addLast(“encoder”, new StringEncoder());
  5.  
  6.         // and then business logic.
  7.         pipeline.addLast(“handler”, new TelnetClientHandler());
  8.         pipeline.addLast(“encoder2″, new StringEncoder());
 

发消息的时候,会经过encode,顺序是encode2—–>encoder

收消息的时候,会经过decoder,顺序是framerdecoderhandler

感觉都是同个顺序出去的话,是不是顺手点呢?

内部结构

从上面的图可以看到,所有的handler被加入以后,会被封装成HandlerContext(包括PreHandlerNextHandler等),然后组成一个链状。UpStream消息从队头开始,DownStream从队尾开始

  1. DefaultChannelHandlerContext head = getActualUpstreamContext(this.head);
  2. DefaultChannelHandlerContext tail = getActualDownstreamContext(this.tail);

 

数据的拆分/组装和常用的几个Handler

因为TCP/IP是面向流的协议,数据会被拆分成小的包,并且在接收端重新组装,比如我们发送:

+—–+—–+—–+

| ABC | DEF | GHI |

+—–+—–+—–+

接收端可能受到如下:

+—-+——-+—+—+

| AB | CDEFG | H | I |

+—-+——-+—+—+

因此我们需要将接收到的数据进行整理成可识别的数据格式,在netty中这类属于FrameDecoder,另外还有一类OneToOneHandler,这类的decoder一般要和一个FrameDecoder一起使用。

可以看到都继承自FrameDecoder类,在这个类中有一个成员变量cumulation,因为底层的缓冲区都是每次读完以后就马上释放的,因此就需要一个Buffer来累积的数据:

  1. public abstract class FrameDecoder extends SimpleChannelUpstreamHandler {
  2.     private ChannelBuffer cumulation;
  3. }

下面是处理累计数据的时候可能遇到的场景:

1.读到的数据不足以解析成一条逻辑数据,继续等待

2.读到的数据包含多条逻辑数据

3.读完一条完整数据之后可能还会有剩余的数据

FrameDecoder主要处理边界逻辑,至于什么时候能够解析出一条完整的逻辑数据则交给子类去实现,这里使用了ChannerBuffer中的CompositeChannelBuffer实现0拷贝

  1. //cumulation表示缓存的数据
  2. //1.如果之前没有缓存数据
  3.     if (cumulation == null) {
  4.             // 1.1直接尝试decode
  5.             callDecode(ctx, e.getChannel(), input, e.getRemoteAddress());
  6.             if (input.readable()) {
  7.                 //1.2 decode之后还有剩余,则将剩下的放到新的ChannelBuffer
  8.       //如果在callDecode并且完整解析出消息之后,抛出异常,可能会使得input剩余的数据丢失 https://github.com/netty/netty/issues/364 
  9.                 (this.cumulation = newCumulationBuffer(ctx, 
  10.     input.readableBytes())).writeBytes(input);
  11.             }
  12.         } else {
  13.     //2.之前就有缓存数据
  14.             assert cumulation.readable();
  15.             boolean fit = false;
  16.             
  17.             //假设新到的数据有20字节可写
  18.             int readable = input.readableBytes();
  19.             //cumlation只剩余10可写(即还需要10字节的空间)
  20.             int writable = cumulation.writableBytes();
  21.             int w = writable - readable;
  22.             //缓冲区可写的小于新写入的
  23.             if (< 0) {
  24.     //看之前已经读到了哪里,也就是说之前有多少数据已经废弃
  25.                 int readerIndex = cumulation.readerIndex();
  26.                 //如果丢弃已经读过的部分,可以将新写入的数据写入,则丢弃
  27.                 if (+ readerIndex >= 0) {
  28.                     // the input will fit if we discard all read bytes, so do it
  29.                     cumulation.discardReadBytes();
  30.                     fit = true;
  31.                 }
  32.             } else {
  33.                 // ok the input fit into the cumulation buffer
  34.                 fit = true;
  35.             }
  36.             
  37.             //cumulation中是否有足够的空间,如果没有,则拼成一个CompositeChannelBuffer,如果有则写入
  38.             ChannelBuffer buf;
  39.             if (fit) {
  40.                 // the input fit in the cumulation buffer so copy it over
  41.                 buf = this.cumulation;
  42.                 buf.writeBytes(input);
  43.             } else {
  44.                 // wrap the cumulation and input 
  45.                 buf = ChannelBuffers.wrappedBuffer(cumulation, input);
  46.                 this.cumulation = buf;
  47.             }
  48.  
  49.  
  50.             callDecode(ctx, e.getChannel(), buf, e.getRemoteAddress());
  51.        //如果数据全部解析完毕,清空缓冲区,否则将剩下的部分取出
  52.        //如果在callDecode并且完整解析出消息之后,抛出异常;cumulation没有清空,那么下次来的时候assert会失败;或者剩余数据丢失  https://github.com/netty/netty/issues/364 
  53.             if (!buf.readable()) {
  54.                 // nothing readable left so reset the state
  55.                 this.cumulation = null;
  56.             } else {
  57.                 // create a new buffer and copy the readable buffer into it
  58.                 this.cumulation = newCumulationBuffer(ctx, buf.readableBytes());
  59.                 this.cumulation.writeBytes(buf);
  60.             }
  61.         }
  62. ==============================================================
  63.  private void callDecode(
  64.             ChannelHandlerContext context, Channel channel,
  65.             ChannelBuffer cumulation, SocketAddress remoteAddress) throws Exception {
  66.  
  67.         while (cumulation.readable()) {
  68.             int oldReaderIndex = cumulation.readerIndex();
  69.     // 是否解析成功的逻辑由子类实现
  70.             Object frame = decode(context, channel, cumulation);
  71.             if (frame == null) {
  72.                 if (oldReaderIndex == cumulation.readerIndex()) {
  73.                     // Seems like more data is required.
  74.                     // Let us wait for the next notification.
  75.                     break;
  76.                 } else {
  77.                     // Previous data has been discarded.
  78.                     // Probably it is reading on.
  79.                     continue;
  80.                 }
  81.             } else if (oldReaderIndex == cumulation.readerIndex()) {
  82.                 throw new IllegalStateException(
  83.                         “decode() method must read at least one byte ” +
  84.                         “if it returned a frame (caused by: ” + getClass() + “)”);
  85.             }
  86.        // 如果解析出来多条数据,判断是否分开发送还是一起发送
  87.     
  88.             unfoldAndFireMessageReceived(context, remoteAddress, frame);
  89.         }
  90.     }

下面是几个常用的FrameDecoder

1.FixLengthFrameDecoder,定长的解码器

  1.     if (buffer.readableBytes() < frameLength) {
  2.             return null;
  3.         } else {
  4.             return buffer.readBytes(frameLength);
  5.         }

2.DelimiterBasedFrameDecoder,指定分隔符的decoder

基于分隔符(可以多个,以最前面的为准)的解码器,可以指定最大长度

假设指定\n 为分隔符,最大长度100

        a.在超过100长度以后还没有找到\n的,当前的数据以及之后的数据区全部丢弃,直到找到\n

        b.没有超过最大长度,继续保留数据

3.LengthFieldBasedFrameDecoder

基于长度的解码器,比如”长度+内容主要是下面4个属性

lengthFieldOffset   = 0 在某些协议可能会以固定的特殊字符开头,这表示这些字符的长度

lengthFieldLength   = 2 表示长度的字节

lengthAdjustment    = 0 某些协议长度可能包括头信息,这里可以出去头

initialBytesToStrip = 0 (= do not strip header) 跳过多少个头字节

http://docs.jboss.org/netty/3.2/api/org/jboss/netty/handler/codec/frame/LengthFieldBasedFrameDecoder.html 这里有详细的说明

其他一些decoder

1.OneToOneHandler,比如StringEncoder/StringDecoder,如果经常要发送字符串,那这个比较有用

2ReplayingDecoder: 它是FrameDecoder的一个变种子类,它相对于FrameDecoder是非阻塞解码。也就是说,使用 FrameDecoder时需要考虑到读到的数据有可能是不完整的,而使用ReplayingDecoder就可以假定读到了全部的数据。

3ObjectEncoder ObjectDecoder:编码解码序列化的Java对象。

4HttpRequestEncoder和 HttpRequestDecoderhttp协议处理。

http://www.kafka0102.com/2010/06/167.html )

经过decode出完整的逻辑数据后,会进行真正的处理,或者在将数据encode成字节数据,就会进行发送。后面再看看真正进行IO read的时候一些高效的缓冲区操作,以及write的时候流控等。