【迷你微信】基於MINA、Hibernate、Spring、Protobuf的即時聊天系統:2.技術簡介之MinaFilter(1)

項目Logo
歡迎閱讀個人開源項目《迷你微信》服務器《迷你微信》客戶端html

Filter

filter:過濾器?(不知道是否是這麼翻譯,算了知道意思就行了╮(╯▽╰)╭),這種東西在不少語言中都是有的,功能是在某兩層之間插入一層,進行攔截加工處理,這樣能夠方便的添加和刪除處理,而且能夠添加多層的Fileter。java

在Mina的Filter之中,filter是有序的,也就是說,根據你加入filter的方式不一樣,運行的順序也是不一樣的,加入方法有點相似於鏈表。對Mina中Filter的詳細使用請參考Mina Filter,在這裏,咱們只概述兩種Filter的使用:git

ProtocolCodecFilter

協議編碼過濾器,這是一個在服務器接收到客戶端數據,或者服務器往客戶端發送數據時使用的一個編碼器(在客戶端的使用亦然),因爲網絡的傳輸只能經過字節流或者字符串,因此數據對象的發送接收都必須經過編碼(成字節流)和解碼(成對象)。這時,只要在網絡層添加這麼一個ProtocolCodecFilter,即可以隱藏編碼和解碼的實現模塊,而對邏輯層的表現爲直接發送對象,接收對象,簡便了使用。廢話很少說,我們先來看看ProtocolCodecFilter的使用:github

首先,在開啓服務器網絡鏈接的時候添加一個ProtocolCodecFilter (代碼來自開源項目《迷你微信》服務器apache

public void init() {
        // 本身寫的,負責處理網絡層回調的類
        MinaServerHandle minaServerHandle = new MinaServerHandle
        // 創建一個NIO(非阻塞)的鏈接
        acceptor = new NioSocketAcceptor();
        // 添加 ProtocolCodecFilter
    acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new MinaEncoder(), new MinaDecoder()));
        acceptor.setHandler(minaServerHandle);
        try {
            // 綁定端口
            acceptor.bind(new InetSocketAddress(8081));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

MinaEncoder

你們發現,插入的這個ProtocolCodecFilter中,還有兩個參數:MinaEncoder和MinaDecoder,這兩個類都是帖主本身實現的,請繼續看這兩個類的實現: (代碼來自開源項目《迷你微信》服務器)數組

public class MinaEncoder extends ProtocolEncoderAdapter {
    public final int INT_SIZE = 4
        @Override
    public void encode(IoSession ioSession, Object message, ProtocolEncoderOutput output) throws Exception {
        byteArrayOutputStream = new ByteArrayOutputStream();
        byte[] byteArray;
        if (message.getClass().equals(PacketFromServer.class)) {
            packetWillSend = (PacketFromServer) message;

            
            // 加入數據包
            byteArray = byteArrayOutputStream.write(packetWillSend.getMessageBoty());

            int sizeOfAll = byteArrayOutputStream.size() + INT_SIZE;
            byteArray = byteArrayOutputStream.toByteArray();

            IoBuffer buffer = IoBuffer.allocate(sizeOfAll);
            buffer.put(DataTypeTranslater.intToByte(sizeOfAll)); // header
            buffer.put(byteArray); // body
            buffer.flip();
            output.write(buffer);
        }
    }
    }

首先,是這個MinaEncoder ,這是在使用ioSession.write(myPacketFromServer)從服務器往客戶端發送數據時調用,須要繼承於ProtocolEncoderAdapter 並覆蓋父類的encode方法。服務器

先解釋一下這個類的用處,當你調用IoSession.write(myPacketFromServer) 的時候,傳入了一個myPacketFromServer的對象,這是一個帖主本身編寫的類PacketFromServer的實例化對象,然而,IO流並不能直接傳輸一個Java對象,即使可以傳輸(序列化,也會致使靈活性下降,由於客戶端就必須使用Java了,因此,在網絡層加上這麼一個Filter,攔截住你發送的Java對象,將其轉化爲字節流,才能傳輸,而這個類MinaEncoder 即是作的這件事。微信

使用看看實現,首先,判斷傳進來的是否是PacketFromServer對象,接着,將內容一份一份的取出,轉化成byte數組,放入byteArrayOutputStream中,最後,從byteArrayOutputStream中將以前塞入的所有byte數組所有取出,往IoBuffer中塞入,而後從ProtocolEncoderOutput 寫入通往客戶端的輸出流。其中網絡

  • IoBuffer buffer = IoBuffer.allocate(sizeOfAll); 表示只開sizeOfAll這麼大的輸出流,超出後超出的部分將被直接拋棄。

MinaDecoder

接着,輪到MinaDecoder了** (代碼來自開源項目《迷你微信》服務器)**ide

public class MinaDecoder extends CumulativeProtocolDecoder {
    @Override
    protected boolean doDecode(IoSession ioSession, IoBuffer ioBuffer, ProtocolDecoderOutput output) throws Exception {// 若是沒有接收完Size部分(4字節),直接返回false
        if (ioBuffer.remaining() < 4)
            return false;
        else {
            // 標記開始位置,若是一條消息沒傳輸完成則返回到這個位置
            ioBuffer.mark();
            
            byteArrayOutputStream = new ByteArrayOutputStream();
            
            // 讀取Size
            byte[] bytes = new byte[4];
            ioBuffer.get(bytes); // 讀取4字節的Size
            byteArrayOutputStream.write(bytes);
            int bodyLength = DataTypeTranslater.bytesToInt(bytes, 0) - DataTypeTranslater.INT_SIZE; // 按小字節序轉int

            // 若是body沒有接收完整,直接返回false
            if (ioBuffer.remaining() < bodyLength) {
                ioBuffer.reset(); // IoBuffer position回到原來標記的地方
                return false;
            } else {
                byte[] bodyBytes = new byte[bodyLength];
                ioBuffer.get(bodyBytes);
//              String body = new String(bodyBytes, "UTF-8");
                byteArrayOutputStream.write(bodyBytes);
                
                // 建立對象
                NetworkPacket packetFromClient = new NetworkPacket(ioSession, byteArrayOutputStream.toByteArray());
                
                output.write(packetFromClient); // 解析出一條消息
                return true;
        }
    }
    }

與MinaEncoder 差很少,MinaDecoder 是在接收到數據的時候被調用到,將數據轉化爲想要的對象,並交給邏輯層的一個模塊。注意,因爲帖主的網絡協議是本身定義的size + objectByte 格式,因此首先接收到的事4byte,將其轉化爲int後代表整個包的大小。因此,在客戶端傳遞數據前,要先將整個數據包的大小告訴服務器哦!這是防止粘包和缺包的一種很是有效的方法,這裏,插播一下黏包和缺包的解釋:

  • 黏包:因爲TCP協議在優化傳輸時,可能將多條小的數據包鏈接成一個大的數據包一塊兒發送,以此來減小IO次數,提升效率,但這將致使本應該是兩條消息的數據被服務器一次收到,黏在一塊兒,因此叫作粘包。

  • 缺包: 因爲TCP協議在優化傳輸時,可能將一個大的數據包分割成幾回發送,致使收到的數據包不全,因此,自行編寫size來保證不會出現缺包問題。

歡迎閱讀個人開源項目《迷你微信》服務器《迷你微信》客戶端 [1]: https://github.com/MrNerverDie/MiniWeChat-Server [2]: https://github.com/MrNerverDie/MiniWeChat-Client

相關文章
相關標籤/搜索