Netty+SpringBoot搭建游戏服务器(带控制台客户端)

Netty与SpringBoot的游戏服务器

目录

Netty与SpringBoot的游戏服务器

前言

一、项目整体流程

二、项目启动

1.克隆或下载该项目

2.导入idea,或者上一步用idea导入

3.导入数据库

4、启动redis

5、启动登录和游戏服务器

6、启动客户端操作

三、项目设计概要

1、整合springboot

2、整合google-protobuf

3、order的策略模式

总结


前言

该项目为2017年入职第一家公司时的一个项目,当时仅凭着一腔热血,只身进入公司,也可以不要工资,只要能让我学习到项目经验,当然这不可取,实在是条件比较艰苦。言归正传,当初所使用的技术为mybatis+netty,还未使用到spring,其实是我当时还不会用。最近准备学习微服务和rpc等相关技术,所以又把这个项目掏出来重新复习一下netty了。

项目架构:

springboot-2.1.7

netty-4

fastjson

jedis(连接池)

mybatis

google-protobuf(消息传输协议)

项目亮点:

1、登录服务器与游戏服务器可分离,可在登录服务器做多个游戏服务器地址实现负载均衡

2、通过验证redis中的token判断是否有效登录,如无登录信息则无法进入游戏服务器,使用redis管理房间和大厅,提升IO读写速度,加快服务器响应

3、使用protobuf来最大程度减少网络请求中的时间和空间消耗

4、使用策略模式+线程池对消息做异步处理,提高服务器处理效率


一、项目整体流程

Netty+SpringBoot搭建游戏服务器(带控制台客户端)_第1张图片

  1. 客户端通过已知的服务器域名/IP进行登录

  2. 登录成功后服务器返回token和uid以及游戏的ip及端口(可以跟登录服务器同一ip),客户端再次连接游戏服务器
  3. 客户端连接成功后发送登录成功的验证请求,同时也是获取大厅信息的请求
  4. 登录状态验证成功后拿到大厅信息,选择进入大厅内的某不同倍率的房间
  5. 进入房间后服务器将返回该房间所有可以进入的座位,需要有座位才可进行游戏(通过座位来对游戏的最大同时响应连接数做限制)
  6. 进入座位后即可准备开始游戏:获取第一组手牌
  7. 观察当前手牌的牌型,保留期望牌型所需的牌,可进行替换,,例如:3,4,5,6,J 前面四张可能组成顺子,则保留前面四张,进行替换
  8. 替换将从准备好的牌组中顺序增加指定张数的牌,与保留的牌组成新的牌型,可以全部保留,则实际不替换,也可全部不保留,则全部从新牌组中抽取
  9. 根据牌型判断最终结果,如果结果有分,则可以进行比倍,比倍将获得更多分数(具体比倍的逻辑有点忘了,随便整了个,如果有会玩这个游戏的可以留言做出修改)
  10. 比倍结束或不比倍之后,刷新改用户的总分数,游戏结束
  11. 正常退出需退出座位再退出房间,或断开连接后服务端将在一定时间后清除用户信息

二、项目启动

1.克隆或下载该项目

Git clone https://gitee.com/pineappleLB/mx-work.git

2.导入idea,或者上一步用idea导入

链接导入idea:file -> new -> Project from Version Control -> Git -> 粘贴上方链接

下载后导入:file -> Open -> 选择到文件位置(选择maven项目)

3.导入数据库

新建数据库`mxsoha`,将mxsoha.sql中的sql语句导入到数据库

4、启动redis

5、启动登录和游戏服务器

可以修改单独从LoginServer中启动,为GameServer提供负载均衡,也可直接启动GameServer登录和游戏一起

6、启动客户端操作


三、项目设计概要

1、整合springboot

@SpringBootApplication
public class GameApplication implements CommandLineRunner {

    @Autowired
    private GameServer gameServer;

    @Autowired
    private LoginServer loginServer;

    @Autowired
    private CommandLoader commandLoader;


    public static void main(String[] args) {
        new SpringApplicationBuilder(GameApplication.class).web(WebApplicationType.NONE).run(args);
    }

    @Override
    public void run(String... args) throws Exception {
        // 依次启动服务器
        new Thread(loginServer).start();
        new Thread(gameServer).start();
        // 加载指令映射
        commandLoader.loadCommand();
    }
}

