Mina框架剖析

简介: 最近使用Mina开发一个Java的NIO服务端程序,因此也特意学习了Apache的这个Mina框架。首先,Mina是个什么东西?看下官方网站(http://mina.apache.org/)对它的解释:Apache的Mina(Multipurpose Infrastructure Networked Applications)是一个网络应用框架,可以帮助用户开发高性能和高扩展性的网络应用程序;它提供了一个抽象的、事件驱动的异步API,使Java NIO在各种传输协议(如TCP/IP,UDP/IP协议等)下快速高效开发。

一. Mina入门
先用Mina做一个简单的应用程序。
第一步.下载使用的Jar包
a. 登录http://mina.apache.org/downloads.html 下载 mina2.0.1.zip,解压获得mina-core-2.0.0-M1.jar
b. 登录 http://www.slf4j.org/download.html 下载slf4j1.5.2.zip,解压获得slf4j-api-1.5.2.jar 与 slf4j-log4j12-1.5.2.jar
c. 添加Log4j的jar包,注意如果使用slf4j-log4j12-XXX.jar,就需要添加log4j1.2.X。我这里使用的是log4j-1.2.14.jar (Logger配置详情参见
http://mina.apache.org/first-steps.html
OK,4个jar都完备了。

第二步.工程创建配置
创建一个Java Project(默认使用UTF-8编码格式),添加log4j.properties
log4j.rootLogger=DEBUG,MINA,file

log4j.appender.MINA=org.apache.log4j.ConsoleAppender

log4j.appender.MINA.layout=org.apache.log4j.PatternLayout
log4j.appender.MINA.layout.ConversionPattern=%d{yyyy-MM-dd HH\:mm\:ss,SSS} %-5p %c{1} %x - %m%n

log4j.appender.file=org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/minademos.log
log4j.appender.file.MaxFileSize=5120KB
log4j.appender.file.MaxBackupIndex=10
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=VAMS %p | %m | [%t] %C.%M(%L)%n

第三步.服务端程序
创建一个简单的服务端程序:(服务端绑定3005端口)
public class Demo1Server {

private static Logger logger = Logger.getLogger(Demo1Server.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 TextLineCodecFactory(Charset
                        .forName("UTF-8"),
                        LineDelimiter.WINDOWS.getValue(),
                        LineDelimiter.WINDOWS.getValue())));
        // 设置读取数据的缓冲区大小
        acceptor.getSessionConfig().setReadBufferSize(2048);
        // 读写通道10秒内无操作进入空闲状态
        acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10);
        // 绑定逻辑处理器
        acceptor.setHandler(new Demo1ServerHandler());
        // 绑定端口
        acceptor.bind(new InetSocketAddress(PORT));
        logger.info("服务端启动成功...     端口号为:" + PORT);
    } catch (Exception e) {
        logger.error("服务端启动异常....", e);
        e.printStackTrace();
    }
}

}
无需解释,大家看上面的注释也许就了解一二了;
注意:创建服务端最主要的就是绑定服务端的消息编码解码过滤器和业务逻辑处理器;
什么是编码与解码哪?大家知道,网络传输的数据都是二进制数据,而我们的程序不可能直接去操作二进制数据;这时候我们就需要来把接收到的字节数组转换为字符串,当然完全可以转换为任何一个java基本数据类型或对象,这就是解码!而编码恰好相反,就是把要传输的字符串转换为字节!
比如上面使用的Mina自带的根据文本换行符编解码的TextLineCodec过滤器------ 指定参数为根据windows的换行符编解码,遇到客户端发送来的消息,看到windows换行符(\r\n)就认为是一个消息了,而发送给客户端的消息,都会在消息末尾添加上\r\n文本换行符;
业务逻辑处理器是Demo1ServerHandler---看它的具体实现:
public class Demo1ServerHandler extends IoHandlerAdapter {

public static Logger logger = Logger.getLogger(Demo1ServerHandler.class);

@Override
public void sessionCreated(IoSession session) throws Exception {
    logger.info("服务端与客户端创建连接...");
}

@Override
public void sessionOpened(IoSession session) throws Exception {
    logger.info("服务端与客户端连接打开...");
}

@Override
public void messageReceived(IoSession session, Object message)
        throws Exception {
    String msg = message.toString();
    logger.info("服务端接收到的数据为:" + msg);
    if ("bye".equals(msg)) { // 服务端断开连接的条件
        session.close();
    }
    Date date = new Date();
    session.write(date);
}

@Override
public void messageSent(IoSession session, Object message) throws Exception {
    logger.info("服务端发送信息成功...");
}

@Override
public void sessionClosed(IoSession session) throws Exception {

}

@Override
public void sessionIdle(IoSession session, IdleStatus status)
        throws Exception {
    logger.info("服务端进入空闲状态...");
}

@Override
public void exceptionCaught(IoSession session, Throwable cause)
        throws Exception {
    logger.error("服务端发送异常...", cause);
}

}
自定义的业务逻辑处理器继承了IoHandlerAdapter类,它默认覆盖了父类的7个方法,其实我们最关心最常用的只有一个方法:messageReceived() ---- 服务端接收到一个消息后进行业务处理的方法;看代码:
@Override

