NIO框架之MINA源碼解析(四):粘包與斷包處理及編碼與解碼

目錄(?)[+]

NIO框架之MINA源碼解析(一):背景


NIO框架之MINA源碼解析(二):mina核心引擎


NIO框架之MINA源碼解析(三):底層通信與責任鏈模式應用 



1、粘包與段包


粘包:指TCP協議中,發送方發送的若干包數據到接收方接收時粘成一包,從接收緩衝區看,後一包數據的頭緊接着前一包數據的尾。
造成的可能原因:

    發送端需要等緩衝區滿才發送出去,造成粘包

    接收方不及時接收緩衝區的包,造成多個包接收


斷包:也就是數據不全,比如包太大,就把包分解成多個小包,多次發送,導致每次接收數據都不全。


2、消息傳輸的格式


消息長度+消息頭+消息體  即前N個字節用於存儲消息的長度,用於判斷當前消息什麼時候結束。

消息頭+消息體    即固定長度的消息,前幾個字節爲消息頭,後面的是消息頭。

在MINA中用的是

消息長度+消息體 即前4個字節用於存儲消息的長度,用於判斷當前消息什麼時候結束。


3、編碼與解碼


   在網絡中,信息的傳輸都是通過字節的形式傳輸的,而我們在編寫自己的代碼時,則都是具體的對象,那麼要想我們的對象能夠在網絡中傳輸,就需要編碼與解碼。


   編碼:即把我們的消息編碼成二進制形式,能以字節的形式在網絡中傳輸。

   解碼:即把我們收到的字節解碼成我們代碼中的對象。

   在MINA中對象的編碼與解碼用的都是JDK提供的ObjectOutputStream來實現的。


4、MINA中消息的處理實現


消息的接受處理,我們常用的是TCP協議,而TCP協議會分片的,在下面的代碼中,具體功能就是循環從通道里面讀取數據,直到沒有數據可讀,或者buffer滿了,然後就把接受到的數據發給解碼工廠進行處理。


4.1、消息的接收


[java] view plain copy
 print?
  1. //class AbstractPollingIoProcessor  
  2. private void read(S session) {  
  3.         IoSessionConfig config = session.getConfig();  
  4.         int bufferSize = config.getReadBufferSize();  
  5.         IoBuffer buf = IoBuffer.allocate(bufferSize);  
  6.   
  7.         final boolean hasFragmentation = session.getTransportMetadata().hasFragmentation();  
  8.   
  9.         try {  
  10.             int readBytes = 0;  
  11.             int ret;  
  12.   
  13.             try {  
  14.                 //是否有分片 tcp傳輸會有分片,即把大消息分片成多個小消息再傳輸  
  15.                 if (hasFragmentation) {  
  16.             //read方法非阻塞,沒有讀到數據的時候返回0  
  17.                     while ((ret = read(session, buf)) > 0) {  
  18.                         readBytes += ret;  
  19.                         //buffer 滿了  
  20.                         if (!buf.hasRemaining()) {  
  21.                             break;  
  22.                         }  
  23.                     }  
  24.                 } else {  
  25.                     ret = read(session, buf);  
  26.   
  27.                     if (ret > 0) {  
  28.                         readBytes = ret;  
  29.                     }  
  30.                 }  
  31.             } finally {  
  32.                 buf.flip();  
  33.             }  
  34.   
  35.             if (readBytes > 0) {  
  36.                 IoFilterChain filterChain = session.getFilterChain();  
  37.         //處理消息  
  38.                 filterChain.fireMessageReceived(buf);  
  39.                 buf = null;  
  40.   
  41.                 if (hasFragmentation) {  
  42.                     if (readBytes << 1 < config.getReadBufferSize()) {  
  43.                         session.decreaseReadBufferSize();  
  44.                     } else if (readBytes == config.getReadBufferSize()) {  
  45.                         session.increaseReadBufferSize();  
  46.                     }  
  47.                 }  
  48.             }  
  49.   
  50.             if (ret < 0) {  
  51.                 scheduleRemove(session);  
  52.             }  
  53.         } catch (Throwable e) {  
  54.             if (e instanceof IOException) {  
  55.                 if (!(e instanceof PortUnreachableException)  
  56.                         || !AbstractDatagramSessionConfig.class.isAssignableFrom(config.getClass())  
  57.                         || ((AbstractDatagramSessionConfig) config).isCloseOnPortUnreachable()) {  
  58.                     scheduleRemove(session);  
  59.                 }  
  60.             }  
  61.   
  62.             IoFilterChain filterChain = session.getFilterChain();  
  63.             filterChain.fireExceptionCaught(e);  
  64.         }  
  65.     }  


