1. 程式人生 > >Mina 粘包、拆包的實現-網上常見的程式碼有bug。。

Mina 粘包、拆包的實現-網上常見的程式碼有bug。。

mina的粘包拆包其實是蠻簡單的,只是一開始沒搞清楚原理。

我們要約定資料包的格式,我這裡的是(4個位元組長度+json的string字串)

1:寫一個

ProtocolCodecFactory類,用來攔截資料包處理

內容如下

public class MessageCodecFactory implements ProtocolCodecFactory {

    private final DataEncoderEx encoder;

    private final DataDecoderEx decoder;

    public MessageCodecFactory() {

        encoder = new DataEncoderEx();

        decoder = new DataDecoderEx();

    }

/* (non-Javadoc)

     * @see org.apache.mina.filter.codec.ProtocolCodecFactory#getDecoder(org.apache.mina.core.session.IoSession)

     */

@Override

    public ProtocolDecoder getDecoder(IoSession session) throws

Exception {

        return decoder;

    }

/* (non-Javadoc)

     * @see org.apache.mina.filter.codec.ProtocolCodecFactory#getEncoder(org.apache.mina.core.session.IoSession)

     */

@Override

    public ProtocolEncoder getEncoder(IoSession session) throws Exception {

        return encoder;

    }

}


2:在chain裡面註冊解碼器

chain.addLast("codec", new ProtocolCodecFilter(new MessageCodecFactory()));

注意放在多執行緒上面,否則會導致解碼混亂的情況

3:實現decode和encoder

CumulativeProtocolDecoder 這個類的作用很好,我貼一個網上的總結

A. 你的doDecode()方法返回true 時,CumulativeProtocolDecoder 的decode()方法會首
先判斷你是否在doDecode()方法中從內部的IoBuffer 緩衝區讀取了資料,如果沒有,

則會丟擲非法的狀態異常,也就是你的doDecode()方法返回true 就表示你已經消費了
本次資料(相當於聊天室中一個完整的訊息已經讀取完畢),進一步說,也就是此時你
必須已經消費過內部的IoBuffer 緩衝區的資料(哪怕是消費了一個位元組的資料)。如果
驗證過通過,那麼CumulativeProtocolDecoder 會檢查緩衝區內是否還有資料未讀取,
如果有就繼續呼叫doDecode()方法,沒有就停止對doDecode()方法的呼叫,直到有新
的資料被緩衝。
B. 當你的doDecode()方法返回false 時,CumulativeProtocolDecoder 會停止對doDecode()
方法的呼叫,但此時如果本次資料還有未讀取完的,就將含有剩餘資料的IoBuffer 緩
衝區儲存到IoSession 中,以便下一次資料到來時可以從IoSession 中提取合併。如果
發現本次資料全都讀取完畢,則清空IoBuffer 緩衝區。
簡而言之,當你認為讀取到的資料已經夠解碼了,那麼就返回true,否則就返回false。這
個CumulativeProtocolDecoder 其實最重要的工作就是幫你完成了資料的累積,因為這個工
作是很煩瑣的。


也就是說返回true,那麼CumulativeProtocolDecoder會再次呼叫decoder,並把剩餘的資料發下來

返回false就不處理剩餘的,當有新資料包來的時候把剩餘的和新的拼接在一起然後再呼叫decoder

public class DataDecoderEx extends CumulativeProtocolDecoder {

@Override

protected boolean doDecode(IoSession session, IoBuffer in,

ProtocolDecoderOutput out) throws Exception {

// TODO Auto-generated method stub

if(in.remaining()<4)//這裡很關鍵,網上很多程式碼都沒有這句,是用來當拆包時候剩餘長度小於4的時候的保護,不加就出錯咯 

{

returnfalse;

}

        if (in.remaining() > 1) {

            in.mark();//標記當前位置,以便reset 

            int length =in.getInt(in.position());

            if(length > in.remaining()-4){//如果訊息內容不夠,則重置,相當於不讀取size   

            System.out.println("package notenough  left="+in.remaining()+" length="+length);

                in.reset();   

returnfalse;//接收新資料,以拼湊成完整資料   

            }else{  

            System.out.println("package ="+in.toString()); 

            in.getInt();

                byte[] bytes = new byte[length]; 

                in.get(bytes, 0, length);   

                String str = new String(bytes,"UTF-8");    

                if(null != str && str.length() > 0){   

                String strOut = DateSecret.decryptDES(str);//別看這裡的處理,這裡是我的資料包解密演算法~你可以直接拿str當資料

                out.write(strOut);    

                }   

                if(in.remaining() > 0){//如果讀取內容後還粘了包,就讓父類再給一次,進行下一次解析  

//System.out.println("package left="+in.remaining()+" data="+in.toString()); 

                }   

                return true;//這裡有兩種情況1:沒資料了,那麼就結束當前呼叫,有資料就再次呼叫

            }   

        }   

returnfalse;//處理成功,讓父類進行接收下個包   

}

}

public class DataEncoderEx extends ProtocolEncoderAdapter{

/* (non-Javadoc)

     * @see org.apache.mina.filter.codec.ProtocolEncoder#encode(org.apache.mina.core.session.IoSession, java.lang.Object, org.apache.mina.filter.codec.ProtocolEncoderOutput)

     */

    public void encode(IoSession session, Object message,

            ProtocolEncoderOutput out) throws Exception {

    System.out.println(message);

    IoBuffer buf = IoBuffer.allocate(100).setAutoExpand(true);

    String strOut = DateSecret.encryptDES(message.toString());//別看這裡的處理,這裡是我的資料包加密演算法~你可以直接拿message.toString當資料

    buf.putInt(strOut.getBytes(Charset.forName("utf-8")).length);

buf.putString(strOut,Charset.forName("utf-8").newEncoder());

buf.flip();

out.write(buf);

    }

}


這樣基本沒神馬問題,如有問題我會繼續補充