编解码和数据包都解决了,下面来关注一下,业务处理方面的功能怎样进行设计。
1. 构建 NettyServerHandler 类,完成业务逻辑的处理功能。
A. 我们需要一个自定义的线程池,用来执行业务逻辑的处理代码;
B. 我们需要封装一下业务处理环节,服务端的业务处理模式比较简单,基本上采用一个请求对应一个应答的操作,所以,可以提取出统一的调用接口 ServerAction 进行业务处理,之后针对不同的请求设计对应的实现类即可。业务处理环节除了 ServerAction 以外,还需要 请求包的类型 和 应答包的类型 信息。
C. 构建一个映射关系,Key是请求包的类型,Value是对应的业务处理环节。我们可以通过映射关系快速定位业务处理的 ServerAction 实现类的对象,以及,应答包JavaBean的实例化。
2. 与SpringBoot的集成
A. 配置参数的自动装配
B. 映射关系的自动注册
package houlei.net.tcp.hdr;
import io.netty.channel.ChannelHandlerContext;
@FunctionalInterface
public interface ServerAction {
void execute(ChannelHandlerContext ctx, REQ request, RSP response);
}
package houlei.net.tcp.hdr;
import houlei.net.tcp.pkg.PackageFactory;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@Sharable
@Component
public class NettyServerHandler extends SimpleChannelInboundHandler {
private static Logger logger = LoggerFactory.getLogger(NettyServerHandler.class);
private static class ServerActionBean {
public Class> requestType;
public Class> responseType;
public ServerAction action;
}
private static final HashMap actions = new HashMap<>();
@Resource
private ApplicationContext applicationContext;
@Value("${action.executor.corePoolSize:0")
private int corePoolSize;
@Value("${action.executor.maxPoolSize:16}")
private int maxPoolSize;
@Value("${action.executor.keepAliveTime:5000}")
private int keepAliveTime;
private ExecutorService serverActionExecutor;
@PostConstruct
public void init(){
serverActionExecutor = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveTime, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());
String[] names = applicationContext.getBeanNamesForType(ServerAction.class);
for (String name : names) {
ServerAction sa = applicationContext.getBean(name, ServerAction.class);
regist(sa);
}
}
public void regist(ServerAction action) {
Type[] typeArgs = findActualTypeArguments(action, ServerAction.class);
if (typeArgs!=null && typeArgs.length>1) {
ServerActionBean bean = new ServerActionBean();
bean.requestType = (Class)typeArgs[0];
bean.responseType = (Class)typeArgs[1];
bean.action = action;
actions.put(bean.requestType, bean);
logger.info("[NettyServerHandler][regist] regist server action : {}", action.getClass().getName());
}
}
private static Type[] findActualTypeArguments(ServerAction sa, Class interfaceClass) {
Type[] genTypes = sa.getClass().getGenericInterfaces();
for (Type type : genTypes) {
if (type instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) type;
if (pt.getRawType() == interfaceClass) {
return pt.getActualTypeArguments();
}
}
}
return null;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object o) throws Exception {
ServerActionBean bean = actions.get(o.getClass());
if (bean != null) {
serverActionExecutor.submit(()->{
try {
Object request = o;
Object response = PackageFactory.create(bean.responseType);
bean.action.execute(ctx, request, response);
if (response != null) {
ctx.writeAndFlush(response);
}
} catch (Exception ex) {
logger.error("[NettyServerHandler][ServerActionExecutor] execute action {} failed.", bean.action.getClass().getName(), ex);
}
});
}
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
logger.debug("NettyServerHandler#channelActive");
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
super.channelInactive(ctx);
logger.debug("NettyServerHandler#channelInactive");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
ctx.close();
logger.error("NettyServerHandler#exceptionCaught", cause);
}
}
package houlei.net.tcp.hdr.action;
import houlei.net.tcp.hdr.ServerAction;
import houlei.net.tcp.pkg.chat.ChatMessage;
import io.netty.channel.ChannelHandlerContext;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
@Component
public class ChatMessageAction implements ServerAction {
@Override
public void execute(ChannelHandlerContext ctx, ChatMessage request, ChatMessage response) {
BeanUtils.copyProperties(request, response);
}
}
@Component
public class HartbeatAction implements ServerAction {
@Override
public void execute(ChannelHandlerContext ctx, HartbeatPackage request, HartbeatPackage response) {
}
}
@Component
public class LoginAction implements ServerAction {
@Override
public void execute(ChannelHandlerContext ctx, LoginRequestPackage request, LoginResponsePackage response) {
response.setSucceed(false);
}
}
代码虽短,思想重要。所有 ServerAction 接口的实现类只要和SpringBoot集成了,就会被自动注册,整合到业务处理流程当中。
由于是Demo程序,就没有添加数据库相关的代码。