MINA學習

工作中遇到了MINA,以前沒接觸過,所以就想搞搞明白這東西幹嘛的,怎麼玩起來的。最近花了幾天時間去學習,這裏做個小結以整理知識加深理解。 
    Apache MINA是一個網絡應用框架框架,用來幫助用戶簡單地開發高性能和高可靠性的網絡應用程序。它提供了一個通過Java NIO在不同的傳輸例如TCP/IP和UDP/IP上抽象的事件驅動的異步API。(至少知道是搞網絡的框架了~~~) 
    直接看個例子吧!!! 
    實現一個簡單的計算器服務,客戶端發送要計算的表達式給服務器,服務器返回計算結果。比如客戶端發送2+2,服務器返回4。 
    (怎麼就直接上代碼了,還糊塗着呢!!!~~~哈哈哈  Code Monkey總是如此的直接~~) 
CalculatorHandler.java 

複製代碼
 1 public class CalculatorHandler extends IoHandlerAdapter {  
 2     private ScriptEngine jsEngine = null;  
 3   
 4     public CalculatorHandler() {  
 5         ScriptEngineManager sem = new ScriptEngineManager();  
 6         jsEngine = sem.getEngineByName("JavaScript");  
 7         if (jsEngine == null) {  
 8             throw new RuntimeException("Can't find JavaScript Engine");  
 9         }  
10     }  
11   
12     @Override  
13     public void messageReceived(IoSession session, Object message)  
14             throws Exception {  
15         String expression = message.toString();  
16         if ("quit".equalsIgnoreCase(expression.trim())) {  
17             session.close(true);  
18             return;  
19         }  
20         try {  
21             Object result = jsEngine.eval(expression);  
22             session.write("=" + result.toString());  
23         } catch (Exception e) {  
24             session.write("Wrong expression,try again.");  
25         }  
26   
27     }  
28 }  
複製代碼

CalculatorServer.java 

複製代碼
public class CalculatorServer {  
       private static final int PORT = 10010;  
  
       public static void main(String[] args) throws IOException {  
            IoAcceptor acceptor = new NioSocketAcceptor();  
            acceptor.getFilterChain().addLast( "logger" , new LoggingFilter());  
            acceptor.getFilterChain().addLast(  
                         "codec" ,  
                         new ProtocolCodecFilter( new TextLineCodecFactory(Charset  
                                    . forName( "UTF-8"))));  
            acceptor.setHandler( new CalculatorHandler());  
            acceptor.bind( new InetSocketAddress( PORT));  
      }  
}  
複製代碼

運行CalculatorServer並使用telnet登錄進行測試 

它怎麼就跑起來了呢,怎麼這麼神奇呢!? 
    據觀察mian方法中實例化了IoAcceptor,向它的“過濾鏈”末尾添加了兩個Filter,然後設置了Handler(對,就是上面的CalculatorHandler),然後綁定到一個地址。 
    看名字兩個Filter分別和日誌,編解碼相關,都是MINA框架自帶了,就先不看了。綁定地址也沒什麼好說的,重點肯定在這個CalculatorHandler裏面了。 
    CalculatorHandler做了什麼?繼承IoHandlerAdapter並重寫了messageReceived方法。messageReceived顧名思義就是接收到消息後執行這個方法。在這個方法內調用jsEngine.eval(expression)對接收到的表達式進行計算並將結果寫回到session中。 

    根據上面的分析,大致可以推斷這樣的結論(推斷~~推斷~~其實就是“你猜啊!”): 
    MINA中靠session去傳遞消息。IoAcceptor是核心,它綁定在一個地址上(有點像監聽器),並有一個Filter鏈和Handler。它是事件驅動的,發生某些事件時觸發指定的方法。大致的執行過程是監聽地址上發生某些事件,產生session並進過Filter鏈的處理最終進入Handler執行業務邏輯的處理。。。 


    程序也跑起來了,怎麼跑的也去“猜”了,是時候了一探究竟了。。。一探究竟?Code Monkey竟然會用成語!!!啊哈哈哈哈 
http://mina.apache.org/ 官網就在這裏,你看,或者不看,它還在那裏。源碼就在上面,你下,或者不下,隨你吧。。。 
    Apache MINA is a network application framework which helps users develop high performance and high scalability network applications easily. It provides an abstract ·event-driven · asynchronous API over various transports such as TCP/IP and UDP/IP via Java NIO. 

    Apache MINA is often called: 

    NIO framework · library, 
    client · server framework · library, or 
    a networking · socket library. 

    大致看一樣這框架在系統中的位置吧 

    MINA處於應用程序和底層網絡之間,幫助用戶更簡單的開發網絡應用。 
    MINA框架的分層及組件(搞過WEB開發的一看肯定就明白了。。。) 

    基礎MINA應用被分爲三層: 
    I/O Service:執行真正的I/O 
    I/O Filter Chain:將字節過濾/轉換成設計的數據結構(對象) 
    I/O Handler:業務邏輯 
    創建MINA應用必須: 
    創建I/O Service:選擇已存在的或自己去創建 
    創建Filter Chain:可以使用MINA提供的Filter也可以自己實現 
    創建I/O Handler:寫自己的邏輯 

