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