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基础(三):IOFilter、自定义过滤器、日志过滤器
Mina基础(四):理解IoSession、I/O Processor、IoBuffer
Mina基础(七):Mina整合Spring服务端、Spring boot 客户端
至此,Mina基础系统完毕,有问题可以留言,尽量解答。