既然主要的組件有這麼幾個,一個個看吧。 

  IoService的職責很清晰了就不說了,方法說說明看源碼吧。AbstractIoService是IoService的一個抽象實現類,增加了一些默認的實現(這個類裏實現而來設置Handler)。我們自己寫代碼的時候也有很多這樣的設計,接口+一個抽象的實現類,帶來的好處就不說了。 
    IoService有兩個重要的子接口:IoAcceptor和IoConnector。MINA應用的服務端必須實現IoAcceptor,客戶端必須實現IoConnector(當然,MINA提供了一些實現類)。 
    IoAcceptor類圖 

 IoAcceptor幾個實現類: 
    NioSocketAcceptor: the non-blocking Socket transport IoAcceptor 
    NioDatagramAcceptor: the non-blocking UDP transport IoAcceptor 
    AprSocketAcceptor: the blocking Socket transport IoAcceptor, based on ARP 
    VmPipeSocketAcceptor: the in-VM IoAcceptor 
    IoConnector類圖 

  IoConnector幾個實現類: 
    NioSocketConnector: the non-blocking Socket transport IoConnector 
    NioDatagramConnector: the non-blocking UDP transport IoConnector 
    AprSocketConnector: the blocking Socket transport IoConnector, based on ARP 
    ProxyConnector: a IoConnector providing proxy support 
    SerialConnector: a IoConnector for serial transport 
    VmPipeConnector:the in-VM IoConnector 
    根據自己的需求選擇合適的IoAcceptor和IoConnector。 

    IoSession 
    Session是MINA的核心:每次客戶端連接到服務器端都會創建一個新的session,並保持在內存中,直到連接斷開。 
    Session的狀態: 
    Connected: the session has been created and is avaiable 
    Idle: the session hasn't processed any request for at least a period of time(the period is configurable) 
        Idle for read: no read has actually been made for a period of time 
        Idle for write: no write has actually been made for a period of time 
        Idle for both: no read nor write for a period of time 
    Closing: the session is being closing (the remaining messages are being flushed,cleaning up is terminated) 
    Closed: the session is now closed, nothing else can be done to revive it 
    Session狀態圖 

 Filters 
    Filters處在IoService和IoHandler之間,過濾所有的I/O事件和請求。 
    一些“開箱即用”的過濾器: 
    LoggingFilter logs all events and requests. 
    LoggingFilter日誌記錄所有事件和請求。 
    PtotocolCodecFilter converts an incoming ByteBuffer into message POJO and vice versa. 
    PtotocolCodecFilter將傳入的消息轉換成POJO ByteBuffer,反之亦然。 
    CompressionFilter compresses all data. 
    CompressionFilter壓縮所有數據。 
    SSLFilter adds SSL - TLS - StartTLS support. 
    SSLFilter添加SSL - TLS - StartTLS支持。 
    ... 
    
    Handler 
    IoHandler接口定義瞭如下7個方法: 
    sessionCreated 
    當一個新連接被建立,從一個I/O處理器調用該方法。應爲該方法在同一個線程中被調用,所以該方法應儘量處理消耗時間較小的任務。 
    sessionOpened 
    當連接被打開後調用。該方法在sessionCreated之後調用。最大的不同是從另一個線程被調用。 
    sessionClosed 
    當連接被關閉後調用。 
    sessionIdle 
    session被閒置的時候調用。 
    exceptionCaught 
    當任何異常被拋出時調用。當異常是IOException時,MINA將自動關閉連接connection。 
    messageReceived 
    當接收到消息的時候被調用 
    messageSent 
    當發送消息時被調用 

    瞭解以上內容後去理解開頭的例子就很容易了。 
    當我們使用MINA開始定義自己的協議進行網絡通信的時候就需要自己實現編解碼器了,這時有必要先了解一下IoBuffer這個類。 
    IoBuffer是MINA框架使用的byte buffer。 
    IoBuffer是ByteBuffer的替代品。爲什麼不適用ByteBuffer?直接原因有一下兩點: 
    1.ByteBuffer不提供像get/putString,get/putAsciiInt這樣方便的getters和putters方法。 
    2.ByteBuffer的固定容量使記錄變長數據非常的不方便。 
    注意:IoBuffer在MINA3中可能被放棄,因爲僅僅爲了擴充容量而拓展現有的buffer。MINA3可能使用InputStream去替代byte buffer或其他方案。 
    
    IoBuffer操作 
    分配一個新的Buffer 
    public static IoBuffer allocate(int capacity) 
    public static IoBuffer allocate(int capacity, boolean direct) 
    capacity:Buffer的容量 
    direct:是否直接緩衝。true to get direct buffer,false to get heap buffer 
    也可以通過以下方式獲取Buffer 
    // 默認使用heap buffer 
    IoBuffer.setUseDirectBuffer (true ); 
    IoBuffer buffer = IoBuffer.allocate(1024); 

    創建自動擴展的Buffer 
    public abstract IoBuffer setAutoExpand( boolean autoExpand); 
    創建自動收縮的Buffer 
    public abstract IoBuffer setAutoShrink( boolean autoShrink); 

