天天看点

【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>