編解碼和數據包都解決了,下面來關注一下,業務處理方面的功能怎樣進行設計。
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<REQ, RSP> {
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<Class, ServerActionBean> 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<Runnable>());
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<ChatMessage, ChatMessage> {
@Override
public void execute(ChannelHandlerContext ctx, ChatMessage request, ChatMessage response) {
BeanUtils.copyProperties(request, response);
}
}
@Component
public class HartbeatAction implements ServerAction<HartbeatPackage, HartbeatPackage> {
@Override
public void execute(ChannelHandlerContext ctx, HartbeatPackage request, HartbeatPackage response) {
}
}
@Component
public class LoginAction implements ServerAction<LoginRequestPackage, LoginResponsePackage> {
@Override
public void execute(ChannelHandlerContext ctx, LoginRequestPackage request, LoginResponsePackage response) {
response.setSucceed(false);
}
}
代碼雖短,思想重要。所有 ServerAction 接口的實現類只要和SpringBoot集成了,就會被自動註冊,整合到業務處理流程當中。
由於是Demo程序,就沒有添加數據庫相關的代碼。