初识mina

    最近在看郭霖大神的慕课网视频学习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是一样的,只是改了名字而已。
这样就完成了一个简单的客户端和服务器连接的例子







  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值