Mina基础(七):Mina整合Spring服务端、Spring boot 客户端

Spring 作为服务端

将类交给Spring管理,通过配置文件注入所需要的Bean,通过配置文件绑定端口启动Mina服务端。

项目整体代码目录结构(见上一篇):

     

1. 设置I/O接收器

    <!-- 设置I/O接收器,指定接收到请求后交给handler处理 -->
    <!-- 此部分被 NioSocketAcceptor 隐式使用,无此则会报字符串无法转换成 InetSocketAddress -->
    <!-- 从字符串到 SocketAddress 的转换,会偿试使用该自定义属性编辑器 -->
    <bean id="customEditorConfigurer" class="org.springframework.beans.factory.config.CustomEditorConfigurer">
        <property name="customEditors">
            <map>
                <entry key="java.net.SocketAddress" value="org.apache.mina.integration.beans.InetSocketAddressEditor"/>
            </map>
        </property>
    </bean>

 2. 会话管理类,注入服务端的处理handler

    <!-- session会话管理类 -->
    <bean id="pcmSessionManager" class="cc.xmc.mina.session.DefaultSessionManagerImpl" />

    <!-- 处理的handler类,将具体的处理执行类,注入到ServiceHandler的Map中 -->
    <bean id="ioHandler" class="cc.xmc.mina.handler.ServiceHandler">
        <property name="handlers">
            <map>
                <entry key="clientBind">
                    <bean class="cc.xmc.mina.handler.interfaceAndImpl.BindHandler" />
                </entry>
                <entry key="clientClose">
                    <bean class="cc.xmc.mina.handler.interfaceAndImpl.SessionClosedHandler" />
                </entry>
                <entry key="clientPush">
                    <bean class="cc.xmc.mina.handler.interfaceAndImpl.PushMessageHandler" />
                </entry>
            </map>
        </property>
    </bean>

3. 注入线程池filter、日志filter、编解码工厂

    <!-- 自带的线程池filter -->
    <bean id="executorFilter" class="org.apache.mina.filter.executor.ExecutorFilter" />
    <bean id="mdcInjectionFilter" class="org.apache.mina.filter.logging.MdcInjectionFilter">
        <constructor-arg value="remoteAddress" />
    </bean>

    <!-- 日志 -->
    <bean id="logFilter" class="org.apache.mina.filter.logging.LoggingFilter" />
    
    <!-- 自己实现的编解码工厂 -->
    <bean id="coderFilter" class="org.apache.mina.filter.codec.ProtocolCodecFilter">
        <constructor-arg>
            <bean class="cc.xmc.mina.filter.codec.CustomProtocolCodecFactory" />
        </constructor-arg>
    </bean>

4. 心跳机制filter

    <!-- 心跳检测设置 -->
    <bean id="keepAliveFilter" class="org.apache.mina.filter.keepalive.KeepAliveFilter">
        <!-- 构造函数的第一个参数传入自己实现的心跳工厂 -->
        <constructor-arg>
            <bean class="cc.xmc.mina.filter.KeepAliveFactoryImpl">
                <constructor-arg value="true" />
            </bean>
        </constructor-arg>
        <!-- 第二个参数需要的是IdleStatus对象,value值为设置读写空闲 -->
        <constructor-arg type="org.apache.mina.core.session.IdleStatus" value="BOTH_IDLE" />
        <!-- 第三个参数是超时无反馈情况下的处理机制  默认为CLOSE  即关闭连接 -->
        <constructor-arg>
            <bean class="cc.xmc.mina.filter.KeepAliveRequestTimeoutHandlerImpl" />
        </constructor-arg>
        <!-- 心跳频率,不设置则默认为60s -->
        <property name="requestInterval" value="120" />
        <!-- 心跳超时时间,不是默认为30s -->
        <property name="requestTimeout" value="30" />
        <!-- 是否传递给其他filter,不设置则为false -->
        <property name="forwardEvent" value="false" />
    </bean>

5. 将之前的filter注入到过滤器链中

	<!-- 过滤器链 -->
	<bean id="filterChainBuilder" class="org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder" >
		<property name="filters">
			<map>
				<!-- 自带的线程池filter -->
				<entry key="executor" value-ref="executorFilter" />
				<entry key="mdcInjectionFilter" value-ref="mdcInjectionFilter" />
				<!-- 编解码器filter -->
				<entry key="coderFilter" value-ref="coderFilter" />
				<!-- 日志的filter -->
				<entry key="logFilter" value-ref="logFilter" />
				<!-- 心跳filter -->
				<entry key="keepAliveFilter" value-ref="keepAliveFilter" />
			</map>
		</property>
	</bean>

6. 绑定端口、消息处理handler、过滤器链

    <!-- 绑定到端口,绑定handler,绑定filter链等 -->
    <bean id="ioAcceptor" class="org.apache.mina.transport.socket.nio.NioSocketAcceptor" init-method="bind" destroy-method="unbind">
        <!-- 端口号 -->
        <property name="defaultLocalAddress" value=":9000" />
        <!-- 绑定自定义的handler -->
        <property name="handler" ref="ioHandler" />
        <!-- 绑定过滤器链集合 -->
        <property name="filterChainBuilder" ref="filterChainBuilder" />
        <property name="reuseAddress" value="true" />
    </bean>
</beans>

7. 由web.xml中,配置扫描到Spring容器,包含此文件即可

完成以上配置后,启动该项目,即可以启动Mina服务端。


Spring boot 作为客户端

因为未提供Mina的start包,所以需要自定义Configuration。

具体与Spring中的配置文件类似,实现IO接收、线程池、编解码、日志、心跳filter,过滤器链,创建连接等步骤。

服务端也可以类似的进行Spring boot搭建,只需要将一些服务端特有的设置进行配置即可。

完整客户端项目目录结构:

注意:

客户端不需要进行心跳请求和超时处理,session的封装,handler的具体处理等等,所有在客户端的代码需要进行精简。

1. 配置Configuration将bean交由Spring 管理

MinaClientConfig(理解为就是将xml文件使用Java方式进行配置

/*
 *
 * describe mina客户端配置
 * @author xmc
 * @date 2018/7/25 10:48
 * @param  * @param null
 * @return
 */
@Configuration
public class MinaClientConfig {

    /**
     * 设置I/O接收器
     * @return
     */
    private Map<Class<?>, Class<? extends PropertyEditor>> customEditors = new HashMap<>();
    @Bean
    public CustomEditorConfigurer customEditorConfigurer() {
        customEditors.put(SocketAddress.class, InetSocketAddressEditor.class);
        CustomEditorConfigurer configurer = new CustomEditorConfigurer();
        configurer.setCustomEditors(customEditors);
        return configurer;
    }

    /**
     * 线程池filter
     */
    @Bean
    public ExecutorFilter executorFilter() {
        return new ExecutorFilter();
    }
    @Bean
    public MdcInjectionFilter mdcInjectionFilter() {
        return new MdcInjectionFilter(MdcInjectionFilter.MdcKey.remoteAddress);
    }

    /**
     * 编码器filter
     */
    @Bean
    public ProtocolCodecFilter protocolCodecFilter() {
        return new ProtocolCodecFilter(new CustomProtocolCodecFactory());
    }

    /**
     * 日志filter
     */
    @Bean
    public LoggingFilter loggingFilter() {
        LoggingFilter filter = new LoggingFilter();
        return filter;
    }

    /**
     * 心跳filter
     */
    @Bean
    public KeepAliveFilter keepAliveFilter() {
        // 客户端不需要发送心跳
        KeepAliveFactoryImpl keepAliveFactory = new KeepAliveFactoryImpl(false);
        // 注入心跳工厂,读写空闲
        KeepAliveFilter filter = new KeepAliveFilter(keepAliveFactory);
        return filter;
    }

    /**
     * 过滤器链
     */
    @Bean
    public DefaultIoFilterChainBuilder defaultIoFilterChainBuilder(ExecutorFilter executorFilter,
                                                                   MdcInjectionFilter mdcInjectionFilter,
                                                                   ProtocolCodecFilter protocolCodecFilter,
                                                                   LoggingFilter loggingFilter,
                                                                   KeepAliveFilter keepAliveFilter) {
        DefaultIoFilterChainBuilder filterChainBuilder = new DefaultIoFilterChainBuilder();
        Map<String, IoFilter> filters = new LinkedHashMap<>();
        filters.put("executor", executorFilter);
        filters.put("mdcInjectionFilter", mdcInjectionFilter);
        filters.put("protocolCodecFilter", protocolCodecFilter);
        // filters.put("loggingFilter", loggingFilter); 不注入日志filter
        filters.put("keepAliveFilter", keepAliveFilter);
        filterChainBuilder.setFilters(filters);
        return filterChainBuilder;
    }


    /**
     * 创建连接
     * @return
     */
    @Bean(initMethod = "init", destroyMethod = "destroy")
    public NioSocketConnector nioSocketConnector(ClientHandler clientHandler,
                                                 DefaultIoFilterChainBuilder defaultIoFilterChainBuilder) {
        NioSocketConnector connector = new NioSocketConnector();
        // 设置连接超时时间
        connector.setConnectTimeoutMillis(30000);
        // 设置处理handler
        connector.setHandler(clientHandler);
        // 绑定过滤器链
        connector.setFilterChainBuilder(defaultIoFilterChainBuilder);
        return connector;
    }

}

2. 在启动Spring boot 容器时,启动客户端连接

在Spring框架下是通过ApplicationListener监听器来实现的。在Spring Boot中给我们提供了两个接口来帮助我们实现这样的需求-CommandLineRunner和ApplicationRunner。

@Component
@ConfigurationProperties(prefix="mina")
public class MinaRun implements CommandLineRunner {
 
    private static final Logger logger = LogManager.getLogger(MinaRun.class);
 
    private String host;
    private int port;
    private String account;
    private String msg;
 
    private NioSocketConnector connector;
 
    @Autowired
    public MinaRun(NioSocketConnector connector) {
        this.connector = connector;
    }
 
    @Override
    public void run(String... args) throws Exception {
        logger.info(">>>>>>>>>>>>>>>>>>正在连接远程服务器>>>>>>>>>>>>>>>>>>>>");
        try {
            // 设置服务器地址,端口号
            InetSocketAddress address = new InetSocketAddress(host, port);
            ConnectFuture future = connector.connect(address);
            // 获取session执行绑定方法
            future.awaitUninterruptibly();
            IoSession session = future.getSession();
            bind(session, account, msg);
            logger.info("<<<<<<<<<<<<<<<<<<与远程服务器连接成功<<<<<<<<<<<<<<<<<<");
        } catch (Exception e) {
            logger.error("<<<<<<<<<<<<<<<<<<与远程服务器连接失败<<<<<<<<<<<<<<<<<<" + e.getMessage());
            e.printStackTrace();
        }
    }
 
    /**
     *
     * @param session 会话
     * @param msg 账户描述
     * @param account 账户名
     */
    private static void bind(IoSession session, String account, String msg) {
        SentBody sent = new SentBody();
        sent.put(Message.SESSION_KEY, account);
        sent.put("message", msg);
        sent.setKey("clientBind");
        // 转换为自定义协议
        CustomPack pack = new CustomPack(CustomPack.REQUEST, new Gson().toJson(sent, SentBody.class));
        session.write(pack);
    }
 
    public String getHost() {
        return host;
    }
 
    public void setHost(String host) {
        this.host = host;
    }
 
    public int getPort() {
        return port;
    }
 
    public void setPort(int port) {
        this.port = port;
    }
 
    public String getAccount() {
        return account;
    }
 
    public void setAccount(String account) {
        this.account = account;
    }
 
    public String getMsg() {
        return msg;
    }
 
    public void setMsg(String msg) {
        this.msg = msg;
    }
}

@ConfigurationProperties(prefix="mina") 读取配置文件中 mina的前缀,会自动注入到host、port等属性中去。

完成以上配置后,将host、port设置为服务端的地址端口,启动服务端,然后启动客户端即可实现2者之间的通讯。

附:

客户端的心跳实现:

/**
 * 心跳实现类
 * 服务端发送的是hb_request,那么客户端就应该返回hb_response
 */
public class KeepAliveFactoryImpl implements KeepAliveMessageFactory {

    // 服务端需要发送请求,客户端无需发送
    private boolean sendHbRequest;

    public KeepAliveFactoryImpl(boolean isServer) {
        this.sendHbRequest = isServer;
    }

    /**
     * 服务端心跳发送请求命令
     */
    private static final String HEART_BEAT_REQUEST = Message.CMD_HEARTBEAT_REQUEST;

    /**
     * 客户端心跳响应命令
     */
    private static final String HEART_BEAT_RESPONSE = Message.CMD_HEARTBEAT_RESPONSE;

    // 用来获取一个心跳请求包
    @Override
    public Object getRequest(IoSession session) {
        // 无需请求心跳
        return null;
    }

    // 用来获取一个心跳回复包
    @Override
    public Object getResponse(IoSession session, Object request) {
        return new CustomPack(CustomPack.RESPONSE, HEART_BEAT_RESPONSE);
    }

    // 用来判断接收到的消息是不是一个心跳请求包,是就返回true
    @Override
    public boolean isRequest(IoSession session, Object message) {
        if (message instanceof CustomPack) {
            CustomPack pack = (CustomPack) message;
            return pack.getContent().equals(Message.CMD_HEARTBEAT_REQUEST);
        }
        return false;
    }

    // 用来判断接收到的消息是不是一个心跳回复包,是就返回true
    @Override
    public boolean isResponse(IoSession session, Object message) {
        // 不接受回复包
        return false;
    }

}

Mina基础(一):基本结构分析、长短连接、IOService

Mina基础(二):基础服务端、客户端搭建

Mina基础(三):IOFilter、自定义过滤器、日志过滤器

Mina基础(四):理解IoSession、I/O Processor、IoBuffer

Mina基础(五):编写自定义协议及编解码器

Mina基础(六):Mina整合Spring之前的准备工作

Mina基础(七):Mina整合Spring服务端、Spring boot 客户端

至此,Mina基础系统完毕,有问题可以留言,尽量解答。

猜你喜欢

转载自blog.csdn.net/x3499633/article/details/81295608