首先使用MINA2的时候,你最起码的知道怎么下载jar包和在工程中引入哪些jar包。
附上下载地址:http://mirrors.cnnic.cn/apache/mina/mina/2.0.7/dist/apache-mina-2.0.7-bin.zip 和必须引入工程的jar包:mina-core-2.0.7.jar;slf4j-api-1.6.6.jar;slf4j-log4j12-1.7.5.jar,不多说直接上代码:
第一步:编写通信要用的编码器类、解码器类和编解码工厂类
1、字符编码:
/** * @Description: * * @Title: CharsetEncoder.java * @Package com.joyce.mina.code * @Copyright: Copyright (c) 2014 * * @author Comsys-LZP * @date 2014-3-19 上午11:48:53 * @version V2.0 */ package com.joyce.mina.code; import java.nio.ByteOrder; import java.nio.charset.Charset; import org.apache.log4j.Logger; import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.core.session.IoSession; import org.apache.mina.filter.codec.ProtocolEncoder; import org.apache.mina.filter.codec.ProtocolEncoderOutput; /** * @Description: 字符编码 * * @ClassName: CharsetEncoder * @Copyright: Copyright (c) 2014 * * @author Comsys-LZP * @date 2014-3-19 上午11:48:53 * @version V2.0 */ public class CharsetEncoder implements ProtocolEncoder { /** * Log4j日志 */ private static final Logger logger = Logger.getLogger(CharsetEncoder.class); private static final Charset charset = Charset.forName("UTF-8"); @Override public void dispose(IoSession session) throws Exception { logger.info("#####################dispose#########################"); } @Override public void encode(IoSession session, Object message, ProtocolEncoderOutput out) throws Exception { logger.debug("#####################encode#########################"); IoBuffer buff = IoBuffer.allocate(100).setAutoExpand(true); buff.order(ByteOrder.LITTLE_ENDIAN); String smsgbody = message.toString(); byte[] bytearr = smsgbody.getBytes(charset); buff.put(bytearr); // 为下一次读取数据做准备 buff.flip(); out.write(buff); } }
2、字符解码:
/** * @Description: * * @Title: CharsetDecoder.java * @Package com.joyce.mina.code * @Copyright: Copyright (c) 2014 * * @author Comsys-LZP * @date 2014-3-19 上午11:35:49 * @version V2.0 */ package com.joyce.mina.code; import java.nio.charset.Charset; import org.apache.log4j.Logger; import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.core.session.IoSession; import org.apache.mina.filter.codec.ProtocolDecoder; import org.apache.mina.filter.codec.ProtocolDecoderOutput; /** * @Description: 字符解码 * * @ClassName: CharsetDecoder * @Copyright: Copyright (c) 2014 * * @author Comsys-LZP * @date 2014-3-19 上午11:35:49 * @version V2.0 */ public class CharsetDecoder implements ProtocolDecoder { /** * Log4j日志 */ private final static Logger logger = Logger.getLogger(CharsetDecoder.class); private static final Charset charset = Charset.forName("UTF-8"); // 可变的IoBuffer数据缓冲区 private IoBuffer buff = IoBuffer.allocate(100).setAutoExpand(true); @Override public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception { logger.debug("#####################decode#########################"); // 如果有信息 if (in.hasRemaining()) { // 判断消息是否是结束符,不同平台的结束符也不一样; // windows换行符(\r\n)就认为是一个完整消息的结束符了; UNIX 是\n;MAC 是\r byte b = in.get(); if (b == '\n') { buff.flip(); byte[] bytes = new byte[buff.limit()]; buff.get(bytes); String message = new String(bytes, charset); buff = IoBuffer.allocate(100).setAutoExpand(true); // 如果结束了,就写入转码后的数据 out.write(message); } else { buff.put(b); } } } @Override public void dispose(IoSession session) throws Exception { logger.info("#####################dispose#########################"); logger.info(session.getCurrentWriteMessage()); } @Override public void finishDecode(IoSession session, ProtocolDecoderOutput out) throws Exception { logger.info("#####################完成解码#########################"); } }
上面的decode方法是解码方法,它主要是把读取到数据中的换行符去掉。因为在mina通信协议中以换行符为结束符,如果不定义结束符那么程序会在那里一直等待下一条发送的数据。
这里用到了IoBuffer,MiNa中传输的所有二进制信息都存放在IoBuffer中,IoBuffer是对Java NIO中ByteBuffer的封装(Mina2.0以前版本这个接口也是ByteBuffer),提供了更多操作二进制数据,对象的方法,并且存储空间可以自增长,用起来非常方便;简单理解,它就是个可变长度的byte字节数组!
1). static IoBuffer allocate(int capacity,boolean useDirectBuffer)
创建IoBuffer实例,第一个参数指定初始化容量,第二个参数指定使用直接缓冲区还是JAVA 内存堆的缓存区,默认为false。
2).IoBuffer setAutoExpand(boolean autoExpand)
这个方法设置IoBuffer 为自动扩展容量,也就是前面所说的长度可变,那么可以看出长度可变这个特性默认是不开启的。
3). IoBuffer flip()
limit=position, position=0,重置mask,为了读取做好准备,一般是结束buffer操作,将buffer写入输出流时调用;这个必须要调用,否则极有可能position!=limit,导致position后面没有数据;每次写入数据到输出流时,必须确保position=limit。
4). IoBuffer clear()与IoBuffer reset()
clear:limit=capacity , position=0,重置mark;它是不清空数据,但从头开始存放数据做准备---相当于覆盖老数据。
reset就是清空数据
5). int remaining()与boolean hasRemaining()
这两个方法一般是在调用了flip方法后使用的,remaining()是返回limt-position的值!hasRemaining()则是判断当前是否有数据,返回position < limit的boolean值!
3、编解码过滤工厂:
/** * @Description: * * @Title: CharsetCodecFactory.java * @Package com.joyce.mina.code.factory * @Copyright: Copyright (c) 2014 * * @author Comsys-LZP * @date 2014-3-19 上午11:34:14 * @version V2.0 */ package com.joyce.mina.code.factory; import org.apache.mina.core.session.IoSession; import org.apache.mina.filter.codec.ProtocolCodecFactory; import org.apache.mina.filter.codec.ProtocolDecoder; import org.apache.mina.filter.codec.ProtocolEncoder; import com.joyce.mina.code.CharsetDecoder; import com.joyce.mina.code.CharsetEncoder; /** * @Description: 字符编码、解码工厂类,编码过滤工厂 * * @ClassName: CharsetCodecFactory * @Copyright: Copyright (c) 2014 * * @author Comsys-LZP * @date 2014-3-19 上午11:34:14 * @version V2.0 */ public class CharsetCodecFactory implements ProtocolCodecFactory { @Override public ProtocolDecoder getDecoder(IoSession session) throws Exception { return new CharsetDecoder(); } @Override public ProtocolEncoder getEncoder(IoSession session) throws Exception { return new CharsetEncoder(); } }
第二步:编写IoHandler实现类代码
IoHander是Io读写的事件驱动类,这里的Io操作都会触发里面的事件。所有的业务逻辑都应当在这个类中完成,个人建议如果能封装出来的尽量封装出来,否则业务代码会很多很大!我这里就实现了封装
/** * @Description: * * @Title: ServerMessageReceived.java * @Package com.joyce.mina.message * @Copyright: Copyright (c) 2014 * * @author Comsys-LZP * @date 2014-3-21 上午08:54:13 * @version V2.0 */ package com.joyce.mina.message; import java.text.SimpleDateFormat; import java.util.Collection; import java.util.Date; import org.apache.log4j.Logger; import org.apache.mina.core.session.IoSession; /** * @Description: 消息处理 * * @ClassName: ServerMessageReceived * @Copyright: Copyright (c) 2014 * * @author Comsys-LZP * @date 2014-3-21 上午08:54:13 * @version V2.0 */ public class ServerMessageUtil { /** * Log4j日志对象 */ private static final Logger logger = Logger.getLogger(ServerMessageUtil.class); /** * @Description: 服务器接收信息处理 * * @param session * @param message * * @Title: ServerMessageReceived.java * @Copyright: Copyright (c) 2014 * * @author Comsys-LZP * @date 2014-3-21 上午08:55:53 * @version V2.0 */ public static void messageReceived(IoSession session, Object message){ String content = message.toString(); String datetime = ServerMessageUtil.getNowDate(); // 拿到所有的客户端Session Collection<IoSession> sessions = ServerMessageUtil.getAllClient(session); // 向所有客户端发送数据 if(content.trim().equals("1")){ logger.info("在线用户列表:"); session.write("在线用户列表:\r\n"); for (IoSession sess : sessions) { logger.info("Client" + sess.getId() + ":\t" + sess.getRemoteAddress()); session.write("Client" + sess.getId() + ":\t" + sess.getRemoteAddress() + "\r\n"); } } else if("2".equals(content.trim())){ logger.info("说明:"); StringBuffer sb = new StringBuffer(); sb.append("说明:"); sb.append("\r\n"); sb.append("1、使用@符号进行私聊(@127.0.0.1#你好!-- @ip#你好!)"); sb.append("\r\n"); sb.append("2、无@符号属于群聊"); sb.append("\r\n"); sb.append("3、新增quit和exit命令退出"); sb.append("\r\n"); session.write(sb); } else if("quit".equalsIgnoreCase(content.trim()) || "exit".equalsIgnoreCase(content.trim())){ session.close(true); } else if(content.trim().indexOf("@") != -1 && content.trim().indexOf("#") != -1){ String ip = content.substring(content.indexOf("@") + 1,content.indexOf("#")); for (IoSession sess : sessions) { logger.info(sess.getRemoteAddress() + "-->IP:" + ip); if(sess.getRemoteAddress().toString().indexOf(ip) != -1){ logger.info("Clinet:" + session.getRemoteAddress() + "即将对" + sess.getRemoteAddress() + "私聊"); session.write("您对客户端:" + sess.getRemoteAddress() + "说:" + content.substring(content.indexOf("#")+1) + "\n"); sess.write("客户端:" + session.getRemoteAddress() + "对您说:" + content.substring(content.indexOf("#")+1) + "\n"); } } } else if(!"\r".equals(content)) { for (IoSession sess : sessions) { logger.info("转发给:" + session.getRemoteAddress() + "客户端 messageReceived: " + datetime + "\t" + content); sess.write("客户端:" + session.getRemoteAddress() + "在" + datetime + "对所有人说:\t" + content + "\n"); } } else { session.write("\r"); } } /** * @Description: 获取连接所有客户端 * * @param session * @return * * @Title: ServerMessageUtil.java * @Copyright: Copyright (c) 2014 * * @author Comsys-LZP * @date 2014-3-21 上午11:26:47 * @version V2.0 */ public static Collection<IoSession> getAllClient(IoSession session){ // 拿到所有的客户端Session return session.getService().getManagedSessions().values(); } /** * @Description: 获取现在时间,并且格式为yyyy-MM-dd hh:mm:ss * * @return * * @Title: ServerMessageUtil.java * @Copyright: Copyright (c) 2014 * * @author Comsys-LZP * @date 2014-3-21 上午11:34:09 * @version V2.0 */ public static String getNowDate(){ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); return sdf.format(new Date()); } }
上面的就是封装的一些常用的方法和业务处理等。接下来就是IoHandler实现类了
/** * @Description: * * @Title: ServerMessageHandler.java * @Package com.joyce.mina.server.message * @Copyright: Copyright (c) 2014 * * @author Comsys-LZP * @date 2014-3-19 上午11:54:31 * @version V2.0 */ package com.joyce.mina.server.message; import java.util.Collection; import org.apache.log4j.Logger; import org.apache.mina.core.future.CloseFuture; import org.apache.mina.core.future.IoFuture; import org.apache.mina.core.future.IoFutureListener; import org.apache.mina.core.service.IoHandler; import org.apache.mina.core.session.IdleStatus; import org.apache.mina.core.session.IoSession; import com.joyce.mina.message.ServerMessageUtil; /** * @Description: 处理服务器端消息 * * @ClassName: ServerMessageHandler * @Copyright: Copyright (c) 2014 * * @author Comsys-LZP * @date 2014-3-19 上午11:54:31 * @version V2.0 */ public class ServerMessageHandler implements IoHandler { private static final Logger logger = Logger.getLogger(ServerMessageHandler.class); @Override public void exceptionCaught(IoSession session, Throwable cause) throws Exception { logger.info("服务器发生异常:" + cause.getMessage()); } @Override public void messageReceived(IoSession session, Object message) throws Exception { logger.info("服务器接收到数据: " + message); ServerMessageUtil.messageReceived(session, message); } @Override public void messageSent(IoSession session, Object message) throws Exception { logger.info("服务器发送消息:" + message); } @Override public void sessionClosed(IoSession session) throws Exception { logger.info("关闭当前session:" + session.getId() + "#" + session.getRemoteAddress()); CloseFuture closeFuture = session.close(true); closeFuture.addListener(new IoFutureListener<IoFuture>() { @Override public void operationComplete(IoFuture future) { if (future instanceof CloseFuture) { ((CloseFuture) future).setClosed(); logger.info("sessionClosed CloseFuture setClosed--> " + future.getSession().getId()); } } }); // 获取所有客户端 Collection<IoSession> sessions = ServerMessageUtil.getAllClient(session); // 给每个客户端发送离开信息 for (IoSession sess : sessions) { sess.write("客户端:" + session.getRemoteAddress() + "在" + ServerMessageUtil.getNowDate() + "离开"); } } @Override public void sessionCreated(IoSession session) throws Exception { logger.info("创建一个新连接:" + session.getRemoteAddress()); StringBuffer info = new StringBuffer(); info.append("welcome to the joyce chat room !"); info.append("\r\n\r\n"); session.write(info); // 获取所有在线客户端 Collection<IoSession> sessions = ServerMessageUtil.getAllClient(session); for (IoSession sess : sessions) { StringBuffer welcome = new StringBuffer(); welcome.append("客户端:" + session.getRemoteAddress() + "在" + ServerMessageUtil.getNowDate() + "加入!"); welcome.append("\r\n\r\n"); sess.write(welcome); } } @Override public void sessionIdle(IoSession session, IdleStatus status) throws Exception { logger.info("当前连接" + session.getRemoteAddress() + "处于空闲状态:" + status); } @Override public void sessionOpened(IoSession session) throws Exception { logger.info("打开一个session:" + session.getId() + "#" + session.getBothIdleCount()); StringBuffer info = new StringBuffer(); info.append("1:获取所有在线用户\t2:说明"); info.append("\r\n"); session.write(info); } }
sessionCreated:当一个新的连接建立时,由I/O processor thread调用;
sessionOpened:当连接打开是调用;
messageReceived: 当接收了一个消息时调用;
messageSent:当一个消息被(IoSession#write)发送出去后调用;
sessionIdle:当连接进入空闲状态时调用;
sessionClosed:当连接关闭时调用;
exceptionCaught:实现IoHandler的类抛出异常时调用;
一般情况下,我们最关心的只有messageReceived方法,接收消息并处理,然后调用IoSession的write方法发送出消息!(注意:这里接收到的消息都是Java对象,在IoFilter中所有二进制数据都被解码)一般情况下很少有人实现IoHandler接口,而是继承它的一个实现类IoHandlerAdapter,这样不用覆盖它的7个方法,只需要根据具体需求覆盖其中的几个方法就可以!
Iohandler的7个方法其实是根据session的4个状态值间变化来调用的:
Connected:会话被创建并使用;
Idle:会话在一段时间(可配置)内没有任何请求到达,进入空闲状态;
Closing:会话将被关闭(剩余message将被强制flush);
Closed:会话被关闭;
第三步:编写Server启动类,bind端口,设置编码过程和核心业务处理器
/** * @Description: * * @Title: MinaServer.java * @Package com.joyce.mina.server * @Copyright: Copyright (c) 2014 * * @author Comsys-LZP * @date 2014-3-19 下午12:35:48 * @version V2.0 */ package com.joyce.mina.server; import java.io.IOException; import java.net.InetSocketAddress; import org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder; import org.apache.mina.core.session.IdleStatus; import org.apache.mina.filter.codec.ProtocolCodecFilter; import org.apache.mina.filter.logging.LogLevel; import org.apache.mina.filter.logging.LoggingFilter; import org.apache.mina.transport.socket.SocketAcceptor; import org.apache.mina.transport.socket.nio.NioSocketAcceptor; import com.joyce.mina.code.factory.CharsetCodecFactory; import com.joyce.mina.server.message.ServerMessageHandler; /** * @Description: 服务器启动类 * * @ClassName: MinaServer * @Copyright: Copyright (c) 2014 * * @author Comsys-LZP * @date 2014-3-19 下午12:35:48 * @version V2.0 */ public class MinaServer { private SocketAcceptor acceptor; /** * */ public MinaServer() { // 创建非阻塞的server端的Socket连接 acceptor = new NioSocketAcceptor(); } public boolean start(){ // 获取过滤器链 DefaultIoFilterChainBuilder filterChain = acceptor.getFilterChain(); // 添加编码过滤器 处理乱码、编码问题 filterChain.addLast("codec", new ProtocolCodecFilter(new CharsetCodecFactory())); // 添加日志过滤器 LoggingFilter loggingFilter = new LoggingFilter(); loggingFilter.setMessageReceivedLogLevel(LogLevel.INFO); loggingFilter.setMessageSentLogLevel(LogLevel.INFO); filterChain.addLast("logger", loggingFilter); // 设置核心消息业务处理器 acceptor.setHandler(new ServerMessageHandler()); // 设置session配置,30秒内无操作进入空闲状态 acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 30); try { // 绑定端口3456 acceptor.bind(new InetSocketAddress(3456)); } catch (IOException e) { return false; } return true; } public static void main(String[] args) { MinaServer server = new MinaServer(); server.start(); } }
这里只是一个简单的MINA2服务器的启动,框架很成熟很强大,可以通过Spring配置!这方面的先不扯开,有机会再分享!
此时就可以通过输入http://localhost:3456 来访问了,在控制台会有大量数据输出,绝大部分是浏览器的相关信息和服务器发出去的信息!如果懂telnet的程序猿可以去通过telnet连接上去,效果会更佳!
就说到这里了!
来附上完整项目的地址:http://download.csdn.net/download/luo201227/7101013