Demo中的代码源自作者的开源工程,有实用价值:也就是说,服务端和客户端都要实现消息的发送和接收,这也就实现了双向通信。如果有心的话,稍加改造,也就很容易实现一个简陋的聊天程序了。下节将将给出真正的实现代码。补充一句,本文中的客户端代码跟系列文章中的上篇《NIO框架入门(一):服务端基于Netty4的UDP双向通信Demo演示》,用的是相同的代码,如果您正在评估MIN2和Netty4的UDP服务端区别,可对照着上篇,进行详细对照和分析哦。MINA2服务端准备工作
[1] 第一步:下载MINA2
MINA2的官方网站是:http://mina.apache.org/,最新版本是 Mina 2.0.13,如下图:
补充说明:建议2进制和源码都下载下来,2进制制包中是编译好的jar包,源码包中包含了官方的Demo源代码,需要的时候可以对照着看看,对于最佳实践来说,有一定的参考价值。
[2] 第二步:找到MINA2的核心库文件
一般情况下,只需要mina-core-2.0.13.jar包就行了,见下图:
[3] 第三步:建好MINA2的服务端Java工程,准备开撸
个人习惯用Eclipse,你如用NetBeans或InteliJ也没问题,具体建立过程不熟练的请自行百度吧,唯一注意的是把MINA2的lib包引用进来,我的见下图:
[2] 服务端Handler类 EchoSeverHandler.java:
1 public class EchoSeverHandler extends IoHandlerAdapter 2 { 3 private static Logger logger = LoggerFactory.getLogger(EchoSeverHandler.class); 4 public static final CharsetDecoder decoder = (Charset.forName("UTF-8")).newDecoder(); 5 6 /** 7 * MINA的异常回调方法。 8 * <p> 9 * 本类中将在异常发生时,立即close当前会话。10 *11 * @param session 发生异常的会话12 * @param cause 异常内容13 * @see IoSession#close(boolean)14 */15 @Override16 public void exceptionCaught(IoSession session, Throwable cause) throws Exception17 {18 logger.error("[IMCORE]exceptionCaught捕获到错了,原因是:"+cause.getMessage(), cause);19 session.close(true);20 }21 22 /**23 * MINA框架中收到客户端消息的回调方法。24 * <p>25 * 本类将在此方法中实现完整的即时通讯数据交互和处理策略。26 * <p>27 * 为了提升并发性能,本方法将运行在独立于MINA的IoProcessor之外的线程池中,28 * 详见 {@link ServerLauncher#initAcceptor()}中的MINA设置代码 。29 *30 * @param session 收到消息对应的会话引用31 * @param message 收到的MINA的原始消息封装对象,本类中是 {@link IoBuffer}对象32 * @throws Exception 当有错误发生时将抛出异常33 */34 @Override35 public void messageReceived(IoSession session, Object message)throws Exception36 {37 //*********************************************** 接收数据38 // 读取收到的数据39 IoBuffer buffer = (IoBuffer) message;40 String body = buffer.getString(decoder);41 // 注意:当客户使用不依赖于MINA库的情况下,以下官方推42 // 荐的读取方**在数据首部出现几个字节的未知乱码43 // message.toString()44 logger.debug("【NOTE】>>>>>> 收到客户端的数据:"+body);45 46 //*********************************************** 回复数据47 String strToClient = "Hello,我是Server,我的时间戳是"+System.currentTimeMillis();48 byte[] res = strToClient.getBytes("UTF-8");49 // 组织IoBuffer数据包的方法:本方法才可以正确地让客户端UDP收到byte数组50 IoBuffer buf = IoBuffer.wrap(res); 51 52 // 向客户端写数据53 WriteFuture future = session.write(buf); 54 // 在100毫秒超时间内等待写完成55 future.awaitUninterruptibly(100);56 // The message has been written successfully57 if( future.isWritten() )58 {59 // send sucess!60 }61 // The messsage couldn't be written out completely for some reason.62 // (e.g. Connection is closed)63 else64 {65 logger.warn("[IMCORE]回复给客户端的数据发送失败!");66 }67 }68 }
而官方的代码是直接:message.toString(),这会存在不能正确地解码标准Java UDP代码实现的客户端发过来的数据问题哦。注意点2:服务端的EchoSeverHandler.java文件中,发送数据时的IoBuffer对象组织方法是解决无MINA2客户端依赖的客户端能正确接收MINA2服务端发过去的数据的关键,具体代码如下:
IoBuffer buf = IoBuffer.wrap(res);
而官方的Demo代码以及网上的各种实现更为复杂,但会导致客户端接收数据有多余字节,官方的会产生多余字节和乱码的代码如下:
IoBuffer buf = IoBuffer.allocate(res.length); buf.setAutoExpand(true); buf.putInt(res.length);buf.put(res); buf.flip();buf.shrink();
[1] 客户端主类 EchoClient.java:
1 public class EchoClient 2 { 3 public static void main(String[] args) throws Exception 4 { 5 // 初始化本地UDP的Socket 6 LocalUDPSocketProvider.getInstance().initSocket(); 7 // 启动本地UDP监听(接收数据用的) 8 LocalUDPDataReciever.getInstance().startup(); 9 10 // 循环发送数据给服务端11 while(true)12 {13 // 要发送的数据14 String toServer = "Hi,我是客户端,我的时间戳"+System.currentTimeMillis();15 byte[] soServerBytes = toServer.getBytes("UTF-8");16 17 // 开始发送18 boolean ok = UDPUtils.send(soServerBytes, soServerBytes.length);19 if(ok)20 Log.d("EchoClient", "发往服务端的信息已送出.");21 else22 Log.e("EchoClient", "发往服务端的信息没有成功发出!!!");23 24 // 3000秒后进入下一次循环25 Thread.sleep(3000);26 }27 }28 }
[2] Socket操作类 LocalUDPSocketProvider.java:
1 public class LocalUDPSocketProvider 2 { 3 private static final String TAG = LocalUDPSocketProvider.class.getSimpleName(); 4 private static LocalUDPSocketProvider instance = null; 5 private DatagramSocket localUDPSocket = null; 6 7 public static LocalUDPSocketProvider getInstance() 8 { 9 if (instance == null)10 instance = new LocalUDPSocketProvider();11 return instance;12 }13 14 public void initSocket()15 {16 try17 {18 // UDP本地监听端口(如果为0将表示由系统分配,否则使用指定端口)19 this.localUDPSocket = new DatagramSocket(ConfigEntity.localUDPPort);20 // 调用connect之后,每次send时DatagramPacket就不需要设计目标主机的ip和port了21 // * 注意:connect方法一定要在DatagramSocket.receive()方法之前调用,22 // * 不然整send数据将会被错误地阻塞。这或许是官方API的bug,也或许是调23 // * 用规范就应该这样,但没有找到官方明确的说明24 this.localUDPSocket.connect(25 InetAddress.getByName(ConfigEntity.serverIP), ConfigEntity.serverUDPPort);26 this.localUDPSocket.setReuseAddress(true);27 Log.d(TAG, "new DatagramSocket()已成功完成.");28 }29 catch (Exception e)30 {31 Log.w(TAG, "localUDPSocket创建时出错,原因是:" + e.getMessage(), e);32 }33 }34 35 public DatagramSocket getLocalUDPSocket()36 {37 return this.localUDPSocket;38 }39 }
如果你阅读过本系列的上一篇《NIO框架入门(一):服务端基于Netty4的UDP双向通信Demo演示》,应该能明显地感觉的出来MINA2的UDP服务端API接口使用要是Netty4的繁琐,而且MINA2还存在独立客户端(非依赖于MINA2客户端)实现时的多余字节和乱码问题。但个人认为MINA2的代码风格更符合一般程序员的编码习惯,更好懂一些,而Netty4因历经多个大版本的进化,虽起来非常简洁,但实现并不是那么直观。当然,至于MINA还是Netty,请客观一评估和使用,因为二者并无本质区别。更多学习资源
[1] MINA和Netty的源码在线学习和查阅:[2] MINA和Netty的API文档在线查阅:[3] 更多有关NIO编程的资料:[4] 有关IM聊天应用、消息推送技术的资料:[5] 技术交流和学习:
原标题:【原创】NIO框架入门(二):服务端基于MINA2的UDP双向通信Demo演示
关键词: