最近使用Mina开发一个Java的NIO服务端程序,因此也特意学习了Apache的这个Mina框架。
首先,Mina是个什么东西?看下官方网站(http://mina.apache.org/)对它的解释:Apache的Mina(Multipurpose Infrastructure Networked Applications)是一个网络应用框架,可以帮助用户开发高性能和高扩展性的网络应用程序;它提供了一个抽象的、事件驱动的异步API,使Java NIO在各种传输协议(如TCP/IP,UDP/IP协议等)下快速高效开发。
Apache Mina也称为:
a.NIO框架
b.客户端/服务端框架(典型的C/S架构)
c.网络套接字(networking socket)类库
d.事件驱动的异步API(注意:在JDK7中也新增了异步API)
总之,我们简单理解它是一个封装底层IO操作,提供高级操作API的通讯框架!
一.Mina入门
先用Mina做一个简单的应用程序。
1.下载使用的Jar包
a. 登录http://mina.apache.org/下载 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和slf配置详情参见http://mina.apache.org/first-steps.html)
2.工程创建配置
创建一个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][%d] %p | %m | [%t] %C.%M(%L)%n
3.服务端程序
创建一个简单的服务端程序:(服务端绑定3005端口)
package com.bijian.study.mina.server; import java.net.InetSocketAddress; import java.nio.charset.Charset; import org.apache.log4j.Logger; 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.textline.LineDelimiter; import org.apache.mina.filter.codec.textline.TextLineCodecFactory; import org.apache.mina.transport.socket.nio.NioSocketAcceptor; import com.bijian.study.mina.handler.Demo01ServerHandler; public class Demo01Server { private static Logger logger = Logger.getLogger(Demo01Server.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 Demo01ServerHandler()); // 添加业务处理 // 绑定端口 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)文本换行符;
业务逻辑处理器是Demo01ServerHandler.java,看它的具体实现:
package com.bijian.study.mina.handler; import java.util.Date; import org.apache.log4j.Logger; import org.apache.mina.core.service.IoHandlerAdapter; import org.apache.mina.core.session.IdleStatus; import org.apache.mina.core.session.IoSession; public class Demo01ServerHandler extends IoHandlerAdapter { public static Logger logger = Logger.getLogger(Demo01ServerHandler.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 { //session.close(); 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”,就是客户端告诉服务端,可以终止通话了,关闭与客户端的连接。
4.使用telnet命令来测试服务端
使用我们最常用的telnet命令来测试服务端程序。具体步骤如下:
a.启动服务端程序;
2015-12-27 10:33:24,508 INFO Demo01Server - 服务端启动成功... 端口号为:3005
b.Windows下开始菜单,运行,输入cmd,回车,进入DOS界面,输入:telnet 127.0.0.1 3005回车,连接成功后,服务端程序后台打印如下信息,这个就是业务逻辑处理器打印的。
2015-08-06 23:48:20,170 INFO Demo1ServerHandler - 服务端与客户端创建连接... 2015-08-06 23:48:20,170 INFO Demo1ServerHandler - 服务端与客户端连接打开...
c.telnet中随便输入一个字符串,回车;则可以看到返回的日期,输入bye回车,提示服务端断开连接。
注意: 不要输入中文字符,windows不会把中文字符用utf-8编码再发送给服务端的;
这个就是用Mina实现的服务端程序啦。功能很简单:服务端一直监听3005端口,如果有客户端连接上服务端并发送信息,服务端解析信息(以文本换行符为每条信息的结束符),并返回给服务端一个日期时间。 Mina的底层通信无疑是用socket实现的,它封装后提供给我们一个简单易用的接口。其实基于Http协议的通信也是如此,我们完全可以用mina写一个类似Servlet的基类,来响应各种http请求。上面的第一个例子可以稍加演示:
a. 启动服务端程序;
修改Demo01ServerHandler.java的messageSent方法,增加session.close()语句
@Override public void messageSent(IoSession session, Object message) throws Exception { session.close(); logger.info("服务端发送信息成功..."); }启动服务端程序
2015-12-27 10:36:22,682 INFO Demo01Server - 服务端启动成功... 端口号为:3005
b. 打开浏览器,输入:http://127.0.0.1:3005/回车;
c. 服务端响应如下
2015-12-27 10:39:04,099 INFO Demo01Server - 服务端启动成功... 端口号为:3005 2015-12-27 10:39:10,338 INFO Demo01ServerHandler - 服务端与客户端创建连接... 2015-12-27 10:39:10,338 INFO Demo01ServerHandler - 服务端与客户端创建连接... 2015-12-27 10:39:10,338 INFO Demo01ServerHandler - 服务端与客户端连接打开... 2015-12-27 10:39:10,338 INFO Demo01ServerHandler - 服务端与客户端连接打开... 2015-12-27 10:39:10,338 DEBUG ProtocolCodecFilter - Processing a MESSAGE_RECEIVED for session 1 2015-12-27 10:39:10,338 INFO Demo01ServerHandler - 服务端接收到的数据为:GET / HTTP/1.1 2015-12-27 10:39:10,358 INFO Demo01ServerHandler - 服务端接收到的数据为:Host: 127.0.0.1:3005 2015-12-27 10:39:10,359 INFO Demo01ServerHandler - 服务端接收到的数据为:Connection: keep-alive 2015-12-27 10:39:10,359 INFO Demo01ServerHandler - 服务端接收到的数据为:Cache-Control: max-age=0 2015-12-27 10:39:10,359 INFO Demo01ServerHandler - 服务端接收到的数据为:Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 2015-12-27 10:39:10,360 INFO Demo01ServerHandler - 服务端接收到的数据为:Upgrade-Insecure-Requests: 1 2015-12-27 10:39:10,360 INFO Demo01ServerHandler - 服务端接收到的数据为:User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36 2015-12-27 10:39:10,360 INFO Demo01ServerHandler - 服务端接收到的数据为:Accept-Encoding: gzip, deflate, sdch 2015-12-27 10:39:10,361 INFO Demo01ServerHandler - 服务端接收到的数据为:Accept-Language: zh-CN,zh;q=0.8 2015-12-27 10:39:10,361 INFO Demo01ServerHandler - 服务端接收到的数据为: 2015-12-27 10:39:10,364 INFO Demo01ServerHandler - 服务端发送信息成功... 2015-12-27 10:39:10,366 ERROR Demo01ServerHandler - 服务端发送异常... org.apache.mina.core.write.WriteToClosedSessionException at org.apache.mina.core.polling.AbstractPollingIoProcessor.clearWriteRequestQueue(AbstractPollingIoProcessor.java:618) at org.apache.mina.core.polling.AbstractPollingIoProcessor.removeNow(AbstractPollingIoProcessor.java:569) at org.apache.mina.core.polling.AbstractPollingIoProcessor.removeSessions(AbstractPollingIoProcessor.java:539) at org.apache.mina.core.polling.AbstractPollingIoProcessor.access$600(AbstractPollingIoProcessor.java:66) at org.apache.mina.core.polling.AbstractPollingIoProcessor$Processor.run(AbstractPollingIoProcessor.java:1086) at org.apache.mina.util.NamePreservingRunnable.run(NamePreservingRunnable.java:64) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745) 2015-12-27 10:39:10,769 DEBUG ProtocolCodecFilter - Processing a MESSAGE_RECEIVED for session 2 2015-12-27 10:39:10,770 INFO Demo01ServerHandler - 服务端接收到的数据为:GET /favicon.ico HTTP/1.1 2015-12-27 10:39:10,771 INFO Demo01ServerHandler - 服务端接收到的数据为:Host: 127.0.0.1:3005 2015-12-27 10:39:10,771 INFO Demo01ServerHandler - 服务端接收到的数据为:Connection: keep-alive 2015-12-27 10:39:10,771 INFO Demo01ServerHandler - 服务端接收到的数据为:User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36 2015-12-27 10:39:10,772 INFO Demo01ServerHandler - 服务端接收到的数据为:Accept: */* 2015-12-27 10:39:10,772 INFO Demo01ServerHandler - 服务端接收到的数据为:Referer: http://127.0.0.1:3005/ 2015-12-27 10:39:10,772 INFO Demo01ServerHandler - 服务端接收到的数据为:Accept-Encoding: gzip, deflate, sdch 2015-12-27 10:39:10,773 INFO Demo01ServerHandler - 服务端接收到的数据为:Accept-Language: zh-CN,zh;q=0.8 2015-12-27 10:39:10,773 INFO Demo01ServerHandler - 服务端接收到的数据为: 2015-12-27 10:39:10,773 INFO Demo01ServerHandler - 服务端发送信息成功... 2015-12-27 10:39:10,774 ERROR Demo01ServerHandler - 服务端发送异常... org.apache.mina.core.write.WriteToClosedSessionException at org.apache.mina.core.polling.AbstractPollingIoProcessor.clearWriteRequestQueue(AbstractPollingIoProcessor.java:618) at org.apache.mina.core.polling.AbstractPollingIoProcessor.removeNow(AbstractPollingIoProcessor.java:569) at org.apache.mina.core.polling.AbstractPollingIoProcessor.removeSessions(AbstractPollingIoProcessor.java:539) at org.apache.mina.core.polling.AbstractPollingIoProcessor.access$600(AbstractPollingIoProcessor.java:66) at org.apache.mina.core.polling.AbstractPollingIoProcessor$Processor.run(AbstractPollingIoProcessor.java:1086) at org.apache.mina.util.NamePreservingRunnable.run(NamePreservingRunnable.java:64) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745)
d. 浏览器显示信息如下:
仔细看服务端的解析代码会发现,服务器根据Windows换行符(\r\n)把http请求的头文件给解析啦,并且正确的返回给服务器一个日期字符串。如果我们能根据Http协议的解析方式在服务端的业务逻辑中正确解析请求,并且添加上线程池多线程处理请求,这就是一个基于Http请求的解析容器啦!
Mina够强大吧!
5.客户端程序
Mina能做服务端程序,自然也可以做客户端程度啦。而且可喜的是,客户端程序和服务端程序写法基本一致,很简单的。
客户端代码:
package com.bijian.study.mina.client; import java.net.InetSocketAddress; import java.nio.charset.Charset; import org.apache.log4j.Logger; import org.apache.mina.core.future.ConnectFuture; import org.apache.mina.core.service.IoConnector; import org.apache.mina.core.session.IoSession; import org.apache.mina.filter.codec.ProtocolCodecFilter; import org.apache.mina.filter.codec.textline.LineDelimiter; import org.apache.mina.filter.codec.textline.TextLineCodecFactory; import org.apache.mina.transport.socket.nio.NioSocketConnector; import com.bijian.study.mina.handler.DemoClientHandler; public class Demo01Client { private static Logger logger = Logger.getLogger(Demo01Client.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 DemoClientHandler()); 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对象;同样需要添加编码解码过滤器和业务逻辑过滤器;
业务逻辑过滤器代码:
package com.bijian.study.mina.handler; import org.apache.log4j.Logger; import org.apache.mina.core.service.IoHandlerAdapter; import org.apache.mina.core.session.IoSession; public class DemoClientHandler extends IoHandlerAdapter { private static Logger logger = Logger.getLogger(DemoClientHandler.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. 服务端接收消息并处理成功;
2015-12-27 10:45:36,883 INFO Demo01Server - 服务端启动成功... 端口号为:3005 2015-12-27 10:45:43,073 INFO Demo01ServerHandler - 服务端与客户端创建连接... 2015-12-27 10:45:43,073 INFO Demo01ServerHandler - 服务端与客户端连接打开... 2015-12-27 10:45:43,073 DEBUG ProtocolCodecFilter - Processing a MESSAGE_RECEIVED for session 1 2015-12-27 10:45:43,073 INFO Demo01ServerHandler - 服务端接收到的数据为:我爱你mina 2015-12-27 10:45:43,088 INFO Demo01ServerHandler - 服务端发送信息成功... 2015-12-27 10:45:53,135 INFO Demo01ServerHandler - 服务端进入空闲状态...
客户端接收响应结果
2015-12-27 10:45:43,088 DEBUG ProtocolCodecFilter - Processing a MESSAGE_RECEIVED for session 1 2015-12-27 10:45:43,088 INFO DemoClientHandler - 客户端接收到的信息为:Sun Dec 27 10:45:43 CST 2015
6.长连接VS短连接
此时,查看windows的任务管理器,会发现:当前操作系统中启动了两个java进程。
我们知道,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是Java NIO实现的应用框架,更倾向于短连接的服务;问题是哪一方先断开连接呢?可以在服务端,也可以在客户端,但是提倡在服务端主动断开; 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协议的应用程序啦!
总之,服务端程序或客户端程序创建过程: 创建连接->添加消息过滤器(编码解码等)->添加业务处理。
二.入门程序实例二
完成了第一个简单的应用程序,是不很有成就感呀!现在就用一个具体应用案例来热身。
案例:
模拟移动公司收发短信的功能!
短信发送和接收是最广泛的手机操作功能。收发短信有两个对象参与:手机和移动短信服务器(简称服务器);发送短信时候,手机是客户端,服务器是服务端,手机主动向服务器发送请求;接收短信时候,手机是服务端,服务器是客户端,服务器主动向手机发送请求。手机和服务器互为服务端和客户端。 我们先模拟一个手机向服务器发送短信的例子!
实现:
1. 短信有最少有3部分组成
发送人手机号码,接收人手机号码,短信内容;(其他如发送时间/接收时间等等,作为演示就暂时不考虑啦)
发送的内容以字符串的形式发送,格式如下: 发送人号码;接收人号码;短信内容(文本换行符)
我们可以仍然使用Mina自带的根据文本换行符编解码的方式接收和发送信息。
2. 模拟手机客户端发短信
仔细看看客户端代码,基本和第一个例子一模一样呀,就是需要制定黄色标识部分:创建客户端,指定过滤器,指定业务逻辑处理器,写发送的信息。
package com.bijian.study.mina.client; import java.net.InetSocketAddress; import org.apache.log4j.Logger; import org.apache.mina.core.future.ConnectFuture; import org.apache.mina.core.service.IoConnector; import org.apache.mina.core.session.IoSession; import org.apache.mina.filter.codec.ProtocolCodecFilter; import org.apache.mina.filter.codec.serialization.ObjectSerializationCodecFactory; import org.apache.mina.transport.socket.nio.NioSocketConnector; import com.bijian.study.mina.handler.DemoClientHandler; /* * 模拟手机发短信 */ public class Demo02Client { private static Logger logger = Logger.getLogger(Demo02Client.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 ObjectSerializationCodecFactory())); // 添加业务逻辑处理器类 connector.setHandler(new DemoClientHandler()); IoSession session = null; try { ConnectFuture future = connector.connect(new InetSocketAddress( HOST, PORT));// 创建连接 future.awaitUninterruptibly();// 等待连接创建完成 session = future.getSession();// 获得session String sendPhone="13681803609"; // 当前发送人的手机号码 String receivePhone="13721427169"; // 接收人手机号码 String message="测试发送短信,这个是短信信息哦,当然长度是有限制的哦...."; String msg=sendPhone+";"+receivePhone+";"+message; session.write(msg);// 发送给移动服务端 } catch (Exception e) { logger.error("客户端链接异常...", e); } session.getCloseFuture().awaitUninterruptibly();// 等待连接断开 connector.dispose(); } }
业务逻辑处理类,这个是重点!业务很简单,如果服务器接收信息成功,就返回接收信息成功的信息。在今后用mina写应用程序时,它是重中之一。
package com.bijian.study.mina.handler; import org.apache.log4j.Logger; import org.apache.mina.core.service.IoHandlerAdapter; import org.apache.mina.core.session.IoSession; public class DemoClientHandler extends IoHandlerAdapter { private static Logger logger = Logger.getLogger(DemoClientHandler.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); } }
3. 模拟移动短信服务器收短信
服务端没啥,和第一个例子一样,只需要指定三个地方:创建服务端,指定过滤器,指定业务逻辑处理器。
package com.bijian.study.mina.server; import java.net.InetSocketAddress; import org.apache.log4j.Logger; import org.apache.mina.core.service.IoAcceptor; import org.apache.mina.core.session.IdleStatus; import org.apache.mina.core.session.IoSessionConfig; import org.apache.mina.filter.codec.ProtocolCodecFilter; import org.apache.mina.filter.codec.serialization.ObjectSerializationCodecFactory; import org.apache.mina.transport.socket.nio.NioSocketAcceptor; import com.bijian.study.mina.handler.Demo02ServerHandler; public class Demo02Server { private static Logger logger = Logger.getLogger(Demo02Server.class); private static int PORT = 3005; public static void main(String[] args) { IoAcceptor acceptor = null; try { // 创建一个非阻塞的server端的Socket acceptor = new NioSocketAcceptor(); // 直接发送对象 acceptor.getFilterChain().addLast( "codec", new ProtocolCodecFilter( new ObjectSerializationCodecFactory())); // 获得IoSessionConfig对象 IoSessionConfig cfg = acceptor.getSessionConfig(); // 读写通道10秒内无操作进入空闲状态 cfg.setIdleTime(IdleStatus.BOTH_IDLE, 100); // 绑定逻辑处理器 acceptor.setHandler(new Demo02ServerHandler()); // 绑定端口 acceptor.bind(new InetSocketAddress(PORT)); logger.info("服务端启动成功... 端口号为:" + PORT); } catch (Exception e) { logger.error("服务端启动异常....", e); e.printStackTrace(); } } }
业务逻辑处理类,今后写代码的重点之一。逻辑很简单:解析客户端发来的短信信息,任何做处理,并告诉手机发送成功啦!
package com.bijian.study.mina.handler; import org.apache.log4j.Logger; import org.apache.mina.core.service.IoHandlerAdapter; import org.apache.mina.core.session.IoSession; public class Demo02ServerHandler extends IoHandlerAdapter { public static Logger logger = Logger.getLogger(Demo02ServerHandler.class); @Override public void messageReceived(IoSession session, Object message) throws Exception { String phoneMes = message.toString(); String[] megs=phoneMes.split(";"); String sendPhone = megs[0]; String receivePhone = megs[1]; String mes = megs[2]; logger.info("发送人手机号码:" + sendPhone); logger.info("接受人手机号码:" + receivePhone); logger.info("发送信息:" + mes); // 短信信息存入移动服务端数据库或者写入手机短信转发队列 // ............ session.write("发送成功!"); // 告诉手机发送信息成功啦 } @Override public void messageSent(IoSession session, Object message) throws Exception { session.close(); } @Override public void exceptionCaught(IoSession session, Throwable cause) throws Exception { logger.error("服务端发送异常...", cause); } }
4.运行测试
先启动服务端,再启动客户端,测试成功!
服务端输出:
2015-12-27 10:51:55,780 INFO Demo02Server - 服务端启动成功... 端口号为:3005 2015-12-27 10:52:05,012 DEBUG ProtocolCodecFilter - Processing a MESSAGE_RECEIVED for session 1 2015-12-27 10:52:05,015 INFO Demo02ServerHandler - 发送人手机号码:13681803609 2015-12-27 10:52:05,016 INFO Demo02ServerHandler - 接受人手机号码:13721427169 2015-12-27 10:52:05,016 INFO Demo02ServerHandler - 发送信息:测试发送短信,这个是短信信息哦,当然长度是有限制的哦....
客户端输出:
2015-12-27 10:52:05,025 DEBUG ProtocolCodecFilter - Processing a MESSAGE_RECEIVED for session 1 2015-12-27 10:52:05,031 INFO DemoClientHandler - 客户端接收到的信息为:发送成功!
5.注意事项:
客户端发送信息:
String sendPhone="13681803609"; // 当前发送人的手机号码 String receivePhone="13721427169"; // 接收人手机号码 String message="测试发送短信,这个是短信信息哦,当然长度是有限制的哦...."; String msg=sendPhone+";"+receivePhone+";"+message; session.write(msg);// 发送给移动服务端
此时,调用session.write(msg)时,我们没有在短信的末尾加上文化换行符,也不需要添加文本换行符,这些工作是由我们指定的编解码器完成的,他会自动把发送的信息先转换为底层传输的二进制代码,然后再默认添加上文本换行符。
服务端接收信息:
@Override public void messageReceived(IoSession session, Object message) throws Exception { String phoneMes = message.toString(); String[] megs=phoneMes.split(";"); String sendPhone = megs[0]; String receivePhone = megs[1]; String mes = megs[2]; logger.info("发送人手机号码:" + sendPhone); logger.info("接受人手机号码:" + receivePhone); logger.info("发送信息:" + mes); // 短信信息存入移动服务端数据库或者写入手机短信转发队列 // ............ session.write("发送成功!"); // 告诉手机发送信息成功啦 }
此时,messageReceived()方法就是信息接收到时候调用的,它的参数Object message就是手机发送来的信息,不是底层的二进制代码了,而且末尾没有文本换行符了,已经是java字符串了,因为它被指定的解码器解码了。
最后,发送消息,session.write(msg)后,先编码再发送! 接收消息,messageReceived()前,会先解码的。
三.入门程序实例三
仍然研究上面的例子。手机发短信和服务端接收短信时,我们发送的是一个字符串,它是用“;”号隔开的三部分信息,需要在手机客户端拼装字符串,在服务端解析字符串;是否可以直接发送一个java对象呢?当然可以,而且Mina已经提供了这个编解码过滤器:ObjectSerializationCodecFactory()接口搞定一切!不需要你关心对象的序列号问题,它已经实现好啦!
1. 短信对象
把发送短信指定成一个java对象,这样就很有面向对象的操作啦。
package com.bijian.study.mina.dto; import java.io.Serializable; /* * 手机短信发送DTO */ public class PhoneMessageDto implements Serializable { private String sendPhone; // 发送人手机号 private String receivePhone; // 接收人手机号 private String message; // 短信信息 public String getSendPhone() { return sendPhone; } public void setSendPhone(String sendPhone) { this.sendPhone = sendPhone; } public String getReceivePhone() { return receivePhone; } public void setReceivePhone(String receivePhone) { this.receivePhone = receivePhone; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } }
2. 模拟手机客户端发短信
实在不想写客户端了,基本没变化
package com.bijian.study.mina.client; import java.net.InetSocketAddress; import org.apache.log4j.Logger; import org.apache.mina.core.future.ConnectFuture; import org.apache.mina.core.service.IoConnector; import org.apache.mina.core.session.IoSession; import org.apache.mina.filter.codec.ProtocolCodecFilter; import org.apache.mina.filter.codec.serialization.ObjectSerializationCodecFactory; import org.apache.mina.transport.socket.nio.NioSocketConnector; import com.bijian.study.mina.dto.PhoneMessageDto; import com.bijian.study.mina.handler.DemoClientHandler; /* * 模拟手机发短信 */ public class Demo03Client { private static Logger logger = Logger.getLogger(Demo03Client.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 ObjectSerializationCodecFactory())); // 添加业务逻辑处理器类 connector.setHandler(new DemoClientHandler()); IoSession session = null; try { ConnectFuture future = connector.connect(new InetSocketAddress( HOST, PORT));// 创建连接 future.awaitUninterruptibly();// 等待连接创建完成 session = future.getSession();// 获得session PhoneMessageDto sendMes = new PhoneMessageDto(); sendMes.setSendPhone("13681803609"); // 当前发送人的手机号码 sendMes.setReceivePhone("13721427169"); // 接收人手机号码 sendMes.setMessage("测试发送短信,这个是短信信息哦,当然长度是有限制的哦...."); session.write(sendMes);// 发送给移动服务端 } catch (Exception e) { logger.error("客户端链接异常...", e); } session.getCloseFuture().awaitUninterruptibly();// 等待连接断开 connector.dispose(); } }
客户端处理类没变化
3. 模拟移动短信服务器收短信
服务端也基本没啥变化
package com.bijian.study.mina.server; import java.net.InetSocketAddress; import org.apache.log4j.Logger; import org.apache.mina.core.service.IoAcceptor; import org.apache.mina.core.session.IdleStatus; import org.apache.mina.core.session.IoSessionConfig; import org.apache.mina.filter.codec.ProtocolCodecFilter; import org.apache.mina.filter.codec.serialization.ObjectSerializationCodecFactory; import org.apache.mina.transport.socket.nio.NioSocketAcceptor; import com.bijian.study.mina.handler.Demo03ServerHandler; public class Demo03Server { private static Logger logger = Logger.getLogger(Demo03Server.class); private static int PORT = 3005; public static void main(String[] args) { IoAcceptor acceptor = null; try { // 创建一个非阻塞的server端的Socket acceptor = new NioSocketAcceptor(); // 直接发送对象 acceptor.getFilterChain().addLast( "codec", new ProtocolCodecFilter( new ObjectSerializationCodecFactory())); // 获得IoSessionConfig对象 IoSessionConfig cfg = acceptor.getSessionConfig(); // 读写通道10秒内无操作进入空闲状态 cfg.setIdleTime(IdleStatus.BOTH_IDLE, 100); // 绑定逻辑处理器 acceptor.setHandler(new Demo03ServerHandler()); // 绑定端口 acceptor.bind(new InetSocketAddress(PORT)); logger.info("服务端启动成功... 端口号为:" + PORT); } catch (Exception e) { logger.error("服务端启动异常....", e); e.printStackTrace(); } } }
业务处理类,这个是重点,我们不需要解析字符串了,接收到的数据直接强制转换为对象就可以!
package com.bijian.study.mina.handler; import org.apache.log4j.Logger; import org.apache.mina.core.service.IoHandlerAdapter; import org.apache.mina.core.session.IoSession; import com.bijian.study.mina.dto.PhoneMessageDto; public class Demo03ServerHandler extends IoHandlerAdapter { public static Logger logger = Logger.getLogger(Demo03ServerHandler.class); @Override public void messageReceived(IoSession session, Object message) throws Exception { PhoneMessageDto phoneMes = (PhoneMessageDto) message; String sendPhone = phoneMes.getSendPhone(); String receivePhone = phoneMes.getReceivePhone(); String mes = phoneMes.getMessage(); logger.info("发送人手机号码:" + sendPhone); logger.info("接受人手机号码:" + receivePhone); logger.info("发送信息:" + mes); // 短信信息存入移动服务端数据库 // ............ session.write("发送成功!"); } @Override public void messageSent(IoSession session, Object message) throws Exception { session.close(); } @Override public void exceptionCaught(IoSession session, Throwable cause) throws Exception { logger.error("服务端发送异常...", cause); } }
4. 运行测试
啥都不说了,直接运行测试吧,成功的!
5.其他
服务端主动给手机发短信的demo就不写了,因为到此为止它太easy啦!
通过上面的练习,基本可以使用Mina开发小的应用程序啦。但是,如果服务端和客户端使用不同的语言实现(比如客户端用C写),则使用String或者Java对象传输数据是不可行的,必须自定义协议,以报文的形式发送二进制数据。接下来需要研究Mina的基础知识,需要多查看mina的官方教程和API-----它的资料非常丰富! 接下来我们将重点看IoFilter接口,它是Mina的精髓,最好能研究下它的 TextLineCodecFactory()对象的实现源码。
学习资料:http://wenku.baidu.com/link?url=VyKQnsn4b0BDJ8cQlLUu9cvpGz-Iou_499U4lJE9I0s5nPPY5kF5BDd8qo1yRMOiqsM8wxDPEL_S0koiFp8v5y36G9OGJydC2C12juo0bTW