------------------------------------------------------------------------------華麗麗的分割線------------------------------------------------------------------------------



    是騾子是馬拉出來溜溜,是時候自己動手寫點東西(搞兩個WEB項目吧,一個Server一個Client,Client向Server發送消息並獲取返回內容顯示在頁面上)。 
    回顧一下,建立MINA應用的步驟吧。實現IoService,實現自己的編解碼器,實現IoHandler,加入自己的業務邏輯...... 
    從Client入手吧。接收頁面內容的Action什麼的就不說了,直接從Service(這個Service指Action、Service、DAO中的Service)說起吧。 

複製代碼
@Service("remoteService")  
public class RemoteServiceImpl implements RemoteService {  
    private static final Log log = LogFactory.getLog(RemoteServiceImpl.class);  
    @Autowired  
    private IoConnector ioConnector;  
  
    @Override  
    public ServerResponse executeRequest(ServerRequest request) {  
        IoSession session = null;  
        try {  
            ioConnector.getSessionConfig().setUseReadOperation(true);  
            session = ioConnector.connect().awaitUninterruptibly().getSession();  
            session.write(request);  
            ReadFuture rf = session.read().awaitUninterruptibly();  
            Object message = rf.getMessage();  
            if (message != null) {  
                return (ServerResponse) message;  
            }  
        } catch (Exception e) {  
            // TODO: handle exception  
            log.error("RemoteService execute request error.", e);  
            e.printStackTrace();  
        } finally {  
            if (session != null) {  
                session.close(true);  
            }  
        }  
        return null;  
    }  
  
}  
複製代碼

    沒什麼內容自動注入這些Spring相關的不看了,下面會給出配置。 
    上面的方法使用的ServerRequest、ServerResponse是兩個簡單的POJO。ServerRequest有一個message屬性代表要發送的消息,ServerResponse有一個responseStr屬性代表返回的字符串。 
    一些<bean>的配置

複製代碼
<!-- 屬性編輯器 -->  
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">  
    <property name="customEditors">  
        <map>  
            <entry key="java.net.SocketAddress">  
                <bean class="org.apache.mina.integration.beans.InetSocketAddressEditor" />  
            </entry>  
        </map>  
    </property>  
</bean>  
  
<bean id="executorFilter" class="org.apache.mina.filter.executor.ExecutorFilter"  
    destroy-method="destroy" />  
  
<bean id="codecFilter" class="org.apache.mina.filter.codec.ProtocolCodecFilter">  
    <constructor-arg>  
        <bean class="包名.ServerProtocolCodecFactory">  
            <constructor-arg name="encoder">  
                <bean class="包名.ServerRequestEncoder" />  
            </constructor-arg>  
            <constructor-arg name="decoder">  
                <bean class="包名.ServerResponseDecoder" />  
            </constructor-arg>  
        </bean>  
    </constructor-arg>  
</bean>  
  
<bean id="filterChainBuilder"  
    class="org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder">  
    <property name="filters">  
        <map>  
            <entry key="codecFilter" value-ref="codecFilter" />  
        </map>  
    </property>  
</bean>  
  
<bean id="clientHandler" class="包名.ClientHandler" />  
  
<bean id="ioConnector" class="org.apache.mina.transport.socket.nio.NioSocketConnector"  
    destroy-method="destroy">  
    <!-- “屬性編輯器” 一些屬性在XML 中寫爲String 類型,但實際JAVA 類型中要 求注入的是一個其他的對象類型,你需要對此做出轉換。譬如:下面的defaultRemoteAddress   
        我們傳入的是一個字符串,但實際上NioSocketConnector 中需要的是一個InetSocketAddress,這裏就需要一個編輯器將XML   
        中注入的字符串構造爲 InetSocketAddress 對象。在Mina 自帶的包org.apache.mina.integration.beans   
        中提供 了很多的屬性編輯器,如果你發現某個屬性的編輯器沒有提供,可以自行編寫 InetSocketAddress -->  
    <property name="defaultRemoteAddress" value="${client.host}:${client.port}" />  
    <property name="filterChainBuilder" ref="filterChainBuilder" />  
    <property name="handler" ref="clientHandler" />  
</bean>  
複製代碼

    配置信息中“包名.XXX”(“包名”中包含這公司信息就隱去了)的類都是自己實現的,一共兩塊內容,Handler和編解碼器CodecFilter。 
    CodecFilter負責完成ServerRequest、ServerResponse和byte buffer之間的轉化。 
    代碼就放這裏吧(ServerProtocolCodecFactory ServerRequestEncoder ServerResponseDecoder)。 

複製代碼
public class ServerProtocolCodecFactory implements ProtocolCodecFactory {  
  
    private static final long serialVersionUID = 3338741166925608943L;  
    private ProtocolEncoder encoder;  
    private ProtocolDecoder decoder;  
  
    public ServerProtocolCodecFactory(ProtocolEncoder encoder,  
            ProtocolDecoder decoder) {  
        super();  
        this.encoder = encoder;  
        this.decoder = decoder;  
    }  
  
    @Override  
    public ProtocolDecoder getDecoder(IoSession session) throws Exception {  
        return decoder;  
    }  
  
    @Override  
    public ProtocolEncoder getEncoder(IoSession session) throws Exception {  
        return encoder;  
    }  
}  
  
  
public class ServerRequestEncoder implements ProtocolEncoder {  
    private final static Log log = LogFactory  
            .getLog(ServerRequestEncoder.class);  
    private final static Charset charset = Charset.forName("UTF-8");  
  
