半步多 玄玉的博客

Mina入门之传输Java对象

2012-06-14
玄玉

Mina框架官网为:http://mina.apache.org/,关于介绍,这里只简单说两句,更多的请自行Google

首先,开发一个Mina应用,整体来讲就是:创建连结、设定过滤规则、编写消息处理器这三步

其次,Mina执行流程大致为:IoService-->IoProcessor-->IoFilter-->IoHandler-->IoFilter-->IoProcessor-->IoService

最后,说下消息处理器

无论使用Mina编写服务端还是客户端,都要自定义消息处理器,简而言之就是帮你处理通信消息的东西

比如下面演示代码中的ServerHandler.javaClientHandler.java

自定义消息处理器,通常会继承IoHandlerAdapter.java,通信过程中,会根据情况自动调用里面定义的方法

它一般用于处理程序接收到的消息,以及通信中的连结、断开、消息到达等事件,类似于Swing事件中的调用机制

ok,let`s drink code …

公共的实体类

这是客户端和服务端通信时,传输信息的实体类UserInfo.java

package com.jadyer.demo.mina.model;
import java.io.Serializable;

/**
 * Mina传输的实体类,要求其实现Serializable接口
 * Created by 玄玉<https://jadyer.cn/> on 2012/06/14 14:16.
 */
public class UserInfo implements Serializable {
    private static final long serialVersionUID = 5964298293666394269L;
    private String name;
    private int age;

    /*-- 两个属性的setter和getter略 --*/

    public UserInfo(){}

    public UserInfo(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

服务端

下面是服务端启动类MyServer.java

package com.jadyer.demo.mina.server;
import org.apache.mina.core.service.IoAcceptor;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.serialization.ObjectSerializationCodecFactory;
import org.apache.mina.filter.logging.LoggingFilter;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import java.io.IOException;
import java.net.InetSocketAddress;

/**
 * 服务端示例
 * Created by 玄玉<https://jadyer.cn/> on 2012/06/14 14:06.
 */
public class MyServer {
    public static void main(String[] args) throws IOException {
        //服务器端绑定的端口
        int bindPort = 9876;

        //初始化服务端TCP/IP的基于NIO的套接字,即创建非阻塞服务器端,类似于java.net.ServerSocket
        IoAcceptor acceptor = new NioSocketAcceptor();

        //调用IoSessionConfig设置读取数据的缓冲区大小、读写通道均在10秒内无任何操作就进入空闲状态
        acceptor.getSessionConfig().setReadBufferSize(2048);
        acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10);

        //启用Mina的日志跟踪
        acceptor.getFilterChain().addLast("logger", new LoggingFilter());

        //这段代码要在acceptor.bind()方法前执行,因为绑定套接字之后,就不能再做这些准备工作了
        //设定服务器解析消息的规则是以Object对象为单位进行传输,注意此时该对象需实现Serializable接口
        acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new ObjectSerializationCodecFactory()));

        //若传输的是以换行符为标识的数据,则可适当考虑Mina自带的TextLineCodecFactory换行符编解码器工厂
        //若不清楚操作系统或Telnet软件的换行符是什么,可以删掉TextLineCodecFactory()后面两个参数
        //即TextLineCodecFactory(Charset.forName("UTF-8")),此时用的就是TextLineCodec内部自动识别机制
        //acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(
        //    new TextLineCodecFactory(
        //        Charset.forName("UTF-8"), LineDelimiter.WINDOWS.getValue(), LineDelimiter.WINDOWS.getValue()
        //    )
        ));

        //指定服务器端的消息处理器(它负责编写业务逻辑,即收发数据的地方)
        //然后把编写好的IoHandler注册到IoService,它也要在acceptor.bind()方法之前前执行
        acceptor.setHandler(new ServerHandler());

        //绑定端口,启动服务器
        //这里与java.net.ServerSocket不同的是:IoAcceptor可以多次调用bind()方法同时监听多个端口
        //或者在一个方法中传入多个SocketAddress参数,来监听多个端口
        acceptor.bind(new InetSocketAddress(bindPort));

        System.out.println("MinaServer is startup, and it`s listing on := " + bindPort);
    }
}

下面是服务端的自定义消息处理器ServerHandler.java