public void messageReceived(IoSession session, Object message)
        throws Exception {
    String msg = message.toString();
    logger.info("服务端接收到的数据为:" + msg);
    if ("bye".equals(msg)) { // 服务端断开连接的条件
        session.close();
    }
    Date date = new Date();
    session.write(date);
}

接受到客户端信息,并返回给客户端一个日期;如果客户端传递的消息为“bye”,就是客户端告诉服务端,可以终止通话了,关闭与客户端的连接。

使用命令行的telnet来测试下服务端程序!
a. 启动服务端程序;

b. Windows下开始菜单,运行,输入cmd,回车;

c. 输入:telnet 127.0.0.1 3005 回车

d. 连接成功后,服务端程序的后台会打印如下信息:这个就是业务逻辑逻辑处理器打印的:
e. telnet中随便输入一个字符串,回车;则可以看到返回的日期;

f. 输入bye,回车,提示服务端断开连接
如果需要重新测试,则需要再次重复C的步骤;

注意:
不要输入中文字符,windows不会把中文字符用utf-8编码再发送给服务端的;

第四步.客户端程序
Mina能做服务端程序,自然也可以做客户端程度啦。最重要的是,客户端程序和服务端程序写法基本一致,很简单的。

客户端代码:

public class MinaClient01 {

private static Logger logger = Logger.getLogger(MinaClient01.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 TextLineCodecFactory(Charset
                    .forName("UTF-8"), LineDelimiter.WINDOWS.getValue(),
                    LineDelimiter.WINDOWS.getValue())));
    // 添加业务逻辑处理器类
    connector.setHandler(new Demo1ClientHandler());
    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();
}

}
和服务端代码极其相似,不同的是服务端是创建NioSocketAcceptor对象,而客户端是创建NioSocketConnector对象;同样需要添加编码解码过滤器和业务逻辑过滤器;
业务逻辑过滤器代码:
public class Demo1ClientHandler extends IoHandlerAdapter {

private static Logger logger = Logger.getLogger(Demo1ClientHandler.class);

@Override
public void messageReceived(IoSession session, Object message)
        throws Exception {
    String msg = message.toString();
    logger.info("客户端接收到的信息为:" + msg);
}

@Override
public void exceptionCaught(IoSession session, Throwable cause)
        throws Exception {
    logger.error("客户端发生异常...", cause);
}

}
它和服务端的业务逻辑处理类一样,继承了IoHandlerAdapter类,因此同样可以覆盖父类的7个方法,同样最关心messageReceived方法,这里的处理是接收打印了服务端返回的信息;另一个覆盖的方法是异常信息捕获的方法;
测试服务端与客户端程序!
a. 启动服务端,然后再启动客户端(客户端发送的消息是“我爱你mina”)
b. 服务端接收消息并处理成功;

客户端接收响应结果

第五步.长连接VS短连接
此时,查看windows的任务管理器,会发现:当前操作系统中启动了3个java进程(注意其中一个进程是myEclipse的)。

我们知道,java应用程序的入口是main()方法,启动一个main()方法相当于开始运行一个java应用程序,此时会运行一个Java虚拟机,操作系统中会启动一个进程,就是刚刚看到的“javaw.exe”。也就是每启动一个Java应用程序就是多出一个Java进程。因为启动了Mina服务端和客户端2个服务端程序,所有其他2个进程的出现。
测试一下:再次启动一个客户端程序,查看任务管理器,会发现进程又多出一个,这个是刚刚启动的客户端进程,它和前一个客户端进程一直存在。这就是一个典型的长连接。

长连接的现象在网络中非常普遍,比如我们的QQ客户端程序,登录成功后与腾讯的服务器建立的就是长连接;除非主动关闭掉QQ客户端,或者是QQ服务端挂了,才会断开连接;看我们的服务端程序,就有关闭连接的条件:如果客户端发送信息“bye”,服务端就会主动断开连接!
@Override

