什么是Mina?
Apache MINA is a network application framework which helps users develop high performance and high scalability network applications easily. It provides an abstract event-driven asynchronous API over various transports such as TCP/IP and UDP/IP via Java NIO.
Apache MINA是一个网络应用框架,可以帮助我们开发高性能和高扩展性的网络应用。它通过封装Java NIO提供了一个支持各种传输协议(如:TCP/IP和UDP/IP)的抽象事件驱动异步API。
NIO framework library,
client server framework library, or
a networking socket library
Mina是对Java NIO框架进行了一层封装的Socket库。
Apache MINA comes with many subprojects :
Asyncweb : An HTTP server build on top of MINA asynchronous framework
FtpServer : A FTP server
SSHd : A Java library supporting the SSH protocol
Vysper : An XMPP server
Apache MINA自带了许多子项目:
异步http服务
ftp服务
一个支持ssh协议的java库
XMPP服务
Mina框架主页:
Mina框架下载地址:
https://mina.apache.org/downloads-mina.html
为什么使用Mina?
传统socket:阻塞式通信
每建立一个Socket连接时,同时创建一个新线程对该Socket进行单独通信(采用阻塞的方式通信)。这种方式具有很高的响应速度,并且控制起来也很简单,在连接数较少的时候非常有效,但是如果对每一个连接都产生一个线程的无疑是对系统资源的一种浪费,如果连接数较多将会出现资源不足的情况。
nio:非阻塞通信
nio设计背后的基石:反应器模式,用于事件多路分离和分派的体系结构模式。
反应器模式的核心功能如下:
将事件多路分用
将事件分派到各自相应的事件处理程序
NIO 的非阻塞 I/O 机制是围绕 选择器和 通道构建的。Channel 类表示服务器和客户机之间的一种通信机制。Selector 类是 Channel 的多路复用器。 Selector 类将传入客户机请求多路分用并将它们分派到各自的请求处理程序。
通道(Channel 类):表示服务器和客户机之间的一种通信机制。
选择器(Selector类):是 Channel 的多路复用器。
Selector 类将传入的客户机请求多路分用并将它们分派到各自的请求处理程序。
简单的来说:NIO是一个基于事件的IO架构。
最基本的思想就是:有事件我通知你,你再去做你的事情。而且NIO的主线程只有一个,不像传统的模型,需要多个线程以应对客户端请求,也减轻了JVM的工作量。
当Channel注册至Selector以后,经典的调用方法如下:nio中取得事件通知,就是在selector的select事件中完成的。在selector事件时有一个线程向操作系统询问,selector中注册的Channel&&SelectionKey的键值对的各种事件是否有发生,如果有则添加到selector的selectedKeys属性Set中去,并返回本次有多少个感兴趣的事情发生。如果发现这个值>0,表示有事件发生,马上迭代selectedKeys中的SelectionKey,根据Key中的表示的事件,来做相应的处理。实际上,这段说明表明了异步socket的核心,即异步socket不过是将多个socket的调度(或者还有他们的线程调度)全部交给操作系统自己去完成,异步的核心Selector,不过是将这些调度收集、分发而已。
参考文章地址:http://lcllcl987.iteye.com/blog/70703
使用Mina框架实现C/S通讯
Mina快速入门
https://mina.apache.org/mina-project/quick-start-guide.html
Mina API文档
https://mina.apache.org/mina-project/apidocs/index.html
在工程中包含以下两个jar
客户端代码
package linchaolong.mina.client;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.net.UnknownHostException;
// Client Socket
public class Client {
private boolean _isExit = false;
public static void main(String[] args) {
Client client = new Client();
client.start();
}
private void start() {
BufferedReader in = null;
try {
final Socket socket = new Socket("127.0.0.1", 9999);
final BufferedReader reader = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(
socket.getOutputStream()));
// 创建一个线程用于接收消息
new Thread(new Runnable() {
@Override
public void run() {
try {
while( !_isExit ){
String line = reader.readLine();
System.out.println("from server : " + line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
// 从控制台输入消息,发送到服务端
in = new BufferedReader(new InputStreamReader(System.in));
String line = null;
while (!"bye".equals((line = in.readLine()))) {
writer.write(line);
writer.newLine();
writer.flush();
}
_isExit = true;
in.close();
writer.close();
reader.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
_isExit = true;
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
服务端代码
package linchaolong.mina.server;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;
import java.util.Timer;
import java.util.TimerTask;
import linchaolong.mina.client.MyIoHandler;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
import org.apache.mina.filter.logging.LoggingFilter;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
public class MinaServer {
public static void main(String[] args) {
MinaServer server = new MinaServer();
server.start();
}
private void start() {
try {
final NioSocketAcceptor acceptor = new NioSocketAcceptor();
// 消息的传送会经过一系列拦截器
// 添加自定义的拦截器
acceptor.getFilterChain().addLast( "logger", new LoggingFilter() );
acceptor.getFilterChain().addLast( "codec", new ProtocolCodecFilter( new TextLineCodecFactory( Charset.forName( "UTF-8" )))); // 字符编码
// 设置会话管理类
acceptor.setHandler(new MyIoHandler());
// 设置read buff size(字节)
acceptor.getSessionConfig().setReadBufferSize( 2048 ); // buff是可自动扩展的,但设置一个合适值,效率会较高。
// 设置空闲时间(单位:秒,会话空闲时会调用IoHandler的sessionIdle方法,BOTH_IDLE:读和写,READER_IDLE:读,WRITER_IDLE:写)
acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10);
// 监听一个端口
acceptor.bind(new InetSocketAddress(9999));
new Timer().schedule(new TimerTask() {
@Override
public void run() {
// 发送一个广播,所以会话都能接受到
acceptor.broadcast("Hello All.");
}
}, 10000);
} catch (IOException e) {
e.printStackTrace();
}
}
}
会话管理类
package linchaolong.mina.client;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.Date;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
public class MyIoHandler extends IoHandlerAdapter {
// 会话创建
@Override
public void sessionCreated(IoSession session) throws Exception {
super.sessionCreated(session);
// 获取客户端ip
InetSocketAddress socketAddress = (InetSocketAddress) session.getRemoteAddress();
InetAddress inetAddress = socketAddress.getAddress();
System.out.println("sessionCreated id=" + session.getId() + " , ip=" + inetAddress.getHostAddress());
}
// 会话打开
@Override
public void sessionOpened(IoSession session) throws Exception {
super.sessionOpened(session);
System.out.println("sessionOpened");
}
// 会话关闭
@Override
public void sessionClosed(IoSession session) throws Exception {
super.sessionClosed(session);
System.out.println("sessionClosed");
}
// 会话等待
@Override
public void sessionIdle(IoSession session, IdleStatus status)
throws Exception {
super.sessionIdle(session, status);
System.out.println( "IDLE " + session.getIdleCount( status ));
}
// 会话异常
@Override
public void exceptionCaught(IoSession session, Throwable cause)
throws Exception {
super.exceptionCaught(session, cause);
cause.printStackTrace();
}
// 接受消息
@Override
public void messageReceived(IoSession session, Object message)
throws Exception {
// 读取客户端消息
String str = message.toString();
System.out.println("from session " + session.getId() + " : " + str);
if (str.trim().equalsIgnoreCase("quit")) {
session.close(true);
return;
}
// 给客户端返回数据
session.write(new Date().toString());
}
// 发送消息
@Override
public void messageSent(IoSession session, Object message) throws Exception {
super.messageSent(session, message);
System.out.println("messageSent : " + message);
}
// 关闭输入流
@Override
public void inputClosed(IoSession session) throws Exception {
super.inputClosed(session);
System.out.println("inputClosed");
}
}
过滤器(Filter)
过滤器的主要作用就是将协议逻辑从业务逻辑中分离出来。
Codec Filter
https://mina.apache.org/mina-project/userguide/ch9-codec-filter/ch9-codec-filter.html
大多数网络应用程序需要一种方式来找出当前消息结束和下一个消息开始的地方。
你可以实现这一切的逻辑在你的IoHandler,但加入了ProtocolCodecFilter加入将会使你的代码更清洁,更容易维护。
它允许您将协议逻辑从业务逻辑(IoHandler)中分离出来。
1.创建一个工厂类,实现ProtocolCodecFactory接口
package linchaolong.mina.server.filter;
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 {
private final MyTextLineEncoder encoder;
private final MyTextLineDecoder decoder;
public MyTextLineCodecFactory() {
// 使用自定义编码/解码类
encoder = new MyTextLineEncoder();
decoder = new MyTextLineDecoder();
}
@Override
public ProtocolEncoder getEncoder(IoSession session) throws Exception {
return encoder;
}
@Override
public ProtocolDecoder getDecoder(IoSession session) throws Exception {
return decoder;
}
}
2.自定义编码器
package linchaolong.mina.server.filter;
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 MyTextLineEncoder implements ProtocolEncoder {
@Override
public void encode(IoSession session, Object message,
ProtocolEncoderOutput out) throws Exception {
//字符编码器
CharsetEncoder encoder = (CharsetEncoder) session.getAttribute("encoder");
if (encoder == null) {
encoder = Charset.forName( "GBK" ).newEncoder();
session.setAttribute("encoder", encoder);
}
String value = (message == null ? "" : message.toString());
IoBuffer buf = IoBuffer.allocate(value.length()) // 初始化buff size为消息长度
.setAutoExpand(true); //可自动扩展buff size
buf.putString(value, encoder); //把消息添加到buff,并使用指定编码器
buf.flip(); // update position
out.write(buf);
}
@Override
public void dispose(IoSession session) throws Exception {
}
}
3.自定义解码器
package linchaolong.mina.server.filter;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
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 MyTextLineDecoder implements ProtocolDecoder {
@Override
public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out)
throws Exception {
// 字符解码器
CharsetDecoder decoder = (CharsetDecoder) session.getAttribute("decoder");
if (decoder == null) {
decoder = Charset.forName( "GBK" ).newDecoder();
session.setAttribute("decoder", decoder);
}
// 开始位置
int startPos = in.position();
while(in.hasRemaining()){
byte b = in.get();
if (b == ‘\n‘) {
int pos = in.position();
int limit = in.limit();
// 截取从开始位置到当前位置的数据,转换为字符串
// 1.设置截取的位置
in.position(startPos);
in.limit(pos);
//2.截取
IoBuffer buff = in.slice();
byte bytes[] = new byte[limit-2]; // -2 不包含换行符和结束符
buff.get(bytes);
//3.解码
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
CharBuffer charBuffer = decoder.decode(byteBuffer);
out.write(charBuffer.toString());
//4.复原
in.position(pos);
in.limit(limit);
}
}
}
@Override
public void finishDecode(IoSession session, ProtocolDecoderOutput out)
throws Exception {
}
@Override
public void dispose(IoSession session) throws Exception {
}
}
4.使用自定义编码过滤器
acceptor.getFilterChain().addLast( "codec", new ProtocolCodecFilter( new MyTextLineCodecFactory()));
项目地址:https://coding.net/u/linchaolong/p/MinaTest