package com.jadyer.demo.mina.server;
import com.jadyer.demo.mina.model.UserInfo;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;

/**
 * 服务端的自定义消息处理器
 * Created by 玄玉<https://jadyer.cn/> on 2012/06/14 14:06.
 */
class ServerHandler extends IoHandlerAdapter {
    //这是IoHandlerAdapter类中最重要的一个方法
    //IoSession代表与对方机器的TCP/IP连接,Object代表接收到的数据
    @Override
    public void messageReceived(IoSession session, Object message) throws Exception {
        //如果服务端使用的是TextLineCodecFactory(),也就是说设定了解析消息的规则为一行一行读取
        //那么这里就可以写为String str = message.toString();
        //不过,我们已经设定了服务器解析消息的规则:以Java实体对象为单位进行传输
        UserInfo userInfo = (UserInfo)message;
        System.out.println("收到客户机的信息-->" + ReflectionToStringBuilder.toString(userInfo));
        session.write(new UserInfo(userInfo.getName()+"==>>是个神秘的人", 22));
    }

    @Override
    public void sessionOpened(IoSession session) throws Exception{
        System.out.println("InComing Client:" + session.getRemoteAddress());
    }
}

客户端

下面是客户端启动类MyClient.java

package com.jadyer.demo.mina.client;
import com.jadyer.demo.mina.model.UserInfo;
import org.apache.mina.core.service.IoConnector;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.serialization.ObjectSerializationCodecFactory;
import org.apache.mina.transport.socket.nio.NioSocketConnector;
import java.net.InetSocketAddress;
import java.net.SocketAddress;

/**
 * 客户端示例
 * Created by 玄玉<https://jadyer.cn/> on 2012/06/14 14:06.
 */
public class MyClient {
    public static void main(String[] args) {
        //NioSocketConnector功能类似于java.net.Socket,但它是非阻塞的读取数据
        IoConnector connector = new NioSocketConnector();

        connector.setConnectTimeoutMillis(3000);

        //connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(
        //    new TextLineCodecFactory(
        //        Charset.forName("UTF-8"), LineDelimiter.WINDOWS.getValue(), LineDelimiter.WINDOWS.getValue()
        //    )
        //));
        connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new ObjectSerializationCodecFactory()));

        //注册IoHandler,即指定客户器端的消息处理器
        //connector.setHandler(new ClientHandler("岂曰无衣..\r\n月照沟渠...."));
        connector.setHandler(new ClientHandler(new UserInfo("查文斌", 99)));

        //连接服务器
        //该方法是异步执行的,且可以同时连接多个服务端
        SocketAddress remoteAddress = new InetSocketAddress("127.0.0.1", 9876);
        SocketAddress localAddress = new InetSocketAddress("127.0.0.1", 5432);
        //第二个参数若不传递则会使用本地的随机端口访问Server
        //connector.connect(remoteAddress, localAddress);
        connector.connect(remoteAddress);

        System.out.println("Mina Client is startup");
    }
}

下面是客户端的自定义消息处理器ClientHandler.java

package com.jadyer.demo.mina.client;
import com.jadyer.demo.mina.model.UserInfo;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;

/**
 * 客户端的自定义消息处理器
 * Created by 玄玉<https://jadyer.cn/> on 2012/06/14 14:06.
 */
class ClientHandler extends IoHandlerAdapter {
    //双方采用TextLineCodecFactory(即换行符为标识的数据通信)时可以直接定义字符串接收
    //private final String values;
    //ClientHandler(String _values){
    //    this.values = _values;
    //}
    private final UserInfo userInfo;
    ClientHandler(UserInfo _userInfo){
        this.userInfo = _userInfo;
    }

    @Override
    public void sessionCreated(IoSession session) throws Exception {
        System.out.println("sessionCreated is invoked....");
    }