    @Override  
    public void encode(IoSession session, Object message,  
            ProtocolEncoderOutput out) throws Exception {  
        // 將所需傳輸數據編碼轉化放入IoBuffer,將得到的IoBuffer寫入ProtocolEncoderOutput中  
        log.info("####################字符編碼####################");  
        if (!(message instanceof ServerRequest)) {  
            throw new IllegalArgumentException("get unknow type:"  
                    + message.getClass().getName()  
                    + ",should be [ServerRequest].");  
        }  
        ServerRequest request = (ServerRequest) message;  
        IoBuffer buff = IoBuffer.allocate(100).setAutoExpand(true);  
        buff.putString(request.getMessage(), charset.newEncoder());  
        buff.putString(LineDelimiter.DEFAULT.getValue(), charset.newEncoder());  
        buff.flip();  
        out.write(buff);  
    }  
  
    @Override  
    public void dispose(IoSession session) throws Exception {  
        log.info("####################Dispose####################");  
    }  
  
}  
  
  
public class ServerResponseDecoder implements ProtocolDecoder {  
    private final static Log log = LogFactory  
            .getLog(ServerResponseDecoder.class);  
    private final static Charset charset = Charset.forName("UTF-8");  
    private IoBuffer buff = IoBuffer.allocate(100).setAutoExpand(true);  
  
    @Override  
    public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out)  
            throws Exception {  
        log.info("###############decode############");  
        while (in.hasRemaining()) {  
            byte b = in.get();  
            if (b == '\n') {  
                buff.flip();  
                byte[] bytes = new byte[buff.limit()];  
                buff.get(bytes);  
                String message = new String(bytes, charset);  
                ServerResponse response = new ServerResponse();  
                response.setResponseStr(message);  
                out.write(response);  
            } else {  
                buff.put(b);  
            }  
        }  
    }  
  
    @Override  
    public void finishDecode(IoSession session, ProtocolDecoderOutput out)  
            throws Exception {  
        log.info("################完成解碼###################");  
    }  
  
    @Override  
    public void dispose(IoSession session) throws Exception {  
        log.info("#################Dispose##################");  
    }  
  
} 
複製代碼

    接着看Handler(其實一點內容都沒有,繼承了IoHandlerAdapter但都沒裏面的方法還都是調父類的,只是留着先,後期可以加入自己的邏輯) 

複製代碼
public class ClientHandler extends IoHandlerAdapter {  
    private static final Log log = LogFactory.getLog(ClientHandler.class);  
  
    @Override  
    public void exceptionCaught(IoSession session, Throwable cause)  
            throws Exception {  
        super.exceptionCaught(session, cause);  
        log.error("Client handler caught an error", cause);  
    }  
  
    @Override  
    public void messageReceived(IoSession session, Object message)  
            throws Exception {  
        super.messageReceived(session, message);  
    }  
  
    @Override  
    public void messageSent(IoSession session, Object message) throws Exception {  
        log.debug("ClientHandler.messageSent"+message);  
        super.messageSent(session, message);  
    }  
} 
複製代碼

    這樣客戶端就完成了,當然,你要加發送消息的頁面,顯示返回結果的頁面。 

    服務端和客戶端沒什麼差別,無非Client用的IoConnector改爲IoAcceptor......要注意的是,Server的編解碼器。解碼器Decoder能將接收到的消息轉成正確的對象,編碼器Encoder編碼結果能被客戶端的解碼器解碼(就是這樣一個對應關係ServerDecoder-ClientEncoder,ServerEncoder-ClientDecoder,編碼成什麼樣就可以自己約定了)。 

(這麼爛的圖都敢上~~~) 

------------------------------------------------------------------------------華麗麗的分割線又來了------------------------------------------------------------------------


    總不能是發這麼個字符串過去回來個字符串吧,實際項目中沒這麼簡單的需求吧???比如一個關鍵字和其他參數過去回來一堆數據一個List什麼的還差不多。 
    要實現上面的需求,關鍵的就在於協議的定義了。發送過去的關鍵字和參數是怎麼樣的返回回來的數據結構是怎麼要的,怎麼進行編解碼等等了。搞起搞起~~~~~~ 

    這塊搞得時間比較長,但還是覺得寫得不大好。不過至少也是週末時間努力的結果,大家看看吧。不多說,上代碼。 
    Client端的編解碼及協議。 
    Client編碼在ServerRequest自身中實現,ServerRequestEncoder僅僅調用ServerRequest的方法獲取結果並寫入到ProtocolEncoderOutput中。 
    ServerProtocol中主要定義了一些協議中使用到的常量。 

複製代碼
    public static final Charset charset = Charset.forName("UTF-8");  
    public static final int protocolHeadLength = 4;  
    public static final String equalString = "=";  
    public static final char equalChar = '=';  
    public static final String separateString = "\5";  
    public static final char separateChar = '\5';  
    public static final String endString = "\7";  
    public static final char endChar = '\7';  
複製代碼

ServerRequest.java 

複製代碼
public class ServerRequest extends ServerProtocol {  
  
    private static final long serialVersionUID = 7447009933974403515L;  
    private static final Log log = LogFactory.getLog(ServerRequest.class);  
  
    private IoBuffer parameters;  
  