public void messageReceived(IoSession session, Object message)
        throws Exception {
    String msg = message.toString();
    logger.info("服务端接收到的数据为:" + msg);
    if ("bye".equals(msg)) { // 服务端断开连接的条件
        session.close();
    }
    Date date = new Date();
    session.write(date);
}

与长连接相对应的是短连接,比如常说的请求/响应模式(HTTP协议就是典型的请求/响应模式)-----客户端向服务端发送一个请求,建立连接后,服务端处理并响应成功,此时就主动断开连接了!
短连接是一个简单而有效的处理方式,也是应用最广的。问题是哪一方先断开连接呢?可以在服务端,也可以在客户端,但是提倡在服务端主动断开;
Mina的服务端业务逻辑处理类中有一个方法messageSent,他是在服务端发送信息成功后调用的:
@Override

public void messageSent(IoSession session, Object message) throws Exception {
    logger.info("服务端发送信息成功...");
}

修改后为
@Override

public void messageSent(IoSession session, Object message) throws Exception {
    session.close(); //发送成功后主动断开与客户端的连接

logger.info("服务端发送信息成功...");

}

这时候客户端与服务端就是典型的短连接了;再次测试,会发现客户端发送请求,接收成功后就自动关闭了,进程只剩下服务端了!

到此为止,我们已经可以运行一个完整的基于TCP/IP协议的应用程序啦!

总结:
服务端程序或客户端程序创建过程:
创建连接---添加消息过滤器(编码解码等)——>添加业务处理

目录
相关文章
|
7月前
|
设计模式 存储 算法
协议解析必用的责任链模式
协议解析必用的责任链模式
64 0
|
4月前
|
Java 应用服务中间件 Linux
(九)Java网络编程无冕之王-这回把大名鼎鼎的Netty框架一网打尽!
现如今的开发环境中,分布式/微服务架构大行其道,而分布式/微服务的根基在于网络编程,而Netty恰恰是Java网络编程领域的无冕之王。Netty这个框架相信大家定然听说过,其在Java网络编程中的地位,好比JavaEE中的Spring。
176 3
|
6月前
|
Java
揭秘Java多态:为何同一消息,对象们却各有“心思”?
【6月更文挑战第17天】Java中的多态性让不同对象对同一方法有独特响应。以动物园为例,抽象类`Animal`定义`makeSound()`,子类如`Tiger`, `Lion`, `Monkey`继承并重写该方法。通过`Animal`引用调用,实际执行子类实现,展示动态绑定的威力。多态提升代码灵活性,支持扩展而无需改动原有代码,体现面向对象的核心思想。
31 2
|
弹性计算 Java Unix
搭稳Netty开发的地基,用漫画帮你分清同步异步阻塞非阻塞
Netty Netty是一款非常优秀的网络编程框架,是对NIO的二次封装,本文将重点剖析Netty客户端的启动流程,深入底层了解如何使用NIO编程客户端。 Linux网络编程5种IO模型 根据UNIX网络编程对于IO模型的分类,UNIX提供了5种IO模型,分别是 阻塞IO 、 非阻塞IO、 IO复用 、 信号驱动IO 、 异步IO 。这几种IO模型在《UNIX网络编程》中有详解,这里作者只简单介绍,帮助大家回忆一下这几种模型。 对于Linux来说,所有的操作都是基于文件的,也就是我们非常熟悉的fd,在缺省的情况下,基于文件的操作都是 阻塞的 。下面就通过系统调用 recvfrom 来回顾下
112 0
|
7月前
|
网络协议 Java 容器
《跟闪电侠学Netty》阅读笔记 - ChannelHandler 生命周期
《跟闪电侠学Netty》阅读笔记 - ChannelHandler 生命周期
83 0
《跟闪电侠学Netty》阅读笔记 - ChannelHandler 生命周期
|
设计模式 C++
2023-7-11-第十六式职责链模式
2023-7-11-第十六式职责链模式
70 0
|
存储 缓存 Java
线程池之刨根问底
线程池之刨根问底
127 0
线程池之刨根问底
|
Android开发 UED 容器
再谈事件分发
再谈事件分发
120 0
|
编解码
Netty基础招式——ChannelHandler的最佳实践(二)
Netty基础招式——ChannelHandler的最佳实践(二)
1454 0
Netty基础招式——ChannelHandler的最佳实践(二)