原文同步至http://www.waylau.com/mina-chat/
在《MINA 快速入门》一文中,我们介绍了如何利用 MINA 快速构建一个 Time Server(时间服务器)。在《Netty 实现聊天功能》一文,我们也介绍了如何用 Netty 实现聊天功能。由于 MINA 和 Netty 是同一个作者,架构类似,如果你掌握其中一个,学习另外一个也不是难事。现在我们就用 MINA 来实现聊天功能。
##准备
- JDK 7+
- Maven 3.2.x
- MINA 2.x
- Eclipse 4.x
##服务端
让我们从 handler (处理器)的实现开始,handler 是由 MINA 生成用来处理 I/O 事件的, 处理器继承自 IoHandlerAdapter。
###SimpleChatServerHandler.java
1public class SimpleChatServerHandler extends IoHandlerAdapter { // (1) 2 3 private final Set<IoSession> sessions = Collections 4 .synchronizedSet(new HashSet<IoSession>()); // (2) 5 6 @Override 7 public void sessionCreated(IoSession session) throws Exception {// (3) 8 sessions.add(session); 9 broadcast(" has join the chat", session); 10 } 11 12 @Override 13 public void sessionClosed(IoSession session) throws Exception {// (4) 14 sessions.remove(session); 15 broadcast(" has left the chat", session); 16 } 17 18 @Override 19 public void messageReceived(IoSession session, Object message) 20 throws Exception {// (5) 21 String str = message.toString(); 22 broadcast(str, session); 23 } 24 25 @Override 26 public void sessionIdle(IoSession session, IdleStatus status) 27 throws Exception {// (6) 28 System.out.println("[Server] IDLE " + session.getRemoteAddress() 29 + session.getIdleCount(status)); 30 } 31 32 @Override 33 public void exceptionCaught(IoSession session, Throwable cause) { 34 cause.printStackTrace();// (7) 35 System.out.println("[Server] Client:" + session.getRemoteAddress() 36 + "异常"); 37 // 遇到未捕获的异常,则关闭连接 38 session.close(true); 39 } 40 41 /** 42 * 广播消息 43 * 44 * @param message 45 */ 46 private void broadcast(String message, IoSession exceptSession) {// (8) 47 synchronized (sessions) { 48 for (IoSession session : sessions) { 49 if (session.isConnected()) { 50 if (session.equals(exceptSession)) { 51 session.write("[You]" + message); 52 } else { 53 session.write("[Client" + session.getRemoteAddress() 54 + "] " + message); 55 } 56 57 } 58 } 59 } 60 } 61}
1.SimpleChatServerHandler 继承自 IoHandlerAdapter,这个类实现了 IoHandler 接口,IoHandlerAdapter 提供了许多事件处理的接口方法,然后你可以覆盖这些方法。现在仅仅只需要继承 IoHandlerAdapter 类而不是你自己去实现接口方法。
2.Set<IoSession> sessions 用来存储所有的 连接上来的 session.
3.覆盖了 sessionCreated() 事件处理方法。每当从服务端收到新的客户端连接时,客户端的 IoSession 就存入存入 sessions 列表中,并通知列表中的其他客户端 IoSession
4.覆盖了 sessionClosed() 事件处理方法。每当从服务端收到客户端断开时,客户端的 IoSession 从 sessions 列表中,并通知列表中的其他客户端 IoSession
5.覆盖了 messageReceived() 事件处理方法。每当从服务端读到客户端写入信息时,将信息广播给其他客户端的 IoSession。
6.覆盖了 sessionIdle() 事件处理方法。服务端监听到客户端闲置情况
7.exceptionCaught() 事件处理方法是当出现 Throwable 对象才会被调用,即当 MINA 由于 IO 错误或者处理器在处理事件时抛出的异常时。在大部分情况下,捕获的异常应该被记录下来并且把关联的 IoSession 给关闭掉。然而这个方法的处理方式会在遇到不同异常的情况下有不同的实现,比如你可能想在关闭连接之前发送一个错误码的响应消息。
8.broadcast() 服务器用于广播的方法
###SimpleChatServer.java
编写一个 main() 方法来启动服务端。
1public class SimpleChatServer { 2 3 public static void main(String[] args) { 4 int port; 5 if (args.length > 0) { 6 port = Integer.parseInt(args[0]); 7 } else { 8 port = 8080; 9 } 10 11 SocketAcceptor acceptor = new NioSocketAcceptor(); // (1) 12 13 acceptor.getFilterChain().addLast( "codec", 14 new ProtocolCodecFilter( new TextLineCodecFactory( Charset.forName( "UTF-8" )))); // (2) 15 acceptor.setHandler(new SimpleChatServerHandler()); // (3) 16 17 acceptor.getSessionConfig().setReadBufferSize(2048); // (4) 18 acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 100);// (5) 19 20 try { 21 acceptor.bind(new InetSocketAddress(port)); // (6) 22 } catch (IOException e) { 23 e.printStackTrace(); 24 } 25 System.out.println("[Server]Listening on port " + port); 26 } 27}
1.NioSocketAcceptor 基于 TCP/IP 的 socket 连接。
2.ProtocolCodecFilter 是编码和解码的过滤器链,将一个连入的 ByteBuffer 转化为消息 POJO,反之亦然。而TextLineCodecFactory是 MINA 提供的一个编解码是,可以方便处理基于文本的协议。
3.设置在处理器为之前创建的 SimpleChatServerHandler。
4.会话配置,设置字节缓存大小。
5.会话配置,设置闲置时间。
6.剩下的就是绑定端口然后启动服务。这里我们在机器上默认绑定了机器所有网卡上的 8080 端口。
恭喜!你已经完成了基于 MINA 聊天服务端程序。
##客户端
###SimpleChatClientHandler.java
客户端的处理类比较简单,只需要将读到的信息打印出来即可
1public class SimpleChatClientHandler extends IoHandlerAdapter { 2 @Override 3 public void messageReceived(IoSession session, Object message) { 4 String str = message.toString(); 5 System.out.println(str); 6 } 7}
###SimpleChatClient.java
编写一个 main() 方法来启动客户端。
1public class SimpleChatClient { 2 private static final long CONNECT_TIMEOUT = 30 * 1000L; // 30 秒; 3 private static final String HOSTNAME = "127.0.0.1"; 4 private static final int PORT = 8080; 5 /** 6 * @param args 7 */ 8 public static void main(String[] args) { 9 10 NioSocketConnector connector = new NioSocketConnector(); // (1) 11 connector.setConnectTimeoutMillis(CONNECT_TIMEOUT); 12 connector.getFilterChain().addLast( "codec", 13 new ProtocolCodecFilter( new TextLineCodecFactory( Charset.forName( "UTF-8" )))); 14 connector.setHandler(new SimpleChatClientHandler()); 15 IoSession session; 16 ConnectFuture future = connector.connect(new InetSocketAddress( 17 HOSTNAME, PORT)); // (2) 18 future.awaitUninterruptibly(); 19 session = future.getSession(); 20 21 while(true){ 22 try { 23 BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); 24 session.write(in.readLine()); 25 } catch (IOException e) { 26 e.printStackTrace(); 27 } 28 } 29 } 30}
1.NioSocketConnector 用于 TCP/IP 连接
2.连接到指定的服务器
##运行效果
先运行 SimpleChatServer,再可以运行多个 SimpleChatClient,控制台输入文本继续测试



##源码
见 https://github.com/waylau/apache-mina-2-user-guide-demos 中 simplechat
##参考
- Apache MINA 2 用户指南 https://github.com/waylau/apache-mina-2.x-user-guide
- Netty 4.x 用户指南 https://github.com/waylau/netty-4-user-guide