egret+java小游戏服务端10-重构指令传输,单线程改为多线程阻塞队列


前言

服务端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);
    }

}

显然是不能再用了
算了因为暂时用不上这玩意,还是放在后面解决吧

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值