Mina使用起来多么简洁方便呀,就是不具备Java NIO的基础,只要了解了Mina常用的API,就可以灵活使用并完成应用开发。
首先,看Mina在项目中所处的位置,如下图:
Mina处于中间层,它不关心底层网络数据如何传输,只负责接收底层数据,过滤并转换为Java对象提供给我们的应用程序,然后把应用程序响应值过滤并转换为底层识别的字节,提供给底层传输; 总之,Mina是底层数据传输和用户应用程序交互的接口!
Mina工作流程图如下:
这个流程图不仅很直观的看出了Mina的工作流程,也涵盖了Mina的三个核心接口:IoService接口,IoFilter接口和IoHandler接口:
第一步.创建服务对象(客户端或服务端) ---IoService接口实现
第二步. 数据过滤(编码解码等) ---IOFilter接口实现
第三步. 业务处理 ---IoHandler接口实现
Mina的精髓是IOFilter,它可以进行日志记录,信息过滤,编码解码等操作,把数据接收发送从业务层独立出来。
创建服务对象,则是把NIO繁琐的部分进行封装,提供简洁的接口。
业务处理是我们最关心的部分,跟普通的应用程序没任何分别。
一.IoService接口
作用:IoService是创建服务的顶层接口,无论客户端还是服务端,都是从它继承实现的。
1.类结构
常用接口为:IoService,IoAcceptor,IoConnector
常用类为:NioSocketAcceptor,NioSocketConnector
类图如下:
先提出两个问题:
1.为什么有了IoService接口还要定义AbstractIoService抽象类?
2. AbstractIoService抽象类与IoAcceptor(IoConnector)有什么区别?
分析:
a.IoService接口声明了服务端的共有属性和行为;
b.IoAcceptor接口继承了IoService接口,并添加了服务端特有的接口属 性及方法,比如bind()方法,成为典型的服务端接口;
c.IoConnector接口同样继承了IoService接口,并添加了客户端特有的 接口属性及方法,比如connect()方法,成为典型的客户端接口;
IoService是IoAcceptor和IoConnector父接口,为什么不直接定义IoAcceptor和IoConnector接口呢,因为它们有共同的特点,比如共同属性,管理服务的方法等,所有IoService的出现是为了代码复用。
d.AbstractIoService实现了IoService中管理服务的方法,比如 getFilterChainBuilder方法,获得过滤器链;为什么有了IoService接口还要定义AbstractIoService抽象类?一样为了代码的复用!AbstractIoService抽象类实现了服务端或客户端的共有的管理服务的方法,不需要让IoService接口的子类重复的实现这些方法;
e.AbstractIoService抽象类继承了AbstractIoService抽象类并实现了 IoAcceptor接口,成为了拥有管理服务端实现功能的服务端类;我们常用的NioSocketAcceptor就是它的子类;
AbstractIoConnector抽象类继承了AbstractIoService抽象类并实现了 IoConnector接口,成为了拥有管理客户端实现功能的客户端类;我们常用的NioSocketConnector就是它的子类;
AbstractIoService抽象类与IoAcceptor(IoConnector)有什么区别?很清楚,AbstractIoService抽象类实现的是共有的管理服务的方法,只有管理功能的一个类;而两个接口却是不同的两个服务角色,一个客户端,一个服务端。
2.应用
在实际应用中,创建服务端和客户端的代码很简单:
创建服务端:
IoAcceptor acceptor = null; // 创建连接
try {
// 创建一个非阻塞的server端的Socket
acceptor = new NioSocketAcceptor();
创建客户端:
// 创建一个非阻塞的客户端程序
IoConnector connector = new NioSocketConnector();
而我们常常关心的就是服务端和客户端的一些参数信息:
1).IoSessionConfig getSessionConfig()
获得IoSession的配置对象IoSessionConfig,通过它可以设置Socket连接的一些选项。
a. void setReadBufferSize(int size)
这个方法设置读取缓冲的字节数,但一般不需要调用这个方法,因为IoProcessor 会自动调整缓冲的大小。你可以调用setMinReadBufferSize()、setMaxReadBufferSize()方法,这样无论IoProcessor 无论如何自动调整,都会在你指定的区间。
b. void setIdleTime(IdleStatus status,int idleTime)
这个方法设置关联在通道上的读、写或者是读写事件在指定时间内未发生,该通道就进入空闲状态。一旦调用这个方法,则每隔idleTime 都会回调过滤器、IoHandler 中的sessionIdle()方法。
c. void setWriteTimeout(int time)
这个方法设置写操作的超时时间。
d. void setUseReadOperation(boolean useReadOperation)
这个方法设置IoSession 的read()方法是否可用,默认是false。
// 获得IoSessionConfig对象
IoSessionConfig cfg=acceptor.getSessionConfig();
// 设置读取数据的缓冲区大小()
cfg.setReadBufferSize(2048);
// 读写通道10秒内无操作进入空闲状态
cfg.setIdleTime(IdleStatus.BOTH_IDLE, 10);
// 写操作超时时间10秒
cfg.setWriteTimeout(10);
2).DefaultIoFilterChainBuilder getFilterChain()
获得过滤器链,由此来配置过滤器;非常核心的一个配置!(过滤器是Mina的核心)
// 创建一个非阻塞的server端的Socket
acceptor = new NioSocketAcceptor();
// 设置日志过滤器
acceptor.getFilterChain().addLast("logger",new LoggingFilter());
// 设置过滤器(使用Mina提供的文本换行符编解码器)
acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new TextLineCodecFactory(Charset.forName("UTF-8"), LineDelimiter.WINDOWS.getValue(), LineDelimiter.WINDOWS.getValue())));
3).setHandler(IoHandler handler)
向IoService注册IoHandler进行业务处理。这是服务(无聊客户端还是服务端)必不可少的配置。
// 添加业务逻辑处理器类
connector.setHandler(new Demo1ClientHandler());
4).其他配置 服务端必须指定绑定的端口号
// 绑定端口 acceptor.bind(new InetSocketAddress(PORT));
logger.info("服务端启动成功... 端口号为:" + PORT);
客户端必须指定请求的服务器地址和端口号:(该方法是异步执行的)
ConnectFuture future = connector.connect(new InetSocketAddress( HOST, PORT));// 创建连接
future.awaitUninterruptibly();// 等待连接创建完成
session = future.getSession();// 获得session
session.write("我爱你mina");// 发送消息
5).关闭客户端
因为客户端的连接是异步的,所有必须先连接上服务端获得了session才能通信;同时,一旦需要关闭,必须指定disponse()方法关闭客户端,如下:
// 添加业务逻辑处理器类
connector.setHandler(new DemoClientHandler());
IoSession session = null;
try {
ConnectFuture future = connector.connect(new InetSocketAddress( HOST, PORT));// 创建连接
future.awaitUninterruptibly();// 等待连接创建完成
session = future.getSession();// 获得session
session.write("我爱你mina");// 发送消息
} catch (Exception e) {
logger.error("客户端链接异常...", e);
}
session.getCloseFuture().awaitUninterruptibly();// 等待连接断开
connector.dispose();
这是Mina2的处理方式,但在Mina1.1.7中,必须使用setWorkerTimeout()方法关闭客户端:
// 在关闭客户端前进入空闲状态的时间为1秒
//Set how many seconds the connection worker thread should remain alive once idle before terminating itself.
connector.setWorkerTimeout(1);
6).总结
IoService是创建服务端和客户端的接口,实际应用中我们大多都是应用它的实现类来创建服务对象;但是,在开发中你最关心的是你究竟处于哪个位置?说直白一点就是:你究竟是开发客户端还是服务端?比如经常跟银行打交道的朋友都知道,开发中往往银行是服务端,我们要和它打交道就是要知道银行服务端的IP,端口号和请求格式,写一个客户端来请求数据。
但有一点可以肯定,服务端的难度比客户端大!服务端首先要制定协议,其次是考虑并发量;这些都不是很轻松就可以搞定的。
IoService就这些啦,下面的IoFilter接口是Mina的精髓,要关键学习。
二.IoFilter接口
Mina最主要的工作就是把底层传输的字节码转换为Java对象,提供给应用程序;或者把应用程序返回的结果转换为字节码,交给底层传输。这些都是由IoFilter完成的,因此IoFilter是Mina的精髓所在。
在Mina程序中,IoFilter是必不可少的;有了它,Mina的层次结构才异常清晰:
IoFilter----消息过滤
IoHandler----业务处理
Filter,过滤器的意思。IoFilter,I/O操作的过滤器。IoFilter和Servlet中的过滤器一样,主要用于拦截和过滤网络传输中I/O操作的各种消息。在Mina的官方文档中已经提到了IoFilter的作用:
(1)记录事件的日志(Mina默认提供了LoggingFilter)
(2)测量系统性能
(3)信息验证
(4)过载控制
(5)信息的转换(主要就是编码和解码)
(6)和其他更多的信息
IoService实例会绑定一个DefaultIoFilterChainBuilder——过滤器链,我们把自定义的各种过滤器(IoFilter)自由的插放在这个过滤器链上了,类似于一种可插拔的功能!
1.类结构
常用接口为:IoFilter,IoFilterChainBuilder
常用类为:IoFilterAdapter,DefaultIoFilterChainBuilder,ProtocolCodecFilter,LoggingFilter
类图如下:
同上面,先提出两个问题:
1. 在IoService中如何添加多个IoFilter?
2. 如何自定义协议编解码器?
分析:
a. IoFilter有2个实现类:IoFilterAdapter是个抽象的适配器类,我们可以根据需要扩展这个类,并且有选择的覆盖过滤器的方法;所有方法的默认把事件转发到下一个过滤器;
查看源码如下:
/**
* {@inheritDoc}
*/
public void sessionOpened(NextFilter nextFilter, IoSession session)
throws Exception {
nextFilter.sessionOpened(session);
}
b .ReferenceCountingFilter封装了IoFilter实例,监看调用该filter的对象的个数,如果没有任何对象调用该IoFilter,就自动销毁IoFilter;查看源码如下:
public class ReferenceCountingFilter extends IoFilterAdapter {
private final IoFilter filter;
private int count = 0;
public ReferenceCountingFilter(IoFilter filter) {
this.filter = filter;
}
public void init() throws Exception {
// no-op, will init on-demand in pre-add if count == 0
}
public void destroy() throws Exception {
//no-op, will destroy on-demand in post-remove if count == 0
}
...
}
c. 实现IoFilterAdapter的类有多个,但是我们使用最多的就是ProtocolCodecFilter,它是我们自定义编解码器的入口。
2.应用
我们在应用中解释上面提述的两个问题!
1).添加过滤器
在IoService中如何添加多个IoFilter?如下代码,我添加了2个过滤器:LoggingFilter和TextLineCodecFactory(源码为入门的服务端程序Demo03Server.java基础上增加日志过滤器)
// 创建一个非阻塞的server端的Socket
acceptor = new NioSocketAcceptor();
//设置日志过滤器
acceptor.getFilterChain().addLast("logger", new LoggingFilter());
// 直接发送对象
acceptor.getFilterChain().addLast(
"codec",
new ProtocolCodecFilter(
new ObjectSerializationCodecFactory()));
// 获得IoSessionConfig对象
IoSessionConfig cfg = acceptor.getSessionConfig();
// 读写通道10秒内无操作进入空闲状态
cfg.setIdleTime(IdleStatus.BOTH_IDLE, 100);
// 绑定逻辑处理器
acceptor.setHandler(new Demo03ServerHandler());
// 绑定端口
acceptor.bind(new InetSocketAddress(PORT));
logger.info("服务端启动成功... 端口号为:" + PORT);
运行服务端程序,后再执行Demo03Client.java,服务端打印信息如下:
2016-01-03 22:13:14,571 INFO Demo03Server - 服务端启动成功... 端口号为:3005 2016-01-03 22:13:19,771 INFO LoggingFilter - CREATED 2016-01-03 22:13:19,773 INFO Demo03ServerHandler - 服务端与客户端创建连接... 2016-01-03 22:13:19,774 INFO LoggingFilter - OPENED 2016-01-03 22:13:19,774 INFO Demo03ServerHandler - 服务端与客户端连接打开... 2016-01-03 22:13:19,785 INFO LoggingFilter - RECEIVED: HeapBuffer[pos=0 lim=169 cap=2048: 00 00 00 A5 AC ED 00 05 73 72 01 00 29 63 6F 6D...] 2016-01-03 22:13:19,785 DEBUG ProtocolCodecFilter - Processing a MESSAGE_RECEIVED for session 1 2016-01-03 22:13:19,797 INFO Demo03ServerHandler - 发送人手机号码:13681803609 2016-01-03 22:13:19,798 INFO Demo03ServerHandler - 接受人手机号码:13721427169 2016-01-03 22:13:19,798 INFO Demo03ServerHandler - 发送信息:测试发送短信,这个是短信信息哦,当然长度是有限制的哦.... 2016-01-03 22:13:19,804 INFO LoggingFilter - SENT: HeapBuffer[pos=0 lim=0 cap=0: empty] 2016-01-03 22:13:19,804 INFO Demo03ServerHandler - 服务端发送信息成功... 2016-01-03 22:13:19,805 INFO LoggingFilter - CLOSED
注意:LoggerFilter的日志,修改代码,交换LoggingFilter和TextLineCodecFactory的位置,如下所示:
// 创建一个非阻塞的server端的Socket
acceptor = new NioSocketAcceptor();
// 直接发送对象
acceptor.getFilterChain().addLast(
"codec",
new ProtocolCodecFilter(
new ObjectSerializationCodecFactory()));
//设置日志过滤器
acceptor.getFilterChain().addLast("logger", new LoggingFilter());
// 获得IoSessionConfig对象
IoSessionConfig cfg = acceptor.getSessionConfig();
// 读写通道10秒内无操作进入空闲状态
cfg.setIdleTime(IdleStatus.BOTH_IDLE, 100);
// 绑定逻辑处理器
acceptor.setHandler(new Demo03ServerHandler());
// 绑定端口
acceptor.bind(new InetSocketAddress(PORT));
logger.info("服务端启动成功... 端口号为:" + PORT);
运行服务端程序,后再执行Demo03Client.java,服务端打印信息如下:
2016-01-03 22:18:20,578 INFO Demo03Server - 服务端启动成功... 端口号为:3005 2016-01-03 22:18:26,725 INFO LoggingFilter - CREATED 2016-01-03 22:18:26,727 INFO Demo03ServerHandler - 服务端与客户端创建连接... 2016-01-03 22:18:26,727 INFO LoggingFilter - OPENED 2016-01-03 22:18:26,727 INFO Demo03ServerHandler - 服务端与客户端连接打开... 2016-01-03 22:18:26,738 DEBUG ProtocolCodecFilter - Processing a MESSAGE_RECEIVED for session 1 2016-01-03 22:18:26,750 INFO LoggingFilter - RECEIVED: com.bijian.study.mina.dto.PhoneMessageDto@16e5255 2016-01-03 22:18:26,750 INFO Demo03ServerHandler - 发送人手机号码:13681803609 2016-01-03 22:18:26,750 INFO Demo03ServerHandler - 接受人手机号码:13721427169 2016-01-03 22:18:26,750 INFO Demo03ServerHandler - 发送信息:测试发送短信,这个是短信信息哦,当然长度是有限制的哦.... 2016-01-03 22:18:26,756 INFO LoggingFilter - SENT: 发送成功! 2016-01-03 22:18:26,757 INFO Demo03ServerHandler - 服务端发送信息成功... 2016-01-03 22:18:26,757 INFO LoggingFilter - CLOSED
对比上下日志,会发现,如果LoggingFilter在编码器前,它会在编码器处理前打印请求值和返回值的二进制信息,在编码器之后就不会打印!
在FilterChain中都是addLast()的方式添加在过滤链的最后面,这时候,把那个过滤器放在前面,就会先执行那个过滤器!
同addLast()方法一样,还提供了addFirst(),addBefore()等方法供使用。此时,就不难知道如何添加过滤器了吧!它们的顺序如何,就看你的设置的位置了!
同时发现,日志过滤器是根据IoSession的状态(创建、开启、发送、接收、异常等等)来记录会话的事件信息的!这对我们跟踪IoSession很有用。当地,也可以自定义logger的日志级别,定义记录那些状态的日志。比如:
// 设置日志过滤器
LoggingFilter lf=new LoggingFilter();
lf.setMessageReceivedLogLevel(LogLevel.DEBUG);
acceptor.getFilterChain().addLast("logger",lf);
2).自定义编解码器
如何自定义协议编解码器?
协议编解码器是在使用Mina 的时候最需要关注的对象,因为网络传输的数据都是二进制数据(byte),而在程序中面向的是JAVA 对象,这就需要在发送数据时将JAVA 对象编码二进制数据,接收数据时将二进制数据解码为JAVA 对象。
编解码器同样是以过滤器的形式安插在过滤器链上,如下所示:
// 设置过滤器(使用Mina提供的文本换行符编解码器)
acceptor.getFilterChain().addLast(
// 添加消息过滤器
"codec",
new ProtocolCodecFilter(new TextLineCodecFactory(Charset
.forName("UTF-8"),
LineDelimiter.WINDOWS.getValue(),
LineDelimiter.WINDOWS.getValue())));
协议编解码器是通过ProtocolCodecFilter过滤器构造的,看它的构造方法,它需要一个ProtocolCodecFactory对象:
public ProtocolCodecFilter(ProtocolCodecFactory factory) {
if (factory == null) {
throw new IllegalArgumentException("factory");
}
this.factory = factory;
}
ProtocolCodecFactory接口非常直接,通过ProtocolEncoder和ProtocolDecoder对象来构建!
public interface ProtocolCodecFactory {
/**
* Returns a new (or reusable) instance of {@link ProtocolEncoder} which
* encodes message objects into binary or protocol-specific data.
*/
ProtocolEncoder getEncoder(IoSession session) throws Exception;
/**
* Returns a new (or reusable) instance of {@link ProtocolDecoder} which
* decodes binary or protocol-specific data into message objects.
*/
ProtocolDecoder getDecoder(IoSession session) throws Exception;
}
ProtocolEncoder和ProtocolDecoder接口是Mina负责编码和解码的顶级接口!
编码和解码的前提就是协议的制定:比如上面我们使用的Mina自带的根据文本换行符解码的TextLineCodecFactory(),如果遇到文本换行符就开始编解码!
为什么要制定协议呢?常用的协议制定方法有哪些? 我们知道,底层传输的都是二进制数据,服务端和客户端建立连接后进行数据的交互,接受这对方发送来的消息,如何判定发送的请求或者响应的数据结束了呢?总不能一直傻等着,或者随意的就结束消息接收吧。这就需要一个规则!比如QQ聊天工具,当输入完一个消息后,点击发送按钮向对方发送时,此时系统就会在在你的消息后添加一个文本换行符,接收方看到这个文本换行符就认为这是一个完整的消息,解析成字符串显示出来。而这个规则,就称之为协议!
3).制定协议的方法
a.定长消息法:这种方式是使用长度固定的数据发送,一般适用于指令发送。譬如:数据发送端规定发送的数据都是双字节,AA 表示启动、BB 表示关闭等等。
b.字符定界法:这种方式是使用特殊字符作为数据的结束符,一般适用于简单数据的发送。譬如:在消息的结尾自动加上文本换行符(Windows使用\r\n,Linux使用\n),接收方见到文本换行符就认为是一个完整的消息,结束接收数据开始解析。注意:这个标识结束的特殊字符一定要简单,常常使用ASCII码中的特殊字符来标识。
c.定长报文头法:使用定长报文头,在报文头的某个域指明报文长度。该方法最灵活,使用最广。譬如:协议为:协议编号(1字节)+数据长度(4个字节)+真实数据。请求到达后,解析协议编号和数据长度,根据数据长度来判断后面的真实数据是否接收完整。HTTP 协议的消息报头中的Content-Length 也是表示消息正文的长度,这样数据的接收端就知道到底读到多长的字节数就不用再读取数据了。
根据协议,把二进制数据转换成Java对象称为解码(也叫做拆包);把Java对象转换为二进制数据称为编码(也叫做打包);我们这里重点讲解下后面两个协议的具体使用!
4).IoBuffer常用方法
Mina中传输的所有二进制信息都存放在IoBuffer中,IoBuffer是对Java NIO中ByteBuffer的封装(Mina2.0以前版本这个接口也是ByteBuffer),提供了更多操作二进制数据,对象的方法,并且存储空间可以自增长,用起来非常方便;简单理解,它就是个可变长度的byte数组!
a.static IoBuffer allocate(int capacity,boolean useDirectBuffer)
创建IoBuffer实例,第一个参数指定初始化容量,第二个参数指定使用直接缓冲区还是JAVA 内存堆的缓存区,默认为false。
b.IoBuffer setAutoExpand(boolean autoExpand)
这个方法设置IoBuffer 为自动扩展容量,也就是前面所说的长度可变,那么可以看出长度可变这个特性默认是不开启的。
c.IoBuffer flip()
limit=position,position=0,重置mask,为了读取做好准备,一般是结束buf操作,将buf写入输出流时调用;这个必须要调用,否则极有可能position!=limit,导致position后面没有数据;每次写入数据到输出流时,必须确保position=limit。
d.IoBuffer clear()与IoBuffer reset()
clear:limit=capacity , position=0,重置mark;它是不清空数据,但从头开始存放数据做准备,相当于覆盖老数据。
reset就是清空数据
e.int remaining()与boolean hasRemaining()
这两个方法一般是在调用了flip()后使用的,remaining()是返回limt-position的值!hasRemaining()则是判断当前是否有数据,返回position<limit的boolean值!
5).模拟根据文本换行符编解码实例
a.编写解码器
实现ProtocolDecoder接口,覆盖decode()方法。
package com.bijian.study.mina.codec;
import java.nio.charset.Charset;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolDecoder;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
public class MyTextLineCodecDecoder implements ProtocolDecoder {
private Charset charset = Charset.forName("UTF-8");
IoBuffer buf = IoBuffer.allocate(100).setAutoExpand(true);
public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out)
throws Exception {
while (in.hasRemaining()) {
byte b = in.get();
buf.put(b);
if(b == '\n') {
buf.flip();
byte[] msg = new byte[buf.limit()];
buf.get(msg);
String message = new String(msg, charset);
//解码成功,把buf重置
buf = IoBuffer.allocate(100).setAutoExpand(true);
out.write(message);
}
}
}
public void dispose(IoSession session) throws Exception {
}
public void finishDecode(IoSession session, ProtocolDecoderOutput out)
throws Exception {
}
}
方法解释:
public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out)
throws Exception {
decode方法的参数IoBuffer是建立连接后接收数据的字节数组;我们不断的从它里面读数据,直到遇上\r\n就停止读取数据,把上面累加的所有数据转换为一个字符串输出!
package com.bijian.study.mina.codec;
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.ProtocolEncoder;
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
public class MyTextLineCodecEncoder implements ProtocolEncoder {
private Charset charset = Charset.forName("utf-8");
public void encode(IoSession session, Object message,
ProtocolEncoderOutput out) throws Exception {
IoBuffer buf = IoBuffer.allocate(100).setAutoExpand(true);
CharsetEncoder ce = charset.newEncoder();
buf.putString(message.toString(), ce);
// buf.put(message.toString().getBytes(charset));
buf.put((byte) '\r');
buf.put((byte) '\n');
buf.flip();
out.write(buf);
}
public void dispose(IoSession session) throws Exception {
}
}
package com.bijian.study.mina.codec;
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 MyTextLineCodecFactory implements ProtocolCodecFactory {
public ProtocolDecoder getDecoder(IoSession session) throws Exception {
return new MyTextLineCodecDecoder();
}
public ProtocolEncoder getEncoder(IoSession session) throws Exception {
return new MyTextLineCodecEncoder();
}
}
d.测试
到现在,一个简单的根据\r\n换行符编解码的过滤器实现了,添加到一个服务端中测试。
package com.bijian.study.mina.server;
import java.net.InetSocketAddress;
import org.apache.log4j.Logger;
import org.apache.mina.core.service.IoAcceptor;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSessionConfig;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.logging.LogLevel;
import org.apache.mina.filter.logging.LoggingFilter;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import com.bijian.study.mina.codec.MyTextLineCodecFactory;
import com.bijian.study.mina.handler.DemoServerHandler;
public class TestServer01 {
private static Logger logger = Logger.getLogger(TestServer01.class);
private static int PORT = 3005;
public static void main(String[] args) {
IoAcceptor acceptor = null;
try {
// 创建一个非阻塞的server端的Socket
acceptor = new NioSocketAcceptor();
// 设置过滤器(使用Mina提供的文本换行符编解码器)
acceptor.getFilterChain().addLast(
"codec",
new ProtocolCodecFilter(new MyTextLineCodecFactory()));
// 设置日志过滤器
LoggingFilter lf = new LoggingFilter();
lf.setMessageReceivedLogLevel(LogLevel.DEBUG);
acceptor.getFilterChain().addLast("logger", lf);
// 获得IoSessionConfig对象
IoSessionConfig cfg = acceptor.getSessionConfig();
// 读写通道10秒内无操作进入空闲状态
cfg.setIdleTime(IdleStatus.BOTH_IDLE, 100);
// 绑定逻辑处理器
acceptor.setHandler(new DemoServerHandler());
// 绑定端口
acceptor.bind(new InetSocketAddress(PORT));
logger.info("服务端启动成功... 端口号为:" + PORT);
} catch (Exception e) {
logger.error("服务端启动异常....", e);
e.printStackTrace();
}
}
}
package com.bijian.study.mina.client;
import java.net.InetSocketAddress;
import org.apache.log4j.Logger;
import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.service.IoConnector;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.transport.socket.nio.NioSocketConnector;
import com.bijian.study.mina.codec.MyTextLineCodecFactory;
import com.bijian.study.mina.handler.DemoClientHandler;
public class TestClient01 {
private static Logger logger = Logger.getLogger(TestClient01.class);
private static String HOST = "127.0.0.1";
private static int PORT = 3005;
public static void main(String[] args) {
// 创建一个非阻塞的客户端程序
IoConnector connector = new NioSocketConnector();
// 设置链接超时时间
connector.setConnectTimeout(30000);
// 添加过滤器
connector.getFilterChain().addLast(
"codec",
new ProtocolCodecFilter(new MyTextLineCodecFactory()));
// 添加业务逻辑处理器类
connector.setHandler(new DemoClientHandler());
IoSession session = null;
try {
ConnectFuture future = connector.connect(new InetSocketAddress(
HOST, PORT));// 创建连接
future.awaitUninterruptibly();// 等待连接创建完成
session = future.getSession();// 获得session
session.write("hello mina");// 发送消息
} catch (Exception e) {
logger.error("客户端链接异常...", e);
}
session.getCloseFuture().awaitUninterruptibly();// 等待连接断开
connector.dispose();
}
}
先启动服务器,再启动客户端,服务器输出如下:
2016-01-20 21:43:28,081 INFO TestServer01 - 服务端启动成功... 端口号为:3005 2016-01-20 21:43:33,155 INFO LoggingFilter - CREATED 2016-01-20 21:43:33,171 INFO DemoServerHandler - 服务端与客户端创建连接... 2016-01-20 21:43:33,171 INFO LoggingFilter - OPENED 2016-01-20 21:43:33,171 INFO DemoServerHandler - 服务端与客户端连接打开... 2016-01-20 21:43:33,171 DEBUG ProtocolCodecFilter - Processing a MESSAGE_RECEIVED for session 1 2016-01-20 21:43:33,171 DEBUG LoggingFilter - RECEIVED: hello mina 2016-01-20 21:43:33,171 INFO DemoServerHandler - 服务端接收到的数据为:hello mina 2016-01-20 21:43:33,187 INFO LoggingFilter - SENT: Wed Jan 20 21:43:33 CST 2016 2016-01-20 21:43:33,187 INFO DemoServerHandler - 服务端发送信息成功... 2016-01-20 21:45:13,969 INFO LoggingFilter - IDLE 2016-01-20 21:45:13,969 INFO DemoServerHandler - 服务端进入空闲状态...
客户端输出如下:
2016-01-20 21:43:33,187 DEBUG ProtocolCodecFilter - Processing a MESSAGE_RECEIVED for session 1 2016-01-20 21:43:33,203 INFO DemoClientHandler - 客户端接收到的信息为:Wed Jan 20 21:43:33 CST 2016