    public ServerRequest() {  
        super();  
        // direct設置爲false獲取heap buffer. heap buffer效率更高  
        this.parameters = IoBuffer.allocate(256, false).setAutoExpand(true);  
    }  
  
    public ServerRequest(Map<String, Object> paramaters) {  
        this();  
        this.setParameters(paramaters);  
    }  
  
    public void setParameters(Map<String, Object> parameters) {  
        if (parameters == null) {  
            return;  
        }  
        for (Entry<String, Object> entry : parameters.entrySet()) {  
            addParameter(entry.getKey(), entry.getValue());  
        }  
    }  
  
    public void addParameter(String key, Object value) {  
        if (key == null) {  
            return;  
        }  
        CharsetEncoder encoder = charset.newEncoder();  
        try {  
            this.parameters.putString(key, encoder);  
            this.parameters.putString(equalString, encoder);  
            if (value == null) {  
                this.parameters.putString("", encoder);  
            } else {  
                this.parameters.putString(value.toString(), encoder);  
            }  
            this.parameters.putString(separateString, encoder);  
        } catch (CharacterCodingException e) {  
            log.error("ServerRequest CharacterCodingException...", e);  
        }  
    }  
  
    public IoBuffer getProtocolBuffer() {  
        int dataLength = this.parameters.position();  
        // 構造傳輸的IoBuffer  
        IoBuffer transferBuffer = IoBuffer.allocate(protocolHeadLength  
                + dataLength, true);  
        // 向協議頭中寫入數據長度  
        transferBuffer.putInt(dataLength);  
        // 寫入數據  
        this.parameters.flip();  
        transferBuffer.put(this.parameters);  
        transferBuffer.flip();  
        return transferBuffer;  
    }  
}  
複製代碼

     解碼在ServerResponse中實現,ServerResponseDecoder僅調用ServerResponse的方法獲取解碼結果寫入ProtocolDecoderOutput中。 
ServerDecoder.java 

複製代碼
public class ServerResponse extends ServerProtocol {  
  
    private static final long serialVersionUID = -2728053247051653694L;  
    private static final String FALSE = "F";  
    private static final String nextMark = "hasNext";  
    private static final String errorNoFlag = "errorNo";  
    private static final String errorInfoFlag = "errorInfo";  
    private String errorNo;  
    private String errorInfo;  
    private ByteBuffer headBuffer = ByteBuffer.allocate(protocolHeadLength);  
    private List<ServerResponseEntry> list = new ArrayList<ServerResponseEntry>();  
  
    public void parseBuffer(IoBuffer ioBuffer) {  
        if (ioBuffer == null) {  
            return;  
        }  
        for (int i = 0; i < protocolHeadLength && ioBuffer.hasRemaining(); i++) {  
            headBuffer.put(ioBuffer.get());  
        }  
        headBuffer.flip();  
        int dataLength = headBuffer.getInt();  
        // 寫data到buffer  
        ByteBuffer dataBuffer = ByteBuffer.allocate(dataLength);  
        while (ioBuffer.hasRemaining() && dataBuffer.position() < dataLength) {  
            dataBuffer.put(ioBuffer.get());  
        }  
        dataBuffer.flip();  
        CharBuffer dataCharBuffer = charset.decode(dataBuffer);  
        boolean writeKey = true;  
        StringBuilder keyBuilder = new StringBuilder();  
        StringBuilder valueBuilder = new StringBuilder();  
        ServerResponseEntry entry = new ServerResponseEntry();  
        // 這段解析寫的太爛了~~~~~~~~~  
        iteratingDataCharBuffer: while (dataCharBuffer.hasRemaining()) {  
            char character = dataCharBuffer.get();  
            switch (character) {  
            // 遇到'='  
            case equalChar:  
                writeKey = false;  
                break;  
            // 遇到'\5' 分隔符,將Key和Value寫入Map  
            case separateChar:  
                writeKey = true;  
                String key = keyBuilder.toString();  
                String value = valueBuilder.toString();  
                if (key.equals(nextMark)) {  
                    // 遇到“下一條”標識,將記錄加入到list中  
                    ServerResponseEntry item = new ServerResponseEntry();  
                    item.getRow().putAll(entry.getRow());  
                    list.add(item);  
                    entry.getRow().clear();  
                    if (value.equals(FALSE)) {  
                        // 最後一條記錄,結束parseBuffer  
                        dataCharBuffer.mark();  
                        break iteratingDataCharBuffer;  
                    }  
                } else {  
                    System.out.println(key + "=" + value);  
                    entry.addKeyValue(key, value);  
                }  
                keyBuilder = new StringBuilder();  
                valueBuilder = new StringBuilder();  
                break;  
            default:  
                // 拼接KEY和VALUE  
                if (writeKey) {  
                    keyBuilder.append(character);  
                } else {  
                    valueBuilder.append(character);  
                }  
                break;  
            }  
        }  
        dataCharBuffer.reset();  
        keyBuilder = new StringBuilder();  
        valueBuilder = new StringBuilder();  
        // 構造錯誤代碼和錯誤信息  
        Map<String, String> error = new HashMap<String, String>();  
        while (dataCharBuffer.hasRemaining()) {  
            char character = dataCharBuffer.get();  
            switch (character) {  
            // 遇到'='  
            case equalChar:  
                writeKey = false;  
                break;  
            case separateChar:  
                writeKey = true;  
                error.put(keyBuilder.toString(), valueBuilder.toString());  
                keyBuilder = new StringBuilder();  
                valueBuilder = new StringBuilder();  
                break;  
            default:  
                // 拼接KEY和VALUE  
                if (writeKey) {  
                    keyBuilder.append(character);  
                } else {  
                    valueBuilder.append(character);  
                }  
                break;  
            }  
        }  
        errorNo = error.get(errorNoFlag);  
        errorInfo = error.get(errorInfoFlag);  
    }  
  
