最近在看郭霖大神的慕课网视频学习mina框架,把学习的情况记录下来,方便以后查阅。
网上关于mina详细介绍很多,我就不班门弄斧了。一边看视频一边慢慢更新。
这次把注释放到代码里面,更加细致。
官网下载mina的jar包,先只导入mina-core-x.x.x.jar和slf4j-api-x.x.x.jar(mina日志打印包,不导入会报错)。
使用mina最基本的功能只需要四步。
主函数类
public class MinaServer {
public static void main(String[] args) {
try {
NioSocketAcceptor acceptor = new NioSocketAcceptor();
/*
* 自己定义Handler对象来处理消息
*/
acceptor.setHandler(new MyServerHandler());
/*
* 获取拦截器来过滤信息
* 添加新的拦截器
* MyTextLineFactory()是自己定义的拦截器
*/
acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new MyTextLineFactory()));
/*
* 定义idle,即设置连接多久无消息收发为进入空闲状态
* 第一个参数IdleStatus.BOTH_IDLE为多久没有读取且没有收到向客户端发送消息
* 第二个参数是设置时间,单位是秒。每隔60秒再次输出表示你又进入空闲状态
*/
acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 60);
/*
* 绑定一个端口
*/
acceptor.bind(new InetSocketAddress(9090));
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
自定义MyserverHandler类
public class MyServerHandler extends IoHandlerAdapter {
/**
* 网络异常的时候调用方法
*/
@Override
public void exceptionCaught(IoSession session, Throwable cause)
throws Exception {
System.out.println("exceptionCaught");
}
/**
* 接收到消息的时候调用方法
*/
@Override
public void messageReceived(IoSession session, Object message)
throws Exception {
String s = (String)message;
System.out.println("messageReceived : " + s);
}
/**
* 发出消息的时候调用方法
*/
@Override
public void messageSent(IoSession session, Object message) throws Exception {
System.out.println("messageSent");
}
/**
* 客户端会话关闭时调用的方法
*/
@Override
public void sessionClosed(IoSession session) throws Exception {
System.out.println("sessionClosed");
}
/**
* 客户端会话开启的时候调用的方法
*/
@Override
public void sessionCreated(IoSession session) throws Exception {
System.out.println("sessionCreated");
}
/**
* 客户端进入空闲状态的时候调用的方法
*/
@Override
public void sessionIdle(IoSession session, IdleStatus status)
throws Exception {
System.out.println("sessionIdle");
}
/**
* 会话打开的时候调用的方法
*/
@Override
public void sessionOpened(IoSession session) throws Exception {
System.out.println("sessionOpened");
}
}
继承于IoHandlerAdapter类,实现里面的几个方法,将log打印出来、
自定义拦截器来拦截收发消息
public class MyTextLineFactory implements ProtocolCodecFactory {
//自定义加码类
private MyTextLineEncoder mEncoder;
//自定义解码类
private MyTextLineDecoder mDecoder;
//自定义防止数据丢失解码类
private MyTextLineCumulativeDecoder mCumulativeDecoder;
public MyTextLineFactory() {
mEncoder = new MyTextLineEncoder();
mDecoder = new MyTextLineDecoder();
mCumulativeDecoder = new MyTextLineCumulativeDecoder();
}
public ProtocolDecoder getDecoder(IoSession arg0) throws Exception {
//返回的是防丢失的Decoder
return mCumulativeDecoder;
}
public ProtocolEncoder getEncoder(IoSession arg0) throws Exception {
return mEncoder;
}
}
你可以用mina中定义的,不过为了适应自己的需要,还是自己定义比较好。
新建一个MyTextLineFactory继承于ProtocolCodecFactory。
在MyTextLineFactory中,使用了自定义的加解码器来实现收发数据的加解码。
自定义编码类来给数据加码
public class MyTextLineEncoder implements ProtocolEncoder {
public void dispose(IoSession arg0) throws Exception {
}
public void encode(IoSession session, Object message, ProtocolEncoderOutput out)
throws Exception {
String s = null;
if(message instanceof String) {
s = (String)message;
}
//开始转码操作
if(s != null) {
/*
* 将字符串进行编码,采取系统默认的编码方式
* 为了提高效率(因为每一次都要获取系统默认的编码方式),这里采取一个判断
*
* 类似于ListView的ViewHoder
* 如果session中没有获取到属性 encoder,那么就创建一个为系统默认编码的加密方式
* 如果有的话就使用之前就有的,这样的方法可以提高运行效率
*/
CharsetEncoder charsetEncoder = (CharsetEncoder)session.getAttribute("encoder");
if(charsetEncoder == null) {
charsetEncoder = Charset.defaultCharset().newEncoder();
session.setAttribute("encoder", charsetEncoder);
}
//mina框架中开辟内存
IoBuffer ioBuffer = IoBuffer.allocate(s.length());
//设置内存可以自动开辟内存
ioBuffer.setAutoExpand(true);
//设置put数据类型为系统本身的格式
ioBuffer.putString(s,charsetEncoder);
ioBuffer.flip();
//写出数据
out.write(ioBuffer);
}
}
}
最后定义解码器,将收到的字节码转化为字符串
public class MyTextLineCumulativeDecoder extends CumulativeProtocolDecoder{
/**
* 当且仅当数据读取完的时候返回true
* 如果没有读取完想要下次再进行读取的时候就返回false
*/
@Override
protected boolean doDecode(IoSession session, IoBuffer ioBuffer,
ProtocolDecoderOutput out) throws Exception {
//开始读取的位置
int startPosition = ioBuffer.position();
//判断ioBuffer里面是否还有字节可以读取
while(ioBuffer.hasRemaining()) {
byte b = ioBuffer.get();
if(b == '\n') {
//读取到 '\n'的时候的位置记录下来
int currentPosition = ioBuffer.position();
//当前的总长度
int limit = ioBuffer.limit();
//把初始位置定在字符串开始的第一个字节上
ioBuffer.position(startPosition);
//limit是终点的的位置,这里就是读到currentPosition的地方
//从startPosition到currentPosition位置,就是读取到'\n'的总长度
ioBuffer.limit(currentPosition);
//截取从startPosition位置到currentPosition位置的所有字节,返回的是一个IoBuffer
IoBuffer sliceIoBuffer = ioBuffer.slice();
//把字节码转换成字符串
byte [] dest = new byte[sliceIoBuffer.limit()];
sliceIoBuffer.get(dest);
String str = new String(dest);
//写出数据
out.write(str);
/*
* 因为在之间重定位在startPosition的位置,如果我们不重新定位到当前位置的话,
* 就会一直从 startPosition -> currentPosition不停的循环导致死循环
* 这里要把开始位置定义到currentPosition
*/
ioBuffer.position(currentPosition);
ioBuffer.limit(limit);
return true;
}
}
//如果没有读取完就要把position重新读取到开始的位置
ioBuffer.position(startPosition);
return false;
}
}
这里选择继承的是CumulativeProtocalDecoder类,这个类和ProtocalDecoder的区别在于decode方法有一个返回值,这个返回值是在循环读取的时候判断是否读取完成整个字节。如果是完全读取就返回一个true。否则就返回一个false,并且将position定位在开始的位置重新读取。
以上是服务器端的代码,下面是客户端的代码。
写一些简简单单测试的代码测试是否能联通。
public class MinaClient {
public static void main(String[] args) throws Exception{
NioSocketConnector connector = new NioSocketConnector();
connector.setHandler(new MyClientHandler());
connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new TextLineCodecFactory()));
ConnectFuture future = connector.connect(new InetSocketAddress("127.0.0.1",9090));
//阻塞连接,直到连接上服务器
future.awaitUninterruptibly();
//当连接上后会返回一个IoSession对象,有这个对象可以完成许多事情
IoSession ioSession = future.getSession();
//获取从控制台输入的流
BufferedReader readIn = new BufferedReader(new InputStreamReader(System.in));
String inputContent;
//当不输入bye的时候就发送输出的内容
while(!(inputContent = readIn.readLine()).equals("bye")) {
ioSession.write(inputContent);
}
}
}
首先还是导入那两个包
客户端的代码和服务端的代码差不多。从控制台输出数据,服务器能够接受到数据。
其中new MyClientHandler()中的代码与服务器中的MyServerHandler是一样的,只是改了名字而已。
这样就完成了一个简单的客户端和服务器连接的例子