# sunrise **Repository Path**: lieng618/sunrise ## Basic Information - **Project Name**: sunrise - **Description**: 高性能分布式游戏服务器框架 - **Primary Language**: Java - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 2 - **Created**: 2024-07-17 - **Last Updated**: 2026-04-14 ## Categories & Tags **Categories**: Uncategorized **Tags**: 游戏, 游戏服务器, 游戏开发 ## README 此项目不再维护,此项目已移植升级到新的仓库,对架构进行了重构。代码结构更好,可读性更强,更加规范。 新仓库地址:[sunrise-game-frame](https://gitee.com/lieng618/sunrise-game-frame) # sunrise ## 简介 sunrise 是一个分布式游戏服务器框架,底层网络通信基于 Netty实现。 框架不依赖任何第三方中间件,减少开发与部署的负担,只需具备 Java 环境即可运行。 ## 框架结构 sunrise 由多个模块组成: - **sunrise-common**:基础网络服务模块。 - **sunrise-center**:中心服模块。 - **sunrise-external**:对外服模块。 - **sunrise-game**:游戏服模块。 - **sunrise-global**:公共服模块。 - **sunrise-http**:HTTP 服务模块。 - **sunrise-rpc**:RPC 服务模块。 - **sunrise-gen**:代码生成模块。 - **sunrise-tables**:配置表生成模块。 - **sunrise-client**:测试客户端模块。 - **sunrise-bot**:压测机器人模块。 ## 框架图 框架采用分布式设计,通过中心节点同步其他服务信息,所有服务均支持动态增加和减少。 ![sunrise.png](https://s2.loli.net/2024/10/08/C5NSBzgtDVvdixq.png) - 对外服与游戏服之间互相连接。 - 每个游戏服内部包含一个RPC节点,每个公共服内部包含一个RPC节点。 - 游戏服与每个公共服、所有的公共服之间互相连接,但游戏服间的RPC节点并没有连接(如果游戏服之间互连会导致业务逻辑混乱,一般公共数据的处理都放在公共服)。 - 对外服、游戏服、公共服、HTTP 服均与中心服连接,并定时上报当前服务信息(IP、端口、类型)。 - 客户端与对外服建立连接,会先从Http服务获取对外服地址,并且会分配一个游戏服的id,后续的请求都会发给此游戏服进行处理。 - 中心服将其他节点信息同步给需要的服务。 - 游戏服需要对外服的信息,收到中心服下发的地址后,会检测连接。 - RPC节点需要其他RPC节点的信息,收到中心服下发的地址后,会检测连接。 - Http服需要对外服和游戏服的信息,收到中心服下发的地址后,将信息更新到内存中。 ## 开发指南 详见文档,包含业务功能的完整开发流程。 [业务模块开发指南](https://gitee.com/lieng618/sunrise/blob/master/game/%E4%B8%9A%E5%8A%A1%E6%A8%A1%E5%9D%97%E5%BC%80%E5%8F%91%E6%8C%87%E5%8D%97.md) ## 网络模块 - 抽象了网络通信模块,基于 Netty 进行封装,简化了服务器和客户端的创建。 - 所有模块的网络通信均基于此基础模块进行扩展。 - 默认创建的服务器为BaseServer,默认创建的客户端为BaseClient。 [sunrise-common](https://gitee.com/lieng618/sunrise-common) ## 对外服 对外服负责监听客户端连接和游戏服连接,转发消息。 - 包含一个 `ReportClient`,连接到中心服。 - 包含一个 `BaseServer`,监听客户端连接和游戏服的`BaseClient`的连接。 - 对外服与客户端建立连接后,规定接收到的第一条消息为校验消息,约定消息前缀为"CLIENT_CONNECT_",后面拼接上要连接的gameId。若解析出的gameId不存在则会关闭连接。 - 对外服与游戏服建立连接后,规定接收到的第一条消息为校验消息,约定消息前缀为"GAME_CONNECT_",后面拼接上游戏服自己的gameId。若解析出的gameId已存在则会关闭连接。 - 消息处理类为`ExternalServerHandler`和`ExternalWsServerHandler`。 - 对外服和游戏服通信的数据结构为 `GameMessage`,收到客户端发来的字节数据后,将字节数据和客户端信息包装到`GameMessage`中发送给此客户端连接的游戏服。收到游戏服的数据时,将`GameMessage`中的字节数据发给对应的客户端。 - 包含一个 `WebSocket` 服务器,同时支持 WebSocket 协议。 - 默认规则:TCP 端口 + 1 为 WebSocket 端口(如 TCP 端口为 `10000`,则 WebSocket 端口为 `10001`)。因此一个对外服会占用2个端口。 - `external_system`sql表会记录所有对外服的运行状态,启动多个对外服时,通过此表检测服务id是否重复,服务id可用时将会分配端口,启动服务器。 ## 游戏服 游戏服连接多个对外服,并创建 `RpcNode` 用于 RPC 注册与调用。 - 包含一个 `ReportClient`,连接到中心服。 - 包含多个 `BaseClient`,连接到多个对外服。 - 多个 `BaseClient`采用同一个消息管理器`GameMasterMessageManager`,所有从对外服接收到的玩家消息,都会放入同一个消息队列中,进行单线程处理。 - 包含一个`RpcNode`,用于RPC的注册与调用。 - 游戏服调用其他节点的RPC方法时,调用的返回会在消息管理器`GameMasterMessageManager`中处理。 - 如果游戏服注册了RPC方法,收到其他节点发来的调用时,使用的消息管理器为`RpcNode.rpcServer`中的`RpcServerMessageManager`,与`GameMasterMessageManager`消息管理器不在同一线程。因此,游戏服注册的RPC 服务不能直接对玩家数据进行操作,以免出现多线程问题。 - RPC方法内需要对玩家数据进行操作时,使用方法`AsyncEventManager.addAsyncEvent()`,添加异步任务,所有的异步任务都会在消息管理器`GameMasterMessageManager`中处理。 ## 公共服 公共服仅需创建一个 `RpcNode`,注册当前服务提供的 RPC方法。 - 公共服可以启动多个,注册不同的RPC方法,负责不同的业务模块。在配置文件中配置同一个中心服地址,即可让所有RPC节点互相连接。 - RPC节点互相建立连接时,会将当前节点注册的RPC方法发给对方。某个节点下线时,其他节点也会收到通知,移除下线节点所管理的RPC方法。 - RPC调用有两种模式,将调用发给全部节点、或者随机节点发送。 - 处理公共数据时,最好只用单个节点注册业务模块。比如聊天模块,只用一个节点存储聊天记录,就无需考虑多个节点如何保证数据的一致性。 - 不涉及到公共数据的处理时,就可以多个节点注册同一个业务模块。RPC调用时随机发送给某一个节点,不用关心哪个节点处理。 ## RPC 服务 RPC模块提供 RPC通信支持,无需单独启动,其他模块依赖此模块实现 RPC 调用。 - 包含一个 `ReportClient`,连接到中心服。 - 包含一个 `BaseServer`,监听客户端(其他的RPC节点内部的`BaseClient`)连接。 - 消息处理器为`RpcServerMessageManager`, 会处理其他节点的`BaseClient`发来的call调用,并将处理后的返回数据发回去。 - 包含多个 `BaseClient`,连接到其他RPC节点的 `BaseServer`。 - 消息处理器为`RpcClientMessageManager`,会向其他节点的`BaseServer`发送call调用,并对调用后的返回数据进行处理。 - 创建 RPC节点:`RpcNodeManager.createRpcNode(nodeId)` - 注册服务包路径 ```java var rpcNode = RpcNodeManager.createRpcNode(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()); ``` - 部署方式上,服务注册支持随意的拆分和组合,既可以全部放入同一个RPC节点,也可以拆分到不同的RPC节点。 发起call调用时,如果当前节点注册了此方法,则一定会由当前节点处理。 - 比如原本在跨服注册的公会服务,后期想改为本服公会,只需在游戏服进程注册公会服务,则后续发起的call调用一定会由本服RPC节点处理,无需修改业务代码就实现了业务迁移。 - `CallEnum.java`相当于双方的通信约定,此文件可使用Gen模块进行生成(类`GenRpcStartUp`,需更改自己的服务包路径和生成路径)。 - 包[`org.sunrise.game.rpc.example`](https://gitee.com/lieng618/sunrise/tree/master/rpc/src/org/sunrise/game/rpc/example)为完整的使用用例,包含多个RPC节点的启动、多个节点RPC方法的创建与调用 ## HTTP 服务 HTTP 服务从中心服获取对外服地址和 gameId列表,客户端以发送url请求的方式获取服务器地址和gameId。 - 包含一个 `ReportClient`,连接到中心服。 ## GM后台 中心服内部创建web服务器,为后台网页提供api接口。 - `AdminServer`提供接口,`admin-ui`目录下为前端页面。 - 中心服配置文件中配置后台登录账号、密码,网页端口号。 - 中心服启动时会在控制台输出后台地址,浏览器输入地址后进行登录。 - ``` INFO Server.start(AdminServer.java:113) AdminServer started on : http://192.168.105.174:8010/ ``` - 目前是由中心服创建的Javalin Web服务器托管静态文件,后续可通过nginx进行托管。 - 实现功能:节点监控、节点管理、配置更新、发送邮件、玩家下线、操作日志、用户管理。 ## 客户端 客户端通信示例: - sunrise-client: [Java版本通信客户端](https://gitee.com/lieng618/sunrise/tree/master/client/src/org/sunrise/game/client) - sunrise-goldminer:[网页版通信客户端](https://gitee.com/lieng618/sunrise-goldminer) - sunrise-goldminer-wx:[微信小游戏版通信客户端](https://gitee.com/lieng618/sunrise-goldminer-wx) - sunrise-client-unity:[unity版通信客户端](https://gitee.com/lieng618/sunrise-client-unity) WebSocket通信流程解析: - 从http服务器获取game_id和external_address,测试版本可以直接连接ip+port,但正式版本需要使用域名和ssl证书,所以连接的最终地址里,把ip替换为域名,拼接上/ws/port,后端由nginx解析转发给对应的端口。 ```js // 第一步:请求 game_id // 对应 url: https://url/game_id?uid=xxx const gameIdUrl = `https://${networkConfig.serverdomain}/game_id?uid=${uid}`; const gameIdData = await requestAsync(gameIdUrl); const serverId = gameIdData.server_id; // 第二步:请求 external_address // 对应 url: https://url/external_address?type=websocket const addressUrl = `https://${networkConfig.serverdomain}/external_address?type=websocket`; const addressData = await requestAsync(addressUrl); // 返回的 JSON 结构中有 address 字段,格式如 "192.168.1.5:10001" const rawAddress = addressData.address; const parts = rawAddress.split(':'); const targetPort = parts[1]; // 拿到对外服端口 // 正式上线需要域名,所以获取的ip+端口是没法用的 // 所以ip使用域名,拼接上/ws/port,后端会由nginx解析转发到对应的端口 const wsUrl = `wss://${networkConfig.serverdomain}/ws/${targetPort}`; ``` - nginx配置 - 此配置会转发给本机的指定端口,可根据环境配置为不同的服务器地址。 ```nginx server { listen 443 ssl; server_name goldminer.cloud www.goldminer.cloud; ssl_certificate /etc/nginx/cert/www.goldminer.cloud_bundle.crt; ssl_certificate_key /etc/nginx/cert/www.goldminer.cloud.key; ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; ssl_session_timeout 20m; ssl_verify_client off; # 动态匹配 /ws/端口号 # 客户端连接地址示例: wss://www.goldminer.cloud/ws/10001 location ~ ^/ws/(?\d+)$ { rewrite ^/ws/\d+ / break; # 将路径中的端口号变量,拼接到 proxy_pass 中 proxy_pass http://127.0.0.1:$forward_port; # WebSocket 标准头 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; # 传递真实 IP proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; } # 将 /game_id 和 /external_address 转发到 Java 的 HTTP Server (8090) location ~ ^/(game_id|external_address) { proxy_pass http://127.0.0.1:8090; # 转发给你的 HttpServer.java # 传递真实IP等头信息 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } ``` ## 启动 ### 安装 1. **安装 Maven** 参考 [Maven 官方安装指南](https://maven.apache.org/guides/getting-started/maven-in-five-minutes.html) 2. **配置镜像(腾讯云)** 在 `settings.xml` 中添加以下配置: ```xml tencent tencent maven http://mirrors.cloud.tencent.com/nexus/repository/maven-public/ central ``` 4. **构建 sunrise 项目** ```bash cd sunrise mvn clean package ``` 5. **安装 JDK(>=21)** [JDK 下载地址](https://jdk.java.net/archive/) 6. **安装 MySQL(推荐 5.7)** [MySQL 下载地址](https://dev.mysql.com/downloads/mysql/) ### Windows 环境 #### 环境配置 1. 修改 `config` 目录下的所有配置文件,配置 JDBC 地址。 2. 修改 `game-config.properties` 中的 `config.path`,配置数据表路径。 3. 修改 `center-config.properties` 中的 `admin.uipath`,配置前端静态资源路径。 4. 修改 `start/create_sql_table.bat`,配置 JDBC 地址与 MySQL 安装路径。 5. 执行 `start/create_sql_table.bat` 创建数据表。 #### 快速启动 1. 执行 `start/server_run_all.bat` 启动所有服务器。 2. 执行 `start/client.bat` 启动测试客户端。 #### 编译器启动 1. 在 IDE 中打开项目根目录下的 `pom.xml`。 2. 启动中心服: - 类:`CenterServerStartUp` - 参数:`{args[0]: config path, args[1]: center_id}` 3. 启动对外服: - 类:`ExternalServerStartUp` - 参数:`{args[0]: config path, args[1]: external_id}` 4. 启动跨服: - 类:`GlobalServerStartUp` - 参数:`{args[0]: config path, args[1]: global_id}` 5. 启动游戏服: - 类:`GameServerStartUp` - 参数:`{args[0]: config path, args[1]: gameId}` 6. 启动 HTTP 服务: - 类:`HttpServerStartUp` - 参数:`{args[0]: config path}` 7. 启动客户端: - 类:`ClientStartUp` > **注意**:所有模块启动顺序无先后之分,**同一服务的 ID 不能重复**,所有服务ID每次递增时+1。 > > 假设有两个对外服,服务ID可以分别为1、2。 > > 假设有两个游戏服,服务ID可以分别为1、2。 > > 假设有两个跨服,分别负责不同的模块,服务ID可以分别为1001、1002。 > > 跨服的服务ID不能与游戏服相同,(因为游戏服和跨服内部都包含一个RPC节点,所以需要使用不同的服务ID)。 > 跨服的服务ID从1001开始,与游戏服ID做出区分。中心服通过服务ID是否大于1000,判断RPC节点是否为跨服节点。 > > 节点ID的范围为1~4096,即最多支持4096个节点。 [配置详情](https://gitee.com/lieng618/sunrise-common/blob/master/src/org/sunrise/game/common/utils/IdGenerator.java) ### Linux 环境 #### 环境配置 1. 修改 `config` 目录下的配置文件,配置 JDBC 地址。 2. 修改 `game-config.properties` 中的 `config.path`。 3. 修改 `start/create_sql_table.sh`,配置 JDBC 地址与 MySQL 安装路径。 4. 执行 `start/create_sql_table.sh` 创建数据表。 #### 快速启动 1. **安装 PM2** ```bash npm install -g pm2 ``` 2. **开放端口** - MySQL: `3306` - Center: `8000` - HTTP: `8090` - External: `10000-10010` - RPC: `20000-20010` - GM:`8010` - 所有端口按需开放。 - 若所有模块运行在同一机器,则只需开放HTTP、External、GM端口。 - HTTP、External模块是外部客户端需要访问的,GM模块是GM后台网页需要访问的。 3. **配置 IP地址** - 若所有模块运行在同一机器,需配置对外服的 `report.address` 为公网 IP。 - 若模块运行在不同机器,确保所有模块的 `master.address` 为中心服公网 IP,`report.address` 为各自机器公网 IP。 - 核心就是保证所有模块能连接到中心服,以及模块之间能互相连接。 4. **配置日志等级** - 配置文件中的 `log.level` 默认为 `DEBUG` 模式,此模式下会打印大量的日志,方便调试,可根据不同场景进行修改。 5. **启动所有服务** ```bash cd start ./server_run_all.sh ``` 6. **启动客户端** ![client.png](https://picturebed.pixelarrayai.com/356/client.png) - 可在Windows下使用client模块下的客户端进行测试,实现了完整的登录流程,支持tcp与websocket两种通信方式。 - 此客户端实现了模拟发送协议消息,并支持多个玩家登录,每个玩家在自己的窗口中发送。 - 需修改 `client-config.properties` 中的 HTTP 地址为 HTTP 模块绑定的服务器地址。 - bot模块依赖于client模块,实现了动态增减客户端,可自定义协议,设置发送间隔与发送次数,对服务器进行压力测试。 7. **登录GM后台** - http://127.0.0.1:8010 - 初始账号:admin - 初始密码:sunrise ## 联系我 项目逐步完善中,有任何疑问和想法,都可以和我联系。 QQ:1906438581