    public List<ServerResponseEntry> getList() {  
        return list;  
    }  
  
    public String getErrorNo() {  
        return errorNo;  
    }  
  
    public String getErrorInfo() {  
        return errorInfo;  
    }  
}  
複製代碼

     注意哦,解碼是針對服務端的編碼結果的!!! 


    Server相關的主要三個類分別是ProtocolDTO,ProtocolReadDTO,ProtocolWriteDTO,分別是定義協議常量公共方法,解析接收到的內容,構造返回結果。 
    文字能力不寫啊,還是直接給代碼吧! 
ProtocolDTO.java 

複製代碼
public abstract class ProtocolDTO implements Serializable {  
  
    private static final long serialVersionUID = -6932714193726622067L;  
    public static final String keyWorld = "KEY";  
    public static final int headDataLength = 4;  
    public static final Charset charset = Charset.forName("UTF-8");  
    public static final char equalChar = '=';  
    public static final String equalString = "=";  
    public static final char separateChar = '\5';  
    public static final String separateString = "\5";  
    public static final char endChar = '\7';  
    public static final String endString = "\7";  
  
    @Override  
    public String toString() {  
        return ToStringBuilder.reflectionToString(this);  
    }  
  
    @Override  
    public boolean equals(Object obj) {  
        return EqualsBuilder.reflectionEquals(this, obj);  
    }  
  
    @Override  
    public int hashCode() {  
        return HashCodeBuilder.reflectionHashCode(this);  
    }  
複製代碼

ProtocolReadDTO.java 

複製代碼
public class ProtocolReadDTO extends ProtocolDTO {  
  
    private static final long serialVersionUID = 7447009933974403515L;  
    private ByteBuffer headBuffer = ByteBuffer.allocate(headDataLength);  
    private String key = null;  
    private Map<String, Object> data;  
  
    public void parseBuffer(IoBuffer ioBuffer) {  
        if (ioBuffer == null) {  
            return;  
        }  
        StringBuilder keyBuilder = new StringBuilder();  
        StringBuilder valueBuilder = new StringBuilder();  
        data = new HashMap<String, Object>();  
        // IoBuffer轉ByteBuffer  
        for (int i = 0; i < headDataLength && ioBuffer.hasRemaining(); i++) {  
            headBuffer.put(ioBuffer.get());  
        }  
        headBuffer.flip();  
        int dataLength = headBuffer.getInt();  
        // 構造數據緩衝  
        ByteBuffer dataBuffer = ByteBuffer.allocate(dataLength);  
        while (ioBuffer.hasRemaining() && dataBuffer.position() < dataLength) {  
            dataBuffer.put(ioBuffer.get());  
        }  
        dataBuffer.flip();  
        // dataBuffer從ByteBuffer轉成CharBuffer  
        CharBuffer dataCharBuffer = charset.decode(dataBuffer);  
        boolean writeKey = true;  
        while (dataCharBuffer.hasRemaining()) {  
            char character = dataCharBuffer.get();  
            switch (character) {  
            case equalChar:  
                writeKey = false;  
                break;  
            case separateChar:  
                // 遇到分隔符了,向map中寫入key和value  
                if (keyBuilder.toString().equals(keyWorld)) {  
                    this.key = valueBuilder.toString();  
                } else {  
                    data.put(keyBuilder.toString(), valueBuilder.toString());  
                }  
                writeKey = true;  
                keyBuilder = new StringBuilder();  
                valueBuilder = new StringBuilder();  
                break;  
            case endChar:  
  
                break;  
            // 默認操作則將char加入到key和value字符串中  
            default:  
                if (writeKey) {  
                    keyBuilder.append(character);  
                } else {  
                    valueBuilder.append(character);  
                }  
                break;  
            }  
        }  
    }  
  
    public Map<String, Object> getData() {  
        return data;  
    }  
  
    public String getKey() {  
        return key;  
    }  
}  
複製代碼

ProtocolWriteDTO.java 

複製代碼
public class ProtocolWriteDTO extends ProtocolDTO {  
  
    private static final long serialVersionUID = -2728053247051653694L;  
  
    private static final String TRUE = "T";  
    private static final String FALSE = "F";  
    private static final String errorNoFlag = "errorNo";  
    private static final String errorInfoFlag = "errorInfo";  
    private String errorNo = "0";  
    private String errorInfo = "";  
    /** 標識,還有下一條記錄 */  
    private static final String hasNext = "hasNext" + equalChar + TRUE;  
    /** 標識,沒有下一條記錄 */  
    private static final String noNext = "hasNext" + equalChar + FALSE;  
    /** 緩存ProtocolResponse類的屬性和方法 */  
    private Map<Class<?>, ResponseDescription[]> responseProperties = new HashMap<Class<?>, ResponseDescription[]>();  
    private IoBuffer headBuffer;  
    private IoBuffer bodyBuffer;  
    private static final Object[] emptyObjects = new Object[] {};  
  
