在上一篇博文中已经简单介绍过“过滤器”的概念,那么在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>