# sunrise **Repository Path**: hieve/sunrise ## Basic Information - **Project Name**: sunrise - **Description**: 基于Netty实现的高性能游戏服务器框架 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 2 - **Created**: 2025-09-09 - **Last Updated**: 2025-09-09 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # sunrise ## 简介 分布式游戏服务器框架,底层网络通信基于netty实现。 节点间使用rpc进行通信,多个节点只需连接同一中心服,即可实现通信。 每个节点启动时注册当前节点所管理的rpc方法,轻松实现模块的拆分与组合 。 不依赖任何第三方中间件,减少开发与部署的负担,只需要java环境就可以运行。 ## 框架结构 sunrise ├── sunrise-common -- 基础网络服务 ├── sunrise-center -- 中心服 ├── sunrise-external -- 对外服 ├── sunrise-game -- 游戏服 ├── sunrise-global -- 公共服 ├── sunrise-http -- http服务 ├── sunrise-rpc -- rpc服务 ├── sunrise-gen -- 代码生成 ├── sunrise-tables -- 配置表生成 └── sunrise-client -- 测试客户端 ## 框架图 采用分布式设计,通过中心节点同步其他服务信息,所有服务均支持动态增加和减少。 对外服与game服之间互相连接、多个game服之间互相连接、game服与公共服之间互相连接、多个公共服之间互相连接。 对外服、game服、公共服、http服均与中心服互相连接,定时向中心服上报当前服务信息(ip+port+type)。 中心服会向所有服务发送此服务所需要的其他节点信息,例如: game服需要对外服、其他game服、所有公共服的信息,收到地址后检测连接。 公共服需要game服、其他公共服的信息,收到地址后检测连接。 http服需要对外服的地址和game服的id, 客户端发送url请求到http服务,会为此玩家分配一个对外服地址,对外服保持与用户(玩家)的长连接。 同时,会分配一个game服节点,成功连接对外服后,会将此玩家后续的所有消息都转发给同一game。 玩家断线重连时,http服务会将上次所连接的gameId发给客户端,保证重连时回到原先的game节点。若此game已经下线,则会分配新的game。 game服和公共服内部均拥有一个rpc节点(详见rpc模块下RpcNode,包含一个服务器(绑定端口)和若干个客户端(连接到其他rpc节点)),rpc节点间采用rpc进行通信。 单个进程仅能有一个rpc节点,不同进程节点间会互相发送自身的注册方法列表,断开连接时则会清理,从而保证调用call时,能找到注册此方法的节点。 ![_20241008235730.png](https://s2.loli.net/2024/10/08/C5NSBzgtDVvdixq.png) ## 启动 ### 安装 1.安装maven [maven quick start](https://www.baidu.com/s?ie=utf-8&f=3&rsv_bp=1&tn=baidu&wd=maven%E5%AE%89%E8%A3%85%E4%B8%8E%E9%85%8D%E7%BD%AE&oq=linux%2520%25E4%25BD%25BF%25E7%2594%25A8maven&rsv_pq=9507299a00077c8e&rsv_t=de5fG74XgevZ%2BcgHjoLPiPlPq5iidHVv8BmQ4ZoqjP8fv84QUnAThJWvpy4&rqlang=cn&rsv_enter=1&rsv_dl=ts_1&rsv_sug3=13&rsv_sug1=20&rsv_sug7=100&rsv_sug2=0&rsv_btype=t&prefixsug=maven%25E5%25AE%2589%25E8%25A3%2585%25E4%25B8%258E&rsp=1&inputT=8493&rsv_sug4=34653) 2.配置镜像,使用腾讯云镜像 ```xml tencent tencent maven http://mirrors.cloud.tencent.com/nexus/repository/maven-public/ central ``` 3.下载依赖项目 [sunrise-common](https://gitee.com/lieng618/sunrise-common) 4.进入sunrise-common根目录下,执行命令 ```cmd mvn install ``` 5.进入项目根目录sunrise下,执行命令,生成jar包 ```cmd mvn clean package ``` 6.安装jdk,需求jdk版本>=21 [download](https://pm2.keymetrics.io/docs/usage/quick-start/) 7.安装mysql,推荐mysql5.7 [download](https://www.mysql.com/) ### Windows环境 #### 环境配置 - 1.修改config目录下所有配置文件,默认情况下,只需修改配置jdbc地址 - 2.修改config目录下game-config.properties,修改config.path路径 - 3.修改start/create_sql_table.bat脚本文件,配置jdbc地址与本机mysql安装路径 - 4.运行start/create_sql_table.bat,创建数据表 #### 快速启动 - 1.运行start/server_run_all.bat,启动全部服务器 - 2.运行start/client.bat,启动客户端,可启动多个进行测试 #### 编译器启动 - 1.项目根目录,右键选择pom.xml,在idea中打开 - 2.启动中心服 启动类CenterServerStartUp,接受两个参数:{ args[0]:config path args[1]:center_id },分别表示配置路径、启动中心服id - 3.启动对外服 启动类ExternalServerStartUp,接受两个参数:{ args[0]:config path args[1]:external_id },分别表示配置路径、启动对外服id - 4.启动跨服 启动类GlobalServerStartUp,接受两个参数:{ args[0]:config path args[1]:global_id },分别表示配置路径、启动跨服id - 5.启动游戏服 启动类GameServerStartUp,接受两个参数:{ args[0]:config path args[1]:gameId },分别表示配置路径、启动游戏服id - 6.启动http服务 启动类HttpServerStartUp - 7.启动客户端 启动类ClientStartUp 所有模块启动顺序不分先后,同一服务id不能重复 ### Linux环境 #### 环境配置 - 1.修改config目录下所有配置文件,需修改配置的jdbc地址,根据实际修改不同配置中的地址 - 2.修改config目录下game-config.properties,修改config.path路径 - 3.修改start/create_sql_table.sh脚本文件,配置jdbc地址与本机mysql安装路径 - 4.运行start/create_sql_table.sh,创建数据表 #### 快速启动 - 1.安装pm2 [pm2 quick start](https://pm2.keymetrics.io/docs/usage/quick-start/) - 2.云服务器需在控制台安全组开放端口,例如:3306(mysql)、8000(center)、8090(http)、10000-100010(external可预留多个)、20000-20010(rpc可预留多个) - 3.若所有模块运行在同一机器下,则需修改对外服配置中的report.address,配置为公网ip;若运行在不同机器中,确保所有配置中的master.address均为中心服机器的公网ip,所有模块的report.address需配置为各自机器的公网ip - 4.运行start/server_run_all.sh,启动全部服务器 - 5.推荐使用windows系统运行start/client.bat,需将client-config.properties中的http地址配置为http模块所绑定的服务器地址 ## 网络模块 抽象了网络通信模块,基于netty进行封装,创建服务器和客户端更加轻松,当前项目的模块都是在此模块基础上进行扩展。 [sunrise-common](https://gitee.com/lieng618/sunrise-common) ## 对外服 对外服内部创建BaseServer,监听客户端连接和游戏服连接,负责消息的转发;包含一个上报客户端ReportClient,向中心服上报自身地址。 对外服内部也包含一个websocket服务器,用于接收来自ws协议的客户端连接,启动后,当前进程会同时支持tcp与ws的连接。 默认规则为:tcp监听端口+1则为ws监听端口;假设tcp端口为10000,则ws端口为10001。 external_system表保存了对外服信息,启动时会检测此服务id是否已经在运行中,用于保证当前服务的唯一性。 对外服和game通信的数据结构为GameMessage,收到客户端发来的字节数据后,将客户端信息包装到GameMessage中发送给此客户端要连接的game。 要连接的gameId通过http模块获得,约定在客户端与对外服建立连接后发送的第一条消息中包含gameId,若解析出的gameId不存在则会关闭连接。 ```java public class ExternalServer { private final int serverId; private String ip = null; private int port; private final DbService dbService; private BaseServer server; private ExternalWsServer wsServer; } public class GameMessage extends BaseMessage { private long connectionId; private int gameId; private byte[] bytes; private int from; } ``` ## 游戏服 游戏服内部创建了多个BaseClient,连接到多个对外服(connectToExternal);包含一个上报客户端ReportClient,向中心服上报自身地址。 创建了一个RpcNode,用于rpc的注册与调用。 连接到多个对外服的客户端采用同一个线程组、同一个消息管理器GameMasterMessageManager,所有对外服接收到的玩家消息,都会放入同一个消息队列中,进行单线程处理,rpc调用返回也会在此线程中处理。 注意如果游戏服注册了rpc方法,收到其他节点发来的调用时,会在RpcNode.rpcServer中的消息管理器(RpcServerMessageManager)中进行处理,与GameMasterMessageManager的处理器不在同一线程。 因此,game注册的rpc服务只能进行公共逻辑的处理,不能对玩家数据进行操作,以免出现多线程问题。 ```java public class GameClient { private final int serverId; private final DbService dbService; private EventLoopGroup group = null; private Bootstrap b = null; private GameMasterMessageManager gameMasterMessageManager = null; private GameRpcMessageManager gameFromGlobalMessageManager = null; private Map connectToExternals = new ConcurrentHashMap<>(); private ReportClient reportClient; private RpcNode rpcNode; } ``` ## 公共服 公共服仅需创建一个RpcNode,注册当前服务要提供的rpc方法。 提供两种启动方法:1.rpcNode.start(new DbService()) 2.rpcNode.start(port) 推荐使用第一种方式,内部通过数据表rpc_server_system检测服务id的唯一性;也可以传入端口,直接启动。 多个rpc服务通信只需连接同一个中心服即可,所有模块从配置文件中读取中心服地址,内部会调用connectMaster()。 ```java public class GlobalServerStartUp { public static void main(String[] args) { // args[0]:config path args[1]:global_id if (args.length == 0) { args = new String[] { "./config/global-config.properties", "2" }; } ConfigReader.loadConfig(args[0]); var rpcNode = RpcNodeManager.createRpcNode(Integer.parseInt(args[1])); // 添加当前模块要注册的rpc ArrayList list = new ArrayList<>(); list.add("org.sunrise.game.global.service.union"); list.add("org.sunrise.game.global.service.pvp"); CallUtils.init(rpcNode.getNodeId(), list, CallEnum.class); //启动 rpcNode.start(new DbService()); //内存检测 Utils.startMemoryCheck(); } } ``` ## rpc服务 在rpc模块下,包org.sunrise.game.rpc.example为完整的使用用例。 rpc模块无需单独启动,用于其他模块使用时依赖此模块。 一个完整的rpc服务节点包含:一个rpcServer服务、若干个连接到其他rpc服务的BaseClient、一个reportClient;使用时通过RpcNodeManager.createRpcNode()创建。 注意事项: - CallUtils.init()方法,传入:节点名称、注册的服务包地址、call枚举类。 - CallEnum.java 相当于双方的通信约定,此文件可使用Gen模块进行生成(类GenRpcStartUp,需更改自己的服务包路径和生成路径)。 ## Http服务 http模块包含一个上报客户端ReportClient,连接到中心服,获取对外服地址和gameId列表,信息会保存至responses中,定时更新。 客户端以发送url请求的方式获取服务器地址和gameId。 uidGameIds保存了给玩家分配的gameId,保证断线重连后请求到的是同一个gameId。 ```java public class HttpServerStartUp { public static void main(String[] args) { // args[0]:config path if (args.length == 0) { args = new String[] { "./config/http-config.properties" }; } ConfigReader.loadConfig(args[0]); Utils.startMemoryCheck(); var httpServer = new HttpServer(8080); httpServer.connectMaster(); httpServer.start(); } } public class HttpServer { private final int port; private final Server server; public static final Map responses = new HashMap<>(); private final Random random = new Random(); // uid-gameId private final Map uidGameIds = new HashMap<>(); private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); private ReportClient reportClient; } ``` ## 客户端 客户端与服务器之间使用protobuf,协议结构如下: ``` message MBasePacketData { TOPIC packet_type = 1 ; //封包类型ID uint32 packet_id = 2 ; //封包ID bytes packet_data = 3 ; //封包内容 } ``` 目前编写了一个客户端通信实例,实现了一个简易的界面绘制,登录成功后进入地图,可进行多人同步移动。