    public ProtocolWriteDTO(String errorNo, String errorInfo,  
            List<? extends ProtocolResponse> list) {  
        super();  
        this.errorNo = errorNo;  
        this.errorInfo = errorInfo;  
        makeIoBuffer(list);  
    }  
  
    /** 
     * 創建返回報文 
     *  
     * @param list 
     * @throws InvocationTargetException 
     * @throws IllegalAccessException 
     * @throws IllegalArgumentException 
     */  
    private void makeIoBuffer(List<? extends ProtocolResponse> list) {  
        makeBody(list);  
        makeHead();  
    }  
  
    private final void makeBody(List<? extends ProtocolResponse> list) {  
        if (list == null || list.size() == 0) {  
            return;  
        }  
        bodyBuffer = IoBuffer.allocate(256, false).setAutoExpand(true);  
        CharsetEncoder encoder = charset.newEncoder();  
        int last = list.size() - 1;  
        for (int i = 0; i <= last; i++) {  
            ProtocolResponse response = list.get(i);  
            // 讀取response中所有的屬性值  
            ResponseDescription[] readMethods = getResponseProperty(response  
                    .getClass());  
            try {  
                for (ResponseDescription des : readMethods) {  
                    Object objectValue = des.method.invoke(response,  
                            emptyObjects);  
                    String value = objectValue == null ? "" : objectValue  
                            .toString();  
                    bodyBuffer.putString(des.property, encoder)  
                            .putString(equalString, encoder)  
                            .putString(value, encoder)  
                            .putString(separateString, encoder);  
                }  
                // buffer中寫入是否有下一條記錄的標識  
                if (i == last) {  
                    // 最後一條  
                    bodyBuffer.putString(noNext, encoder).putString(  
                            separateString, encoder);  
                    // 加入錯誤代碼和錯誤信息的內容  
                    bodyBuffer.putString(errorNoFlag, encoder)  
                            .putString(equalString, encoder)  
                            .putString(errorNo, encoder)  
                            .putString(separateString, encoder);  
                    bodyBuffer.putString(errorInfoFlag, encoder)  
                            .putString(equalString, encoder)  
                            .putString(errorInfo, encoder)  
                            .putString(separateString, encoder);  
                } else {  
                    bodyBuffer.putString(hasNext, encoder);  
                }  
                bodyBuffer.putString(separateString, encoder);  
            } catch (Exception e) {  
                // TODO: handle exception  
                e.printStackTrace();  
            }  
        }  
    }  
  
    private final void makeHead() {  
        headBuffer = IoBuffer.allocate(headDataLength);  
        headBuffer.putInt(this.bodyBuffer.position());  
    }  
  
    /** 
     * 獲取輸出的IoBuffer 
     *  
     * @return 
     */  
    public IoBuffer getResultBuffer() {  
        bodyBuffer.flip();  
        headBuffer.flip();  
        IoBuffer buffer = IoBuffer.allocate(  
                headBuffer.limit() + bodyBuffer.limit(), false);  
        buffer.put(headBuffer).put(bodyBuffer);  
        buffer.flip();  
        return buffer;  
    }  
  
    /** 
     * 記錄屬性和對應的方法 
     *  
     * @author linpl 
     * @version $Id: ProtocolWriteDTO.java,v 0.1 2012-11-23 下午02:26:38 linpl Exp 
     *          $ 
     */  
    private static final class ResponseDescription {  
        private String property;  
        private Method method;  
  
        public ResponseDescription(String property, Method method) {  
            super();  
            this.property = property;  
            this.method = method;  
        }  
    }  
  
    private ResponseDescription[] getResponseProperty(Class<?> clazz) {  
        ResponseDescription[] result = responseProperties.get(clazz);  
        if (result != null) {  
            return result;  
        }  
        // 向responseProperties加入clazz  
        PropertyDescriptor[] propertyDes = BeanUtils  
                .getPropertyDescriptors(clazz);  
        List<ResponseDescription> methods = new ArrayList<ResponseDescription>();  
        for (PropertyDescriptor pd : propertyDes) {  
            Method readMethod = pd.getReadMethod();  
            if (readMethod != null && !pd.getName().equals("class")) {  
                methods.add(new ResponseDescription(pd.getName(), readMethod));  
            }  
        }  
        result = methods.toArray(new ResponseDescription[methods.size()]);  
        responseProperties.put(clazz, result);  
        return result;  
    }  
  
    public void setErrorNo(String errorNo) {  
        this.errorNo = errorNo;  
    }  
  
    public void setErrorInfo(String errorInfo) {  
        this.errorInfo = errorInfo;  
    }  
}  
複製代碼

    這裏還需要給出ProtocolResponse的說明。ProtocolResponse僅僅是一個標記藉口,實現該接口的類都是POJO,如上面用的SimpleProtocolResponse。 
    SimpleProtocolResponse.java 

複製代碼
public class SimpleProtocolResponse implements ProtocolResponse {  
  