使用CommandLineRunner 接口在spring加载完成后启动nettyServer,

new SpringApplicationBuilder(GameApplication.class).web(WebApplicationType.NONE).run(args);

使用SpringBoot的非Web方式启动,不占用Web端口

2、整合google-protobuf

@Override
protected void initChannel(SocketChannel sc) throws Exception {
        sc.pipeline()
//              .addLast(new IdleStateHandler(5, 0, 0))
                .addLast(new ProtobufVarint32FrameDecoder())
                //给服务器添加编码解码器
                .addLast(new ProtobufDecoder(BasicProto.BaseMessage.getDefaultInstance()))
                .addLast(new ProtobufVarint32LengthFieldPrepender())
                .addLast(new ProtobufEncoder())
                //添加服务端业务处理类
                .addLast(loginServerHandler);
}

proto文件:

/*请求消息*/
message BaseMessage {
    int32 order = 1; //命令
    bytes body = 2; // 消息体
}

// 登录请求
message LoginRequest {
    string name = 1; // 姓名
    string pass = 2; // 密码
}

// 登录返回实体
message LoginResponse {
    Res res = 1; // 请求返回状态
    int32 lobbyPort = 2; // 大厅端口
    string lobbyHost = 3; // 大厅地址
    User user = 4; // 用户信息
}

登录解析:

@Override
public void doMessage(BasicProto.BaseMessage msg, IOSession session) throws Exception {
        UserProto.LoginRequest loginRequest = UserProto.LoginRequest.parseFrom(msg.getBody());
        String name = loginRequest.getName();
        String pass = loginRequest.getPass();
        //调用service中的方法验证用户信息,并返回完整的用户信息
        User u = loginUserService.userLogin(name, pass);
        // 登录逻辑判断
        // 省略....
}

添加protobuf的编解码类,以及全局protobuf基类原型,通过proto类的parseForm方法将基类中的实际请求参数转换为指定的类

3、order的策略模式

    private Map> handlerMap = new ConcurrentHashMap<>();
    private final String BASE_PACKAGE = "club.pinea.handler";
    private final String RESOURCE_PATTERN = "/**/*.class";
    /**
     * 加载command对应的处理类
     */
    public void loadCommand() {

        //spring工具类,可以获取指定路径下的全部类
        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
        try {
            String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                    ClassUtils.convertClassNameToResourcePath(BASE_PACKAGE) + RESOURCE_PATTERN;
            Resource[] resources = resourcePatternResolver.getResources(pattern);
            //MetadataReader 的工厂类
            MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
            for (Resource resource : resources) {
                //用于读取类信息
                MetadataReader reader = readerFactory.getMetadataReader(resource);
                //扫描到的class
                String classname = reader.getClassMetadata().getClassName();
                Class clazz = Class.forName(classname);
                //判断是否有指定主解
                Head anno = clazz.getAnnotation(Head.class);
                if (anno != null) {
                    //将注解中的类型值作为key,对应的类作为 value
//                    handlerMap.put(classname, clazz);
                    Object bean = SpringContextUtils.getBean(clazz);
                    if (bean instanceof TCPMessage) {
                        TCPMessage tcpMessage = (TCPMessage) bean;
                        handlerMap.put(anno.value(), tcpMessage);
                    }
                }
            }
        } catch (IOException | ClassNotFoundException e) {
        }
    }

    /**
     * 获取处理类
     *
     * @param command
     * @return
     */
    public TCPMessage getHandler(int command) {
        return handlerMap.get(command);
    }

通过ResourcePatternResolver类获取项目中指定包下的类,并判断是否有包含自定义注解,将注解对应的order与消息中的order值对应,即可在netty的channelRead中获取到该次请求消息应该由哪个handler处理,即调用getHandler方法传入baseMessage中的order属性即可

总结

项目地址:https://gitee.com/pineappleLB/mx-work

项目有部分内容未完全完成,仅作为学习框架之余的练手项目,如有兴趣可以自行修改完成,对项目有疑问或建议可以评论留言或者在gitee留言

你可能感兴趣的:(java,服务器,redis,java,netty,spring,boot,游戏开发)