?本站文章均為?李華明Himi?原創,轉載務必在明顯處注明:
轉載自【黑米GameDev街區】?原文鏈接:?http://www.himigame.com/apache-mina/831.html
? 點擊訂閱 ??本博客最新動態!及時將最新博文通知您!
?
在上一篇博文中已經簡單介紹過“過濾器”的概念,那么在Mina 中的協議編解碼器通過過濾器 ProtocolCodecFilter 構造,這個過濾器的構造方法需 要一個 ProtocolCodecFactory,這從前面注冊 TextLineCodecFactory 的代碼就可以看出來。 ProtocolCodecFactory 中有如下兩個方法:
public interface ProtocolCodecFactory {
ProtocolEncoder getEncoder(IoSession session) throws Exception;
ProtocolDecoder getDecoder(IoSession session) throws Exception;
}
因此,構建一個 ProtocolCodecFactory 需要 ProtocolEncoder、ProtocolDecoder 兩個實例。你可能要問 JAVA 對象和二進制數據之間如何轉換呢?這個要依據具體的通信協議,也就是 Server 端要和 Client 端約定網絡傳輸的數據是什么樣的格式,譬如:第一個字節表示數據 長度,第二個字節是數據類型,后面的就是真正的數據(有可能是文字、有可能是圖片等等), 然后你可以依據長度從第三個字節向后讀,直到讀取到指定第一個字節指定長度的數據。
簡單的說,HTTP 協議就是一種瀏覽器與 Web 服務器之間約定好的通信協議,雙方按照指定 的協議編解碼數據。我們再直觀一點兒說,前面一直使用的 TextLine 編解碼器就是在讀取 網絡上傳遞過來的數據時,只要發現哪個字節里存放的是 ASCII 的 10、13 字符(\r、\n), 就認為之前的字節就是一個字符串(默認使用 UTF-8 編碼)。
以上所說的就是各種協議實際上就是網絡七層結構中的應用層協議,它位于網絡層(IP)、 傳輸層(TCP)之上,Mina 的協議編解碼器就是讓你實現一套自己的應用層協議棧。
首先我們創建一個傳遞的對象類:
?
package?com.entity;?import?javax.persistence.Column;?import?javax.persistence.Entity;?import?javax.persistence.GeneratedValue;?import?javax.persistence.GenerationType;?import?javax.persistence.Id;?import?javax.persistence.Table;??import?org.hibernate.annotations.Index;?????@Entity?@Table(name?=?"playerAccount")?public?class?PlayerAccount_Entity?{??????private?int?id;?????private?String?name;?????private?String?emailAdress;?????private?int?sex;??????@Id?????@Column(name?=?"playerAccountID")?????@GeneratedValue(strategy?=?GenerationType.AUTO)?????public?int?getId()?{?????????return?id;?????}??????public?void?setId(int?id)?{?????????this.id?=?id;?????}??????@Index(name="nameIndex")?????public?String?getName()?{?????????return?name;?????}??????public?void?setName(String?name)?{?????????this.name?=?name;?????}??????public?String?getEmailAdress()?{?????????return?emailAdress;?????}??????public?void?setEmailAdress(String?emailAdress)?{?????????this.emailAdress?=?emailAdress;?????}??????public?int?getSex()?{?????????return?sex;?????}??????public?void?setSex(int?sex)?{?????????this.sex?=?sex;?????}??}? 2. 創建一個編碼類:
?
package?com.protocol;?????import?java.nio.charset.Charset;?import?java.nio.charset.CharsetEncoder;??import?org.apache.mina.core.buffer.IoBuffer;?import?org.apache.mina.core.session.IoSession;?import?org.apache.mina.filter.codec.ProtocolEncoderAdapter;?import?org.apache.mina.filter.codec.ProtocolEncoderOutput;??import?com.entity.PlayerAccount_Entity;??public?class?HEncoder?extends?ProtocolEncoderAdapter?{??????private?final?Charset?charset;??????public?HEncoder(Charset?charset)?{?????????this.charset?=?charset;??????}??????@Override?????public?void?encode(IoSession?arg0,?Object?arg1,?ProtocolEncoderOutput?arg2)?????????????throws?Exception?{??????????CharsetEncoder?ce?=?charset.newEncoder();??????????PlayerAccount_Entity?paEntity?=?(PlayerAccount_Entity)?arg1;?????????String?name?=?paEntity.getName();???????????IoBuffer?buffer?=?IoBuffer.allocate(100).setAutoExpand(true);?????????buffer.putString(name,?ce);??????????buffer.flip();?????????arg2.write(buffer);??????}??}? 在 Mina 中編寫編碼器可以實現 ProtocolEncoder,其中有 encode()、dispose()兩個方法需 要實現。這里的 dispose()方法用于在銷毀編碼器時釋放關聯的資源,由于這個方法一般我 們并不關心,所以通常我們直接繼承適配器 ProtocolEncoderAdapter。
?
3.創建一個解碼類:
package?com.protocol;????import?java.nio.charset.Charset;?import?java.nio.charset.CharsetDecoder;??import?org.apache.mina.core.buffer.IoBuffer;?import?org.apache.mina.core.session.IoSession;?import?org.apache.mina.filter.codec.CumulativeProtocolDecoder;?import?org.apache.mina.filter.codec.ProtocolDecoderOutput;??import?com.entity.PlayerAccount_Entity;??public?class?HDecoder?extends?CumulativeProtocolDecoder?{??????private?final?Charset?charset;??????public?HDecoder(Charset?charset)?{?????????this.charset?=?charset;??????}??????@Override?????protected?boolean?doDecode(IoSession?arg0,?IoBuffer?arg1,?????????????ProtocolDecoderOutput?arg2)?throws?Exception?{?????????CharsetDecoder?cd?=?charset.newDecoder();??????????String?name?=?arg1.getString(cd);???????????PlayerAccount_Entity?paEntity?=?new?PlayerAccount_Entity();?????????paEntity.setName(name);???????????arg2.write(paEntity);?????????return?true;?????}??}? 在 Mina 中編寫解碼器,可以實現 ProtocolDecoder 接口,其中有 decode()、finishDecode()、 dispose()三個方法。這里的 finishDecode()方法可以用于處理在 IoSession 關閉時剩余的 讀取數據,一般這個方法并不會被使用到,除非協議中未定義任何標識數據什么時候截止 的約定,譬如:Http 響應的 Content-Length 未設定,那么在你認為讀取完數據后,關閉 TCP 連接(IoSession 的關閉)后,就可以調用這個方法處理剩余的數據,當然你也可以忽略調 剩余的數據。同樣的,一般情況下,我們只需要繼承適配器 ProtocolDecoderAdapter,關 注 decode()方法即可。
但前面說過解碼器相對編碼器來說,最麻煩的是數據發送過來的規模,以聊天室為例,一個 TCP 連接建立之后,那么隔一段時間就會有聊天內容發送過來,也就是 decode()方法會被往 復調用,這樣處理起來就會非常麻煩。那么 Mina 中幸好提供了 CumulativeProtocolDecoder 類,從名字上可以看出累積性的協議解碼器,也就是說只要有數據發送過來,這個類就會去 讀取數據,然后累積到內部的 IoBuffer 緩沖區,但是具體的拆包(把累積到緩沖區的數據 解碼為 JAVA 對象)交由子類的 doDecode()方法完成,實際上 CumulativeProtocolDecoder 就是在 decode()反復的調用暴漏給子類實現的 doDecode()方法。
具體執行過程如下所示:
A. 你的 doDecode()方法返回 true 時,CumulativeProtocolDecoder 的 decode()方法會首先判斷你是否在 doDecode()方法中從內部的 IoBuffer 緩沖區讀取了數據,如果沒有,ce); buffer.putString(smsContent, ce);buffer.flip();則會拋出非法的狀態異常,也就是你的 doDecode()方法返回 true 就表示你已經消費了 本次數據(相當于聊天室中一個完整的消息已經讀取完畢),進一步說,也就是此時你 必須已經消費過內部的 IoBuffer 緩沖區的數據(哪怕是消費了一個字節的數據)。如果 驗證過通過,那么 CumulativeProtocolDecoder 會檢查緩沖區內是否還有數據未讀取, 如果有就繼續調用 doDecode()方法,沒有就停止對 doDecode()方法的調用,直到有新 的數據被緩沖。
B. 當你的 doDecode()方法返回 false 時,CumulativeProtocolDecoder 會停止對 doDecode() 方法的調用,但此時如果本次數據還有未讀取完的,就將含有剩余數據的 IoBuffer 緩 沖區保存到 IoSession 中,以便下一次數據到來時可以從 IoSession 中提取合并。如果 發現本次數據全都讀取完畢,則清空 IoBuffer 緩沖區。簡而言之,當你認為讀取到的數據已經夠解碼了,那么就返回 true,否則就返回 false。這 個 CumulativeProtocolDecoder 其實最重要的工作就是幫你完成了數據的累積,因為這個工 作是很煩瑣的。
4.創建一個編解碼工廠類:
?
package?com.protocol;??import?java.nio.charset.Charset;??import?org.apache.mina.core.session.IoSession;?import?org.apache.mina.filter.codec.ProtocolCodecFactory;?import?org.apache.mina.filter.codec.ProtocolDecoder;?import?org.apache.mina.filter.codec.ProtocolEncoder;??????public?class?HCoderFactory?implements?ProtocolCodecFactory?{??????private?final?HEncoder?encoder;?????private?final?HDecoder?decoder;??????public?HCoderFactory()?{?????????this(Charset.defaultCharset());?????}??????public?HCoderFactory(Charset?charSet)?{?????????this.encoder?=?new?HEncoder(charSet);?????????this.decoder?=?new?HDecoder(charSet);?????}??????@Override?????public?ProtocolDecoder?getDecoder(IoSession?arg0)?throws?Exception?{??????????????????return?decoder;?????}??????@Override?????public?ProtocolEncoder?getEncoder(IoSession?arg0)?throws?Exception?{??????????????????return?encoder;?????}??}? 這個工廠類就是包裝了編碼器、解碼器,通過接口中的 getEncoder()、getDecoder() 方法向 ProtocolCodecFilter 過濾器返回編解碼器實例,以便在過濾器中對數據進行編解碼 處理。
? ? 5.?以上3個編解碼有關的類在Server與Client讀需要有,那么同時我們創建好了自定義的編解碼有關的類后,我們設置Server和Client的編碼工廠為我們自定義的編碼工廠類:
?
DefaultIoFilterChainBuilder?chain?=?acceptor.getFilterChain();??????chain.addLast("mycoder",?new?ProtocolCodecFilter(new?HCoderFactory(?????????????Charset.forName("UTF-8"))));? ?
6.書寫測試的消息處理器類Client和Server端;
Client端消息處理器:?
?
????import?org.apache.mina.core.service.IoHandlerAdapter;?import?org.apache.mina.core.session.IoSession;??import?com.protocol.PlayerAccount_Entity;??public?class?ClientMainHanlder?extends?IoHandlerAdapter?{??????????@Override?????public?void?sessionOpened(IoSession?session)?throws?Exception?{?????????PlayerAccount_Entity?ho?=?new?PlayerAccount_Entity();?????????ho.setName("李華明?xiaominghimi@gmail.com");?????????session.write(ho);?????}???????????@Override?????public?void?sessionClosed(IoSession?session)?{?????????System.out.println("I'm?Client?&&??I?closed!");?????}???????????@Override?????public?void?messageReceived(IoSession?session,?Object?message)?????????????throws?Exception?{?????????PlayerAccount_Entity?ho?=?(PlayerAccount_Entity)?message;?????????System.out.println("Server?Say:name:"?+?ho.getName());?????}?}? Server端消息處理器:
?
????import?org.apache.mina.core.service.IoHandlerAdapter;?import?org.apache.mina.core.session.IdleStatus;?import?org.apache.mina.core.session.IoSession;??import?com.entity.PlayerAccount_Entity;?import?com.sessionUtilities.HibernateUtil;??public?class?MainHanlder?extends?IoHandlerAdapter?{??????private?int?count?=?0;????????????????????public?void?sessionCreated(IoSession?session)?{?????????System.out.println("新客戶端連接");?????}?????????????????????public?void?sessionOpened(IoSession?session)?throws?Exception?{?????????count++;?????????System.out.println("第?"?+?count?+?"?個?client?登陸!address:?:?"?????????????????+?session.getRemoteAddress());???????????????}??????????????????@Override?????public?void?messageReceived(IoSession?session,?Object?message)?????????????throws?Exception?{???????????????????????????????????????????????????????PlayerAccount_Entity?ho?=?(PlayerAccount_Entity)?message;?????????System.out.println("Client?Say:"?+?ho.getName());???????????????????????????ho.setName("Himi??317426208@qq.com");?????????session.write(ho);??????????????}??????????????????@Override?????public?void?messageSent(IoSession?session,?Object?message)?{?????????System.out.println("信息已經傳送給客戶端");??????}??????????????????@Override?????public?void?sessionClosed(IoSession?session)?{?????????System.out.println("one?Clinet?Disconnect?!");?????}??????????????????@Override?????public?void?sessionIdle(IoSession?session,?IdleStatus?status)?{??????}??????????????????@Override?????public?void?exceptionCaught(IoSession?session,?Throwable?cause)?{?????????System.out.println("其他方法拋出異常");?????}??}? OK,首先啟動Server端,然后運行Client端,觀察控制臺:
?
?
?
總結
以上是生活随笔為你收集整理的【Apache Mina2.0开发之二】自定义实现Server/Client端的编解码工厂(自定义编码与解码器)!...的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。