    /**
     * 发送消息
     * -----------------------------------------------------------------------------------
     * 客户端连接有两个事件:sessionCreated()和sessionOpened()
     * sessionCreated()是由IoProcessor线程触发的,sessionOpened()跟在其后,是由业务线程触发的
     * 由于Mina中的IoProcessor线程非常少,因此sessionCreated()通常用于处理耗时短的操作
     * 而将业务初始化等功能放在sessionOpened()事件中,比如发送消息
     * -----------------------------------------------------------------------------------
     * 我们可以在sessionOpened()、messageReceived()中通过IoSession.write()方法发送消息
     * 因为在这两个方法中,TCP连接都是打开的状态,只不过发送的时机不同
     * sessionOpened()是在TCP连接建立之后,接收到数据之前发送
     * messageReceived()是在接收到数据之后发送
     * -----------------------------------------------------------------------------------
     */
    @Override
    public void sessionOpened(IoSession session) throws Exception {
        //写数据,该操作是异步的
        //session.write(this.values);
        session.write(this.userInfo);
    }

    @Override
    public void messageReceived(IoSession session, Object message) throws Exception {
        UserInfo userInfo = (UserInfo)message;
        System.out.println("收到服务机的信息-->" + ReflectionToStringBuilder.toString(userInfo));
    }

    /**
     * 关于TCP连接的关闭
     * -----------------------------------------------------------------------------------
     * 无论在客户端还是服务端,IoSession都用于表示底层的一个TCP连接
     * 不过,无论Server端还是Client端的IoSession调用close()之后,TCP连接虽然显示关闭,但主线程仍在运行,即JVM并未退出
     * 这是因为IoSession的close()仅仅是关闭了TCP的连接通道,并没有关闭Server端和Client端的程序
     * 此时需要调用IoService.dispose()停止Server端和Client端
     * -----------------------------------------------------------------------------------
     */
    @Override
    public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
        System.out.println("通信异常-->["+session.getRemoteAddress()+"]-->["+cause.getMessage()+"]...连接即将关闭...");
        //关闭IoSession,该操作是异步的(true表示立即关闭,false表示所有写操作都flush后再关闭)
        //Mina-2.0.16已经不推荐直接调用clost(),推荐使用closeNow()和flushAndClose()替代
        session.close(false);
        //IoSession.IoService getService()用于返回与当前会话对象关联的IoService实例
        session.getService().dispose();
    }
}

控制台输出

这是客户端的控制台输出

Mina Client is startup
sessionCreated is invoked....
13:34:22.922 [NioProcessor-2] DEBUG org.apache.mina.filter.codec.ProtocolCodecFilter - Processing a MESSAGE_RECEIVED for session 1
收到服务机的信息-->com.jadyer.demo.mina.model.UserInfo@26e2473b[name=查文斌==>>是个神秘的人,age=22]

这是服务端的控制台输出

MinaServer is startup, and it`s listing on := 9876
13:34:22.851 [NioProcessor-2] INFO org.apache.mina.filter.logging.LoggingFilter - CREATED
13:34:22.857 [NioProcessor-2] INFO org.apache.mina.filter.logging.LoggingFilter - OPENED
InComing Client:/127.0.0.1:52102
13:34:22.873 [NioProcessor-2] INFO org.apache.mina.filter.logging.LoggingFilter - RECEIVED: HeapBuffer[pos=0 lim=66 cap=2048: 00 00 00 3E AC ED 00 05 73 72 01 00 23 63 6F 6D...]
13:34:22.876 [NioProcessor-2] DEBUG org.apache.mina.filter.codec.ProtocolCodecFilter - Processing a MESSAGE_RECEIVED for session 1
收到客户机的信息-->com.jadyer.demo.mina.model.UserInfo@369a4df3[name=查文斌,age=99]
13:34:22.918 [NioProcessor-2] INFO org.apache.mina.filter.logging.LoggingFilter - SENT: com.jadyer.demo.mina.model.UserInfo@56ee1273
13:34:32.918 [NioProcessor-2] INFO org.apache.mina.filter.logging.LoggingFilter - IDLE
13:34:42.919 [NioProcessor-2] INFO org.apache.mina.filter.logging.LoggingFilter - IDLE
13:34:52.919 [NioProcessor-2] INFO org.apache.mina.filter.logging.LoggingFilter - IDLE
13:35:02.920 [NioProcessor-2] INFO org.apache.mina.filter.logging.LoggingFilter - IDLE
13:35:12.920 [NioProcessor-2] INFO org.apache.mina.filter.logging.LoggingFilter - IDLE

Content