在上一篇博文中已經簡單介紹過“過濾器”的概念,那麼在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 的協定編解碼器就是讓你實作一套自己的應用層協定棧。
首先我們建立一個傳遞的對象類:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
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;
/**
* @author himi
*/
@entity
@table(name = "playeraccount")
public class playeraccount_entity {
private int id;
private string name;
private string emailadress;
private int sex;// 0=man 1=woman
@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.建立一個解碼類:
import java.nio.charset.charsetdecoder;
import org.apache.mina.filter.codec.cumulativeprotocoldecoder;
import org.apache.mina.filter.codec.protocoldecoderoutput;
public class hdecoder extends cumulativeprotocoldecoder {
public hdecoder(charset charset) {
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.建立一個編解碼工廠類:
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);
public protocoldecoder getdecoder(iosession arg0) throws exception {
// todo auto-generated method stub
return decoder;
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 com.protocol.playeraccount_entity;
public class clientmainhanlder extends iohandleradapter {
// 當一個客端端連結到伺服器後
public void sessionopened(iosession session) throws exception {
playeraccount_entity ho = new playeraccount_entity();
ho.setname("李華明 [email protected]");
session.write(ho);
// 當一個用戶端關閉時
public void sessionclosed(iosession session) {
system.out.println("i'm client && i closed!");
// 當伺服器端發送的消息到達時:
public void messagereceived(iosession session, object message)
playeraccount_entity ho = (playeraccount_entity) message;
system.out.println("server say:name:" + ho.getname());
server端消息處理器:
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import org.apache.mina.core.session.idlestatus;
import com.sessionutilities.hibernateutil;
public class mainhanlder extends iohandleradapter {
private int count = 0;
// 當一個新用戶端連接配接後觸發此方法.
/*
* 這個方法當一個 session 對象被建立的時候被調用。對于 tcp 連接配接來說,連接配接被接受的時候 調用,但要注意此時 tcp
* 連接配接并未建立,此方法僅代表字面含義,也就是連接配接的對象 iosession 被建立完畢的時候,回調這個方法。 對于 udp
* 來說,當有資料包收到的時候回調這個方法,因為 udp 是無連接配接的。
public void sessioncreated(iosession session) {
system.out.println("新用戶端連接配接");
// 當一個客端端連結進入時 @override
* 這個方法在連接配接被打開時調用,它總是在 sessioncreated()方法之後被調用。對于 tcp 來
* 說,它是在連接配接被建立之後調用,你可以在這裡執行一些認證操作、發送資料等。 對于 udp 來說,這個方法與
* sessioncreated()沒什麼差別,但是緊跟其後執行。如果你每 隔一段時間,發送一些資料,那麼
* sessioncreated()方法隻會在第一次調用,但是 sessionopened()方法每次都會調用。
count++;
system.out.println("第 " + count + " 個 client 登陸!address: : "
+ session.getremoteaddress());
// 當用戶端發送的消息到達時:
* 對于 tcp 來說,連接配接被關閉時,調用這個方法。 對于 udp 來說,iosession 的 close()方法被調用時才會毀掉這個方法。
// // 我們己設定了伺服器解析消息的規則是一行一行讀取,這裡就可轉為string:
// string s = (string) message;
// // write the received data back to remote peer
// system.out.println("收到客戶機發來的消息: " + s);
// // 測試将消息回送給用戶端 session.write(s+count); count++;
system.out.println("client say:" + ho.getname());
ho.setname("himi [email protected]");
// 當資訊已經傳送給用戶端後觸發此方法.
* 當發送消息成功時調用這個方法,注意這裡的措辭,發送成功之後,也就是說發送消息是不 能用這個方法的。
public void messagesent(iosession session, object message) {
system.out.println("資訊已經傳送給用戶端");
system.out.println("one clinet disconnect !");
// 當連接配接空閑時觸發此方法.
* 這個方法在 iosession 的通道進入空閑狀态時調用,對于 udp 協定來說,這個方法始終不會 被調用。
public void sessionidle(iosession session, idlestatus status) {
// system.out.println("連接配接空閑");
// 當接口中其他方法抛出異常未被捕獲時觸發此方法
* 這個方法在你的程式、mina 自身出現異常時回調,一般這裡是關閉 iosession。
public void exceptioncaught(iosession session, throwable cause) {
system.out.println("其他方法抛出異常");
ok,首先啟動server端,然後運作client端,觀察控制台:

<a href="http://www.himigame.com/wp-content/uploads/2012/05/112.png"></a>