- 功能
- 客户端交互
- 客户端获取服务器列表客户端
- 获取公告
- CDN服务
- 资源包上传
- 更新资源
- 服务端交互
- 服务端验证登录
- 支付分发给服务端
- 数据配置
- GM功能
- 数据统计
- 技术
- 高级语言--Java8
- 框架--SpringBoot2.0
- 项目管理--Gradle
- 缓存--redis
- 数据库Mysql5.6
- 通信框架--Netty
- 传输框架--Protostuff
- 启动流程
- 不占用端口启动
- 通过注解获取协议进行初始化
- 初始化线程池用于有序处理客户端消息
- 初始化游戏数据
- 向后台获取游戏配置数据
- 初始化定时任务用于定时更新数据
- 启动Netty
- 关闭流程
- 实现接口ApplicationListener<ContextClosedEvent>
- 配置中注册监听
- 服务器关闭前处理临时数据落地到数据库
- 开发总结
- 关于缓存
- 缓存使用的redis
- 使用Jackson2JsonRedisSerializer替换了value的序列化与反序列化,但是对应map的序列化如果有排序要求,即使使用ConcurrentSkipListMap有序的集合,依然在反序列化的时候回出现顺序错误,在redis可视化工具看到的数据顺序是对的
- 关于成就设计
- 成就采用的是spring自带的事件系统
- 业务处理完回到数据完成后可以提交事件
- 在成就或任务系统中对事件进行处理
- 关于线程池
- 重写了拒绝策略
/**
* 在线程池提交任务的最后一步——被线程池拒绝的任务,可以在拒绝后调用队列的put()方法,让任务的提交者阻塞,直到队列中任务被被线程池执行后,队列有了多余空间,调用方才返回
*/
private static class BlockCallerPolicy implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
try {
executor.getQueue().put(r);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 关于业务初始化
- 使用了接口
/**
* 初始化接口
*
* @date :2019/3/14 17:15
*/
public interface InitBaseHandler {
/**
* 实现此方法将在服务器启动时进行初始化操作
*/
void init();
}
- 此接口可以在应用初始化时调用,将调用实现此接口的所有实现类
LogHandler.info("no2.初始化游戏数据......");
applicationContext.getBeansOfType(InitBaseHandler.class).values().forEach(InitBaseHandler::init);
- 关于定时器
- 先使用的每分钟一次的定时器
/**
* 定时更新
*/
@Override
public void init() {
LogHandler.info("初始化定时任务");
int second = Calendar.getInstance().get(Calendar.SECOND);
ExecutorHandler.scheduledExecutorService.scheduleAtFixedRate(() -> {
Calendar calendar = Calendar.getInstance();
int minute = calendar.get(Calendar.MINUTE);
if (minute % INTERVAL == 0) {
playerService.updatePlayer();
playerService.updatePlayerData();
globalService.updateGlobalData();
}
}, 60 - second, 60, TimeUnit.SECONDS);
}
- 后期会优化为扩展性更高的类似linux的cron月日时分定时器
- 没有考虑年
- 如果需要可以增加季度
- 没有考虑秒
- 聊天队列会单独使用秒定时器
- 数据解码
- 协议ID
- 参数长度
- 是否压缩
- 参数