前言
服务端GitHub地址:https://github.com/griii/Hak-Server
小游戏网址:https://www.guorii.cn/hak
旧版指令传输
原先的指令传输仅仅通过netty的channelRead回调函数中调用,如下:
@Override
protected void channelRead0(ChannelHandlerContext ctx,
TextWebSocketFrame msg) throws Exception { // (1)
//System.out.println("接收到text消息..." + msg.text());
//先对比uid是否正确
//将消息转为Instruct指令形式,然后交付给manage处理
Channel incoming = ctx.channel();
JSONObject jsonObject = JSONObject.fromObject(msg.text());
Instruct instruct = new Instruct(jsonObject.get("order")+"",jsonObject.getLong("time"),jsonObject.get("obj"),jsonObject.getInt("roomId"),Integer.parseInt(jsonObject.get("uid").toString()));
if (instruct.getOrder().equals("JOIN")){
if (!channelMap.containsKey(incoming.id()+"")){
System.out.println("指令异常返回");
return;
}
System.out.println("map更新" + channelMap.toString());
channelMap.put(instruct.getUid() + "",channelMap.remove(incoming.id()+""));
System.out.println("map更新完毕" + channelMap.toString());
msgManage.join(instruct, userDao.getNameById(instruct.getUid()));
return;
}
if (!channelMap.containsKey(instruct.getUid() + "")){
System.out.println("不存在该UID...");
return;
}
//System.out.println("指令存入队列中...");
msgManage.addInstruct(instruct);
}
Netty作为NIO,是非阻塞轮询的,这个方式就有两个缺点:
1、轮询以单线程形式呈现,多个游戏房间线程争抢,会出现有些房间延迟巨大,有些房间延迟则很小的不公平现象。
2、原先的传输基于旧的Room模块(房间仅作为静态对象存在),前面重构了整个Room,自然这个旧的传输系统也要重构了。
综上,重构思路为:
每个Room房间维护阻塞队列,且有一个线程不断地取队列中的指令消息后执行相应逻辑。
新的指令传输
新建阻塞队列
首先在AbstractRoom中添加一个局部变量
protected BlockingQueue<Instruct> instructs;
在Init中初始化
//以下为生命周期的默认实现==============================================
@Override
public void init(){
this.players = new ConcurrentHashMap<>(MapCapacityUtil.tableSizeFor(getCapacity()));
this.instructs = new ArrayBlockingQueue<Instruct>(64);
}
好像还是第一次使用阻塞队列,不太清楚这个必须要设置的capacity作用,去看一看
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
用于初始化一个数组,好像并没有什么用,所以改成直接用链表算了
this.instructs = new LinkedBlockingQueue<>();
增加IRoom接口方法
//在Room维护的阻塞队列里添加指令消息
void addInstruct(Instruct instruct);
然后在AbstractRoom中添加默认实现
@Override
public void addInstruct(Instruct instruct){
if (instructs == null)
throw new RuntimeException("添加指令消息时还未init!");
instructs.add(instruct);
}
在Netty的Channel监听中添加map
public static Map<String, IRoom> roomMap = new HashMap<>(512);
这个HashMap用于监听到消息后通过用户ID映射到相对应的Room中,然后向Room中的阻塞队列添加消息即可。由于修改操作均在一个线程中完成(匹配线程),所以不设为Concurrent了。
那么就需要在匹配完成后,向这个Map中添加映射:
@Override
public void startThreadRoom(IRoom room) {
//向RoomMap中添加映射
for (String s:room.getUsers().keySet()){
TextWebSocketFrameHandler.roomMap.put(s,room);
}
}
最后修改ChannelRead逻辑
//System.out.println("指令存入队列中...");
roomMap.get(instruct.getUid()+"").addInstruct(instruct);
//msgManage.addInstruct(instruct);
将IRoom接口改为继承Runnable
在AbstractRoom中添加默认的线程Run方法实现:
//线程
@Override
public void run(){
Instruct instruct = instructs.poll();
PlayerPeople player = players.get(instruct.getUid()+"");
if (player == null){
System.out.println("没有找到该用户!");
return;
}
String order = instruct.getOrder();
try {
//通过Spring的BeanFactory获取
String strategyName = order + STRATEGY;
InstructStrategy is = (InstructStrategy) applicationContext.getBean(order + STRATEGY);
//InstructStrategy is = (InstructStrategy)(Class.forName("com.guorui.hak.entity.instruct.strategy.impl." + strategyName).getConstructor().newInstance());
is.instructOrder(instruct,player);
//player.getClass().getMethod(order, Instruct.class).invoke(player,instruct);
}catch (Exception e){
e.printStackTrace();
System.out.println("指令调用失败!该用户没有使用该指令的权限!");
}
}
开始时将IRoom也提交给线程池,相当于每个房间维护一个指令解析线程和一个广播线程
mainLogicThreadPool.submit(mainGameLogicThread);
mainLogicThreadPool.submit(room);
@Bean
public ExecutorService mainLogicThreadPool(){
//线程池仅容载每个游戏房间的内容,不是容载每个用户的数量
//单个服务器的线程池,要求:核心100线程,无法添加线程,核心线程空闲10分钟后销毁
//也就是说等待队列使用不存储的SynchronousQueue即可,拒绝策略为向用户返回拒绝值
//线程乘以2的原因是每个房间维护一个指令解析线程和一个广播线程
return new ThreadPoolExecutor(MAX_THREAD_THRESHOLD*2,
MAX_THREAD_THRESHOLD*2,
10,
TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
Executors.defaultThreadFactory(),
new RejectedHandler());
}
至此,单线程的指令解析MsgManage彻底被废弃。
旧版指令系统装饰器重构
由于旧版装饰器是基于旧版Room模块的:
import com.guorui.hak.entity.room.Room;
public class GetIgnoreBarrierDecorator extends Decorator {
@Override
public void dec() {
Room.players.put(component.getUid() + "",this);
}
}
显然是不能再用了
算了因为暂时用不上这玩意,还是放在后面解决吧