版權聲明:本文爲博主原創文章,未經博主允許不得轉載。
NIO框架之MINA源碼解析(一):背景
NIO框架之MINA源碼解析(二):mina核心引擎
NIO框架之MINA源碼解析(三):底層通信與責任鏈模式應用
1、粘包與段包
粘包:指TCP協議中,發送方發送的若干包數據到接收方接收時粘成一包,從接收緩衝區看,後一包數據的頭緊接着前一包數據的尾。
造成的可能原因:
發送端需要等緩衝區滿才發送出去,造成粘包
接收方不及時接收緩衝區的包,造成多個包接收
斷包:也就是數據不全,比如包太大,就把包分解成多個小包,多次發送,導致每次接收數據都不全。
2、消息傳輸的格式
消息長度+消息頭+消息體 即前N個字節用於存儲消息的長度,用於判斷當前消息什麼時候結束。
消息頭+消息體 即固定長度的消息,前幾個字節爲消息頭,後面的是消息頭。
在MINA中用的是
消息長度+消息體 即前4個字節用於存儲消息的長度,用於判斷當前消息什麼時候結束。
3、編碼與解碼
在網絡中,信息的傳輸都是通過字節的形式傳輸的,而我們在編寫自己的代碼時,則都是具體的對象,那麼要想我們的對象能夠在網絡中傳輸,就需要編碼與解碼。
編碼:即把我們的消息編碼成二進制形式,能以字節的形式在網絡中傳輸。
解碼:即把我們收到的字節解碼成我們代碼中的對象。
在MINA中對象的編碼與解碼用的都是JDK提供的ObjectOutputStream來實現的。
4、MINA中消息的處理實現
消息的接受處理,我們常用的是TCP協議,而TCP協議會分片的,在下面的代碼中,具體功能就是循環從通道里面讀取數據,直到沒有數據可讀,或者buffer滿了,然後就把接受到的數據發給解碼工廠進行處理。
4.1、消息的接收
- //class AbstractPollingIoProcessor
- private void read(S session) {
- IoSessionConfig config = session.getConfig();
- int bufferSize = config.getReadBufferSize();
- IoBuffer buf = IoBuffer.allocate(bufferSize);
- final boolean hasFragmentation = session.getTransportMetadata().hasFragmentation();
- try {
- int readBytes = 0;
- int ret;
- try {
- //是否有分片 tcp傳輸會有分片,即把大消息分片成多個小消息再傳輸
- if (hasFragmentation) {
- //read方法非阻塞,沒有讀到數據的時候返回0
- while ((ret = read(session, buf)) > 0) {
- readBytes += ret;
- //buffer 滿了
- if (!buf.hasRemaining()) {
- break;
- }
- }
- } else {
- ret = read(session, buf);
- if (ret > 0) {
- readBytes = ret;
- }
- }
- } finally {
- buf.flip();
- }
- if (readBytes > 0) {
- IoFilterChain filterChain = session.getFilterChain();
- //處理消息
- filterChain.fireMessageReceived(buf);
- buf = null;
- if (hasFragmentation) {
- if (readBytes << 1 < config.getReadBufferSize()) {
- session.decreaseReadBufferSize();
- } else if (readBytes == config.getReadBufferSize()) {
- session.increaseReadBufferSize();
- }
- }
- }
- if (ret < 0) {
- scheduleRemove(session);
- }
- } catch (Throwable e) {
- if (e instanceof IOException) {
- if (!(e instanceof PortUnreachableException)
- || !AbstractDatagramSessionConfig.class.isAssignableFrom(config.getClass())
- || ((AbstractDatagramSessionConfig) config).isCloseOnPortUnreachable()) {
- scheduleRemove(session);
- }
- }
- IoFilterChain filterChain = session.getFilterChain();
- filterChain.fireExceptionCaught(e);
- }
- }
4.2、解碼與編碼
- //class AbstractIoBuffer
- public Object getObject(final ClassLoader classLoader) throws ClassNotFoundException {
- //首先判斷當前buffer中消息長度是否完整,不完整的話直接返回
- if (!prefixedDataAvailable(4)) {
- throw new BufferUnderflowException();
- }
- //消息長度
- int length = getInt();
- if (length <= 4) {
- throw new BufferDataException("Object length should be greater than 4: " + length);
- }
- int oldLimit = limit();
- //limit到消息結尾處
- limit(position() + length);
- try {
- ObjectInputStream in = new ObjectInputStream(asInputStream()) {
- @Override
- protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
- int type = read();
- if (type < 0) {
- throw new EOFException();
- }
- switch (type) {
- case 0: // NON-Serializable class or Primitive types
- return super.readClassDescriptor();
- case 1: // Serializable class
- String className = readUTF();
- Class<?> clazz = Class.forName(className, true, classLoader);
- return ObjectStreamClass.lookup(clazz);
- default:
- throw new StreamCorruptedException("Unexpected class descriptor type: " + type);
- }
- }
- @Override
- protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
- String name = desc.getName();
- try {
- return Class.forName(name, false, classLoader);
- } catch (ClassNotFoundException ex) {
- return super.resolveClass(desc);
- }
- }
- };
- return in.readObject();
- } catch (IOException e) {
- throw new BufferDataException(e);
- } finally {
- limit(oldLimit);
- }
- }
- //判斷當前消息是否完整
- public boolean prefixedDataAvailable(int prefixLength, int maxDataLength) {
- if (remaining() < prefixLength) {
- return false;
- }
- int dataLength;
- switch (prefixLength) {
- case 1:
- dataLength = getUnsigned(position());
- break;
- case 2:
- dataLength = getUnsignedShort(position());
- break;
- case 4:
- dataLength = getInt(position());
- break;
- default:
- throw new IllegalArgumentException("prefixLength: " + prefixLength);
- }
- if (dataLength < 0 || dataLength > maxDataLength) {
- throw new BufferDataException("dataLength: " + dataLength);
- }
- //判斷當前消息是否完整
- return remaining() - prefixLength >= dataLength;
- }
- //編碼
- public IoBuffer putObject(Object o) {
- int oldPos = position();
- skip(4); // Make a room for the length field.預留4個字節用於存儲消息長度
- try {
- ObjectOutputStream out = new ObjectOutputStream(asOutputStream()) {
- @Override
- protected void writeClassDescriptor(ObjectStreamClass desc) throws IOException {
- try {
- Class<?> clz = Class.forName(desc.getName());
- if (!Serializable.class.isAssignableFrom(clz)) { // NON-Serializable class
- write(0);
- super.writeClassDescriptor(desc);
- } else { // Serializable class
- write(1);
- writeUTF(desc.getName());
- }
- } catch (ClassNotFoundException ex) { // Primitive types
- write(0);
- super.writeClassDescriptor(desc);
- }
- }
- };
- out.writeObject(o);
- out.flush();
- } catch (IOException e) {
- throw new BufferDataException(e);
- }
- // Fill the length field
- int newPos = position();
- position(oldPos);
- //存儲消息長度
- putInt(newPos - oldPos - 4);
- position(newPos);
- return this;
- }
4.3、斷包與粘包處理
- // class CumulativeProtocolDecoder
- public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
- //是否有分片,tcp 有分片
- if (!session.getTransportMetadata().hasFragmentation()) {
- while (in.hasRemaining()) {
- if (!doDecode(session, in, out)) {
- break;
- }
- }
- return;
- }
- // 1、斷包處理
- // 2、處理粘包
- boolean usingSessionBuffer = true;
- //session中是否有斷包情況(上次處理後),斷包保存在session中
- 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;
- }
- //2 粘包處理,可能buffer中有多個消息,需要多次處理(解碼)每個消息,直到消息處理完,或者剩下的消息不是一個完整的消息或者buffer沒有數據了
- 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.");
- }
- //buffer空了
- if (!buf.hasRemaining()) {//buffer沒有數據了
- 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()) {//剩下的消息不是一個完整的消息,斷包出現了
- //如果斷包已經保存在session中,則更新buffer,沒有的話,就把剩下的斷包保存在session中
- if (usingSessionBuffer && buf.isAutoExpand()) {
- buf.compact();
- } else {
- storeRemainingInSession(buf, session);
- }
- } else {
- if (usingSessionBuffer) {
- removeSessionBuffer(session);
- }
- }
- }
- //class ObjectSerializationDecoder
- protected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
- //首先判斷當前buffer中消息長度是否完整,不完整的話直接返回
- if (!in.prefixedDataAvailable(4, maxObjectSize)) {
- return false;
- }
- out.write(in.getObject(classLoader));
- return true;
- }