七的博客

Mina 通信入门(二)-快速入门示例

网络通信电力Mina

Mina 通信入门(二)-快速入门示例

这一章节将通过一个简单的 Mina 的快速入门示例程序,让你快速了解 Mina 的基本用法。程序的逻辑很简单,就是服务器接收客户端发送的消息,并将其原样返回给客户端。客户端发送消息给服务器, 然后打印服务器返回的消息。

入门示例流程图

1. Mina 工作流程概述

在写 HelloWorld 之前,我们先看下面这张图 Mina 官网文档中一张工作流程图:

Mina工作流程图

从图里可以看出,一个 Mina 应用会分为三个层次:

  • IO Service: 执行具体的 I/O操作,这里是对 Java NIO 的封装。
  • IO Filter Chain: 执行过滤、转换消息结构、打印日志等。 换句话来说就是还没有到具体的业务逻辑处理之前的一些处理链。
  • IO Handler: 执行业务逻辑处理,比如收到数据存数据库、给客户端应答报文等。

这三个层次分别代表着三个 Java 接口:

  • IoService
  • IoFilter
  • IoHandler

这三个接口基本就覆盖了整个 Mina 的使用,理解这三个接口是开发 Mina 应用很重要的一个环节。 在后面几张我们将逐步讲解这三个接口,这里是为了写 HelloWorld 之前先了解下需要用到 Mina 的哪些组件。

数据流转

2. 编写服务端代码

服务端的大概逻辑如下:

  • 实例化一个 NioSocketAcceptor 来处理客户端连接的接入,使用的是 NIO 模式。

  • 实例化一个自定义的 ServerHandler 用来处理数据相关的业务逻辑。主要是实现 messageReceived 这个方法,用于处理收到数据时的操作。

  • 设置一个处理文本行的编码器和解码器,这样才可以将收到的字节流转换回原来的字符串形式,同时也可以将字符型转换成网络发送的字节流。

  • 设置连接配置,如缓冲区大小、空闲超时时间等。

  • 调用 bind 方法绑定服务器端口 8080 , 然后启动服务。

package com.suny.mina;

import org.apache.mina.core.service.IoAcceptor;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;

import java.io.IOException;
import java.net.InetSocketAddress;

public class MinaServer {

    // 设置服务端固定暴露的端口号
    private static final int PORT = 8080;

    public static void main(String[] args) throws IOException {
        // 创建 IoAcceptor 来处理连接的接入
        IoAcceptor acceptor = new NioSocketAcceptor();

        // 设置 IoHandler 来处理数据读取、数据写入等等业务逻辑
        acceptor.setHandler(new ServerHandler());
        
            // 添加一个处理文本行的编码器和解码器,这个很重要,没有将会报错
        acceptor.getFilterChain().addLast("codec",
                new ProtocolCodecFilter(new TextLineCodecFactory(Charset.forName("UTF-8"))));

        // 设置连接配置,这里暂时不要去纠结
        acceptor.getSessionConfig().setReadBufferSize(2048);
        acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10);

        // 绑定端口并启动服务器
        acceptor.bind(new InetSocketAddress(PORT));


        System.out.println("Mina服务暴露在端口 " + PORT);
    }

    private static class ServerHandler extends IoHandlerAdapter {
        @Override
        public void messageReceived(IoSession session, Object message) throws Exception {
            String str = message.toString();
            System.out.println("服务端收到客户端的消息: " + str);

            // 将收到的消息原样发送回客户端
            session.write(str);
        }

        @Override
        public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
            cause.printStackTrace();
        }
    }
}

3. 编写客户端代码

客户端的大概逻辑如下:

  • 创建一个 NioSocketConnector 用于连接服务端,使用的是 NIO 模式。
  • 创建 ClientHandler , 用于处理连接的I/O事件。主要的逻辑是重写了 messageReceived 方法,用于处理收到数据时的操作。
  • 设置一个处理文本行的编码器和解码器,这样才可以将收到的字节流转换回原来的字符串形式,同时也可以将字符型转换成网络发送的字节流。
  • 设置连接配置, 如缓冲区大小、空闲超时时间等。
  • 调用 connect 方法连接服务端地址 localhost:8080 ,并等待连接成功。
  • 调用 write 方法发送 HelloWorld, Mina! 到服务端。
  • 等待接收服务器返回的消息。
  • 关闭连接。
package com.suny.mina;

import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.service.IoConnector;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.transport.socket.nio.NioSocketConnector;

import java.io.IOException;
import java.net.InetSocketAddress;


public class MinaClient {
    private static final String HOST = "localhost";
    private static final int PORT = 8080;

    public static void main(String[] args) throws IOException {
        // 创建IoConnector
        IoConnector connector = new NioSocketConnector();

        // 设置 IoHandler 来处理相关业务
        connector.setHandler(new ClientHandler());
        
        // 添加一个处理文本行的编码器和解码器,这个很重要,没有将会报错
        connector.getFilterChain().addLast("codec",
                new ProtocolCodecFilter(new TextLineCodecFactory(Charset.forName("UTF-8"))));

        // 设置连接配置,这里先不要纠结这几个参数
        connector.getSessionConfig().setReadBufferSize(2048);
        connector.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10);

        // 连接到服务端  localhost:8080
        ConnectFuture future = connector.connect(new InetSocketAddress(HOST, PORT));
        future.awaitUninterruptibly();

        // 获取IoSession
        IoSession session = future.getSession();

        // 发送消息给服务器
        session.write("HelloWorld, Mina!");

        // 等待接收服务器返回的消息
        future.awaitUninterruptibly();

        // 关闭连接
        session.closeNow();
        System.out.println("Mina 客户端声明周期结束.");
    }

    // 创建一个自定义的业务处理器
    private static class ClientHandler extends IoHandlerAdapter {
        
        @Override
        public void messageReceived(IoSession session, Object message) throws Exception {
            String str = message.toString();
            System.out.println("客户端收到服务端的消息: " + str);
        }

        @Override
        public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
            cause.printStackTrace();
        }
    }
}

4. 运行示例程序

先启动服务器端的程序 , 再启动客户端的程序,这样才能看到下面的效果。

  1. 运行服务器端的 MinaServer 类,控制台输出:
   Mina服务暴露在端口 8080
  1. 运行客户端的 MinaClient 类,控制台输出:
   客户端收到服务端的消息: HelloWorld, Mina!
   Mina 客户端声明周期结束.

同时,服务器端的控制台输出:

   服务端收到客户端的消息: HelloWorld, Mina!

可以看到 , 客户端发送的消息 “HelloWorld, Mina!” 被服务器接收并原样返回, 客户端接收到服务器返回的消息后打印到控制台,然后关闭连接。

5. 小结

虽然这只是一个 Hello World 级别的程序,但是实际上生产的通信程序基本套路跟它是差不多的,只是在基础上增加了一些业务处理,同时各方面处理的更加完善。下一章节将开始介绍一下几个重要的组件 IoService、IoFilter、IoHandler 等。