天天看點

【APACHE MINA2.0開發之二】自定義實作SERVER/CLIENT端的編解碼工廠(自定義編碼與解碼器)!

在上一篇博文中已經簡單介紹過“過濾器”的概念,那麼在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端,觀察控制台:

【APACHE MINA2.0開發之二】自定義實作SERVER/CLIENT端的編解碼工廠(自定義編碼與解碼器)!

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