    private static final long serialVersionUID = 5507077327194923319L;  
  
    private String workNO;  
    private String name;  
    private String sex;  
    private String age;  
    private String telephone;  
    private String address;  
  
    public String getWorkNO() {  
        return workNO;  
    }  
  
    public void setWorkNO(String workNO) {  
        this.workNO = workNO;  
    }  
  
    public String getName() {  
        return name;  
    }  
  
    public void setName(String name) {  
        this.name = name;  
    }  
  
    public String getSex() {  
        return sex;  
    }  
  
    public void setSex(String sex) {  
        this.sex = sex;  
    }  
  
    public String getAge() {  
        return age;  
    }  
  
    public void setAge(String age) {  
        this.age = age;  
    }  
  
    public String getTelephone() {  
        return telephone;  
    }  
  
    public void setTelephone(String telephone) {  
        this.telephone = telephone;  
    }  
  
    public String getAddress() {  
        return address;  
    }  
  
    public void setAddress(String address) {  
        this.address = address;  
    }  
  
}  
複製代碼

 

 

 

    如果認真的看上面的內容,到這裏估計都明白的,不用怎麼說明了,看看結果吧。 
    注意:這裏只是演示通訊協議實現,所以返回結果什麼的都是寫死在代碼中的。像這樣: 

複製代碼
@Override  
    public void messageReceived(IoSession session, Object message)  
            throws Exception {  
        log.debug("Receive message:" + message);  
        // 獲取接收到的數據  
        ProtocolReadDTO readDTO = (ProtocolReadDTO) message;  
        // 根據readDTO中不同的key可以執行不同的業務代碼  
        if (readDTO.getKey() == null) {  
            // 沒有key,返回錯誤信息  
            ProtocolWriteDTO writeDTO = new ProtocolWriteDTO("-1", "請設置key值",  
                    null);  
            session.write(writeDTO);  
        }  
        // 如readDTO.getKey()爲111,查詢“基本信息”SimpleProtocolResponse  
        // 調用業務代碼...這邊演示就直接構造SimpleProtocolResponse了...  
        List<SimpleProtocolResponse> resultList = new ArrayList<SimpleProtocolResponse>();  
        SimpleProtocolResponse response = new SimpleProtocolResponse();  
        response.setWorkNO("1");  
        response.setName("張三");  
        response.setSex("男");  
        response.setAge("43");  
        response.setTelephone("13566488756");  
        response.setAddress("杭州市上城區......");  
        resultList.add(response);  
        SimpleProtocolResponse response1 = new SimpleProtocolResponse();  
        response1.setWorkNO("2");  
        response1.setName("李四");  
        response1.setSex("男");  
        response1.setAge("28");  
        response1.setTelephone("18864572231");  
        response1.setAddress("杭州市西湖區......");  
        resultList.add(response1);  
        ProtocolWriteDTO writeDTO = new ProtocolWriteDTO("0", "查詢成功", resultList);  
        session.write(writeDTO);  
        // 如readDTO.getKey()爲112,查詢“基本信息”DetailProtocolResponse(未給出示例)  
        // ......  
    }  
複製代碼

 看一下頁面的輸出代碼和結果吧(這塊代碼未必嚴謹,僅僅是顯示一下返回內容,不是現在討論的內容)。 

複製代碼
public class MockClientAction {  
    @Autowired  
    private RemoteService remoteService;  
  
    @RequestMapping(value = "/hello", method = RequestMethod.POST)  
    public String sentHello(@RequestParam("key") String key,  
            @RequestParam("age") String age, @RequestParam("sex") String sex,  
            ModelMap model) {  
        ServerRequest request = new ServerRequest();  
        request.addParameter("KEY", key);  
        request.addParameter("age", age);  
        request.addParameter("sex", sex);  
        ServerResponse response = remoteService.executeRequest(request);  
        if (response != null) {  
            if (response.getErrorNo() != null) {  
                System.out.println("[errorNo" + "=" + response.getErrorNo()  
                        + ";errorInfo=" + response.getErrorInfo() + "]");  
                model.put("errorNo",response.getErrorNo());  
                model.put("errorInfo",response.getErrorInfo());  
            }  
            List<ServerResponseEntry> list = response.getList();  
            for (ServerResponseEntry entry : list) {  
                System.out.print("[");  
                for (Entry<String, String> e : entry.getRow().entrySet()) {  
                    System.out.print(e.getKey() + "=" + e.getValue() + ";");  
                }  
                System.out.println("]");  
            }  
            model.addAttribute("result", response);  
        }  
        return "hello";  
    }  
}  
複製代碼

顯示結果的vm內容 

複製代碼
接收到服務端返回數據(Receive Data):<br/>  
$errorNo<br/>  
$errorInfo<br/>  
<table>  
#foreach($entry in $result.list)  
    #set($row = $entry.row)  
    <tr>  
        <td>$row.workNO</td>  
        <td>$row.name</td>  
        <td>$row.sex</td>  
        <td>$row.age</td>  
        <td>$row.telephone</td>  
        <td>$row.address</td>  
    </tr>  
#end  
</table>  
複製代碼

     好長,好多代碼,終於結束了。 
    (你竟然看到了這裏,肯定是直接拉到下面的吧!!!哈哈哈哈哈哈哈)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章