4.2、解碼與編碼


[java] view plain copy
 print?
  1. //class AbstractIoBuffer  
  2.  public Object getObject(final ClassLoader classLoader) throws ClassNotFoundException {  
  3.     //首先判斷當前buffer中消息長度是否完整,不完整的話直接返回  
  4.         if (!prefixedDataAvailable(4)) {  
  5.             throw new BufferUnderflowException();  
  6.         }  
  7.   
  8.     //消息長度  
  9.         int length = getInt();  
  10.         if (length <= 4) {  
  11.             throw new BufferDataException("Object length should be greater than 4: " + length);  
  12.         }  
  13.   
  14.         int oldLimit = limit();  
  15.     //limit到消息結尾處  
  16.         limit(position() + length);  
  17.         try {  
  18.             ObjectInputStream in = new ObjectInputStream(asInputStream()) {  
  19.                 @Override  
  20.                 protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {  
  21.                     int type = read();  
  22.                     if (type < 0) {  
  23.                         throw new EOFException();  
  24.                     }  
  25.                     switch (type) {  
  26.                     case 0// NON-Serializable class or Primitive types  
  27.                         return super.readClassDescriptor();  
  28.                     case 1// Serializable class  
  29.                         String className = readUTF();  
  30.                         Class<?> clazz = Class.forName(className, true, classLoader);  
  31.                         return ObjectStreamClass.lookup(clazz);  
  32.                     default:  
  33.                         throw new StreamCorruptedException("Unexpected class descriptor type: " + type);  
  34.                     }  
  35.                 }  
  36.   
  37.                 @Override  
  38.                 protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {  
  39.                     String name = desc.getName();  
  40.                     try {  
  41.                         return Class.forName(name, false, classLoader);  
  42.                     } catch (ClassNotFoundException ex) {  
  43.                         return super.resolveClass(desc);  
  44.                     }  
  45.                 }  
  46.             };  
  47.             return in.readObject();  
  48.         } catch (IOException e) {  
  49.             throw new BufferDataException(e);  
  50.         } finally {  
  51.             limit(oldLimit);  
  52.         }  
  53.     }  
  54.   
  55. //判斷當前消息是否完整   
  56. public boolean prefixedDataAvailable(int prefixLength, int maxDataLength) {  
  57.         if (remaining() < prefixLength) {  
  58.             return false;  
  59.         }  
  60.   
  61.         int dataLength;  
  62.         switch (prefixLength) {  
  63.         case 1:  
  64.             dataLength = getUnsigned(position());  
  65.             break;  
  66.         case 2:  
  67.             dataLength = getUnsignedShort(position());  
  68.             break;  
  69.         case 4:  
  70.             dataLength = getInt(position());  
  71.             break;  
  72.         default:  
  73.             throw new IllegalArgumentException("prefixLength: " + prefixLength);  
  74.         }  
  75.   
  76.         if (dataLength < 0 || dataLength > maxDataLength) {  
  77.             throw new BufferDataException("dataLength: " + dataLength);  
  78.         }  
  79.     //判斷當前消息是否完整   
  80.         return remaining() - prefixLength >= dataLength;  
  81.     }  
  82.   
  83. //編碼  
  84.  public IoBuffer putObject(Object o) {  
  85.         int oldPos = position();  
  86.         skip(4); // Make a room for the length field.預留4個字節用於存儲消息長度  
  87.         try {  
  88.             ObjectOutputStream out = new ObjectOutputStream(asOutputStream()) {  
  89.                 @Override  
  90.                 protected void writeClassDescriptor(ObjectStreamClass desc) throws IOException {  
  91.                     try {  
  92.                         Class<?> clz = Class.forName(desc.getName());  
  93.                         if (!Serializable.class.isAssignableFrom(clz)) { // NON-Serializable class  
  94.                             write(0);  
  95.                             super.writeClassDescriptor(desc);  
  96.                         } else { // Serializable class  
  97.                             write(1);  
  98.                             writeUTF(desc.getName());  
  99.                         }  
  100.                     } catch (ClassNotFoundException ex) { // Primitive types  
  101.                         write(0);  
  102.                         super.writeClassDescriptor(desc);  
  103.                     }  
  104.                 }  
  105.             };  
  106.             out.writeObject(o);  
  107.             out.flush();  
  108.         } catch (IOException e) {  
  109.             throw new BufferDataException(e);  
  110.         }  
  111.   
  112.         // Fill the length field  
  113.         int newPos = position();  
  114.         position(oldPos);  
  115.     //存儲消息長度  
  116.         putInt(newPos - oldPos - 4);  
  117.         position(newPos);  
  118.         return this;  
  119.     }  


4.3、斷包與粘包處理

[java] view plain copy
 print?
  1. // class CumulativeProtocolDecoder  
  2.  public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {  
  3.     //是否有分片,tcp 有分片  
  4.         if (!session.getTransportMetadata().hasFragmentation()) {  
  5.             while (in.hasRemaining()) {  
  6.                 if (!doDecode(session, in, out)) {  
  7.                     break;  
  8.                 }  
  9.             }  
  10.   
  11.             return;  
  12.         }  
  13.   
  14.     // 1、斷包處理  
  15.     // 2、處理粘包  
  16.         boolean usingSessionBuffer = true;  
  17.     //session中是否有斷包情況(上次處理後),斷包保存在session中  
  18.         IoBuffer buf = (IoBuffer) session.getAttribute(BUFFER);  
  19.         // If we have a session buffer, append data to that; otherwise  
  20.         // use the buffer read from the network directly.  
  21.         if (buf != null) {//有斷包,則把當前包拼接到斷包裏面  
  22.             boolean appended = false;  
  23.             // Make sure that the buffer is auto-expanded.  
  24.             if (buf.isAutoExpand()) {  
  25.                 try {  
  26.                     buf.put(in);  
  27.                     appended = true;  
  28.                 } catch (IllegalStateException e) {  
  29.                     // A user called derivation method (e.g. slice()),  
  30.                     // which disables auto-expansion of the parent buffer.  
  31.                 } catch (IndexOutOfBoundsException e) {  
  32.                     // A user disabled auto-expansion.  
  33.                 }  
  34.             }  
  35.   
  36.             if (appended) {  
  37.                 buf.flip();  
  38.             } else {  
  39.                 // Reallocate the buffer if append operation failed due to  
  40.                 // derivation or disabled auto-expansion.  
  41.                 buf.flip();  
  42.                 IoBuffer newBuf = IoBuffer.allocate(buf.remaining() + in.remaining()).setAutoExpand(true);  
  43.                 newBuf.order(buf.order());  
  44.                 newBuf.put(buf);  
  45.                 newBuf.put(in);  
  46.                 newBuf.flip();  
  47.                 buf = newBuf;  
  48.   
  49.                 // Update the session attribute.  
  50.                 session.setAttribute(BUFFER, buf);  
  51.             }  
  52.         } else {  
  53.             buf = in;  
  54.             usingSessionBuffer = false;  
  55.         }  
  56.   
  57.     //2 粘包處理,可能buffer中有多個消息,需要多次處理(解碼)每個消息,直到消息處理完,或者剩下的消息不是一個完整的消息或者buffer沒有數據了  
  58.   
  59.         for (;;) {  
  60.             int oldPos = buf.position();  
  61.             boolean decoded = doDecode(session, buf, out);  
  62.             if (decoded) {//解碼 成功  
  63.                 if (buf.position() == oldPos) {  
  64.                     throw new IllegalStateException("doDecode() can't return true when buffer is not consumed.");  
  65.                 }  
  66.         //buffer空了  
  67.                 if (!buf.hasRemaining()) {//buffer沒有數據了  
  68.                     break;  
  69.                 }  
  70.             } else {//剩下的消息不是一個完整的消息,斷包出現了  
  71.                 break;  
  72.             }  
  73.         }  
  74.   
  75.         // if there is any data left that cannot be decoded, we store  
  76.         // it in a buffer in the session and next time this decoder is  
  77.         // invoked the session buffer gets appended to  
  78.         if (buf.hasRemaining()) {//剩下的消息不是一個完整的消息,斷包出現了  
  79.         //如果斷包已經保存在session中,則更新buffer,沒有的話,就把剩下的斷包保存在session中  
  80.             if (usingSessionBuffer && buf.isAutoExpand()) {  
  81.                 buf.compact();  
  82.             } else {  
  83.                 storeRemainingInSession(buf, session);  
  84.             }  
  85.         } else {  
  86.             if (usingSessionBuffer) {  
  87.                 removeSessionBuffer(session);  
  88.             }  
  89.         }  
  90.     }  


[java] view plain copy
 print?
  1. //class  ObjectSerializationDecoder  
  2.  protected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {  
  3.     //首先判斷當前buffer中消息長度是否完整,不完整的話直接返回  
  4.         if (!in.prefixedDataAvailable(4, maxObjectSize)) {  
  5.             return false;  
  6.         }  
  7.   
  8.         out.write(in.getObject(classLoader));  
  9.         return true;  
  10.     }  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章