Giter Club home page Giter Club logo

iohao / iogame Goto Github PK

View Code? Open in Web Editor NEW
801.0 801.0 179.0 3.07 MB

无锁异步化、事件驱动架构设计的 java netty 网络编程框架; 轻量级,无需依赖任何第三方中间件或数据库就能支持集群、分布式; 适用于网络游戏服务器、物联网、内部系统及各种需要长连接的场景; 通过 ioGame 你可以很容易的搭建出一个集群无中心节点、集群自动化、分布式的网络服务器;FXGL、Unity、UE、Cocos Creator、Godot、Netty、Protobuf、webSocket、tcp、socket;java Netty 游戏服务器框架;

Home Page: http://game.iohao.com

License: GNU Affero General Public License v3.0

Java 100.00%
bolt cocos-creator framework fxgl game gameframework godot godot-engine java netty protobuf socket springboot tcp unity unity3d unreal-engine websocket

iogame's Issues

阿里云 SAE 部署 Demo 时,启动后出现 BalancedManager.remove() 方法出现 NullPointerException 异常

see : https://gitee.com/iohao/iogame/issues/I6CS9A

问题场景描述:
阿里云 SAE 部署 Demo 时,启动后出现 BalancedManager.remove() 方法出现 NullPointerException 异常。

image

此时查看/logs/game/game_other.log会发现一直有100开头的ip在断开连接

2023-02-03 15:20:05.811 INFO [Bolt-conn-event-executor-18-thread-751]c.i.game.bolt.broker.server.processor.connection.ConnectionEventBrokerProcessor.onEvent:59 -通知客户端发送模块信息 ConnectionEvent remoteAddress : 100.127.1.194:52642
2023-02-03 15:20:05.812 INFO [Bolt-conn-event-executor-18-thread-751]c.i.g.b.broker.server.processor.connection.CloseConnectionEventBrokerProcessor.onEvent:54 -连接关闭 remoteAddress : 100.127.1.194:52642

问题照成的影响:

  1. 通过在线WebSocket连接测试发现可以正常连接不影响
  2. logs下的日志文件快速增长,长时间会有磁盘空间占满的隐患
  3. 影响正常日志查看
  4. ……

问题的原因与处理:
在 SAE上 做 SLB 的公网端口映射时,应将暴露的 WebSocket 的映射协议设置为 HTTP,旧时代的 SLB 默认似乎不支持 WebSocket

支持多种协议:protobuf、json,并支持可扩展

ioGame 支持同样的一套业务代码,无需做变更,可以同时支持多种协议:protobuf、json,并可扩展。在 ioGame 中切换协议是简单的,只需要一行代码。

使用文档
https://www.yuque.com/iohao/game/uq2zrltrc7to27bt

public class JsonApplication {
    public static void main(String[] args) {
        // 设置 json 编解码。如果不做设置,默认使用 jprotobuf
        IoGameGlobalSetting.me().setDataCodec(new JsonDataCodec());

        // 游戏对外服端口
        int port = 10100;

        // 逻辑服
        var demoLogicServer = new JsonLogicServer();

        // 启动 对外服、网关服、逻辑服; 并生成游戏业务文档
        SimpleHelper.run(port, List.of(demoLogicServer));
    }
}

游戏对外服独立 UserSession 管理部份逻辑,做成单独的类

将 ExternalBizHandler 中 UserSession 部份独立成单独的 ChannelInboundHandler

@ChannelHandler.Sharable
public class ExternalBizHandler extends SimpleChannelInboundHandler<ExternalMessage> {
    ... ... 省略部分代码
    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        // 从 session 管理中移除
        UserSession userSession = UserSessions.me().getUserSession(ctx);
        UserSessions.me().removeUserSession(userSession);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        // 加入到 session 管理
        UserSessions.me().add(ctx);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        // 从 session 管理中移除
        UserSession userSession = UserSessions.me().getUserSession(ctx);
        UserSessions.me().removeUserSession(userSession);
    }
}

集群网关下一直报错

飞书20220823-170715
您好,我使用集群模式之后,虽然可以使用,但是一直报错,这个报错端口号也会一直变。

标准 action 规则

标准 action 映射规则

  1. 业务方法上添加注解 ActionMethod
  2. 业务方法的访问权限必须是:public
  3. 业务方法不能是:static
  4. 业务方法需要是在 action 类中声明的方法

简单的说,标准的 action 是非静态的,且访问权限为 public 的方法。
术语说明:在 action 类中提供的业务方法通常称为 action

业务框架与网络通信框架解耦

新增 ChannelContext 通信通道接口
用于对 bolt AsyncContext、netty Channel 的包装,这样可以使得业务框架与网络通信框架解耦。
为将来 ioGame 实现绳量级架构的使用做准备。

提供 bolt UserProcessor 用户线程池设置策略

分离IO线程池与用户线程池,这样服务器可以在同一时间内处理更多的请求。

框架提供 UserProcessorExecutorStrategy 接口。

主要用于给 UserProcessor 构建 Executor 的策略;

框架会在启动时,如果检测到 UserProcessor 实现了 UserProcessorExecutorAware 接口,就会触发一次

通过该接口,开发者可以给 UserProcessor 配置 Executor;

开发者可以根据自身业务来做定制
see IoGameGlobalConfig.userProcessorExecutorStrategy

用户动态绑定逻辑服节点,实现LOL、王者荣耀匹配后动态分配房间节点

支持对外服的玩家绑定指定的游戏逻辑服(可以做到动态分配游戏逻辑服资源)

描述
支持对外服的玩家绑定指定的游戏逻辑服id,如果用户绑定了指定的游戏逻辑服id,之后与该游戏逻辑服的请求都由这个绑定的游戏逻辑服来处理

场景举例

  1. 什么意思呢?这里用匹配与象棋的场景举例。
  2. 假设我们部署了 5 台象棋逻辑服,在玩家开始游戏之前。我们可以在匹配服中进行匹配,当匹配逻辑服把A、B两个玩家匹配到一起了。
  3. 此时我们可以通过 访问【同类型】的多个逻辑服方法,当得到象棋房间数最少的象棋逻辑服后(这里假设是房间数最少的象棋逻辑服是《象棋逻辑服-2》),把《象棋逻辑服-2》的逻辑服id 绑定到 A、B 两个玩家身上。
  4. 之后与象棋相关的操作请求都会由《象棋逻辑服-2》这个游戏逻辑服来处理,比如:开始游戏、下棋、吃棋、和棋等。
  5. 也可以简单点把这理解成,类似 LOL、王者荣耀的匹配机制。在匹配服匹配到玩家后,把匹配结果中的所有玩家分配到一个房间(节点)里面。
  6. 这是一种动态分配资源最少的节点(逻辑服)的用法之一。
  7. 这个版本先做成只能绑定一个逻辑服的,因为暂时没有想到多个的场景。

大概简图如下
输入图片说明

🐳 玩家A和玩家B可以在不同在对外服上,两个玩家发起一个匹配请求。由匹配逻辑服来处理,假设是房间数最少的象棋逻辑服是《象棋逻辑服-2》,那么《象棋逻辑服-2》的逻辑服id 绑定到 A、B 两个玩家身后。之后的处理的简图如下:

输入图片说明

  • 之后与 象棋相关的操作请求 都会由《象棋逻辑服-2》这个游戏逻辑服来处理,比如:开始游戏、下棋、吃棋、和棋等。
  • 也可以简单点把这理解成,类似 LOL、王者荣耀的匹配机制。在匹配服匹配到玩家后,把匹配结果中的所有玩家分配到一个房间(节点)里面。
  • 这是一种动态分配资源最少的节点(逻辑服)的用法之一。

支持原生protobuf

jprotobuf和原生protobuf序列化结果不一致, jprotobuf存在默认值和null, 会导致序列化完长度与客户端不同. 能否支持原生protobuf

Request for the English documentation

Hi @iohao,
I appreciate your effort to create and share the awesome repo. However, since it is hard to approach the project in Chinese, I would be grateful if you could spare some time to provide the English version for documentation.
Thank you so much.

Best regards,
Kong

监控admin功能

目前有提供现成的,可以监控broker的后台吗?比如能看到有几台broker, 连接了几个逻辑服,每个broker负载如何,还有其他的性能指标等等。

缩小打 jar 包后的包体,打 jar 包后仅 15MB

使用 ioGame 开发的项目,打 jar 包后的包体大小约 15MB。

移除一些第三方库
为缩小打包,将 hutool 依赖移除、将 fastjson2 依赖配置中的 scope 改为 compile;如有使用到相关的,需要开发者自行引入。

如果你的项目中有使用到 hutool 、fastjson2 的,需要在 pom 中添加

<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.11</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2 -->
<dependency>
    <groupId>com.alibaba.fastjson2</groupId>
    <artifactId>fastjson2</artifactId>
    <version>2.0.23</version>
</dependency>

关于高并发

你好!一个非常伟大的开始!
请问客户端高并发访问游戏服务器中的某个方法时,是游戏框架内部有相应的机制去保持原子性还是都需要自己去考虑这些,谢谢!

原生pb与框架互通示例

新增:原生 PB 与 jprotobuf 互转示例,具体查看示例源码的 spring-websocket-native-pb-client 模块
示例文档:https://www.yuque.com/iohao/game/ruaqza

spring-websocket-native-pb-client 模块,单独互转示例代码 NativeProtoTest.java

网络测试步骤:

  1. 启动综合示例 SpringGameOneApplication.java
  2. 启动原生pb 模拟客户端 SpringWebsocketNativeProtoClient.java

SpringWebsocketNativeProtoClient 的请求是使用纯原生的 proto 代码发起请求数据的。

游戏对外服增加路由访问验证、路由权限,将部份逻辑做成单独的类

将 ExternalBizHandler 中路由验证部分独立出来

protected void channelRead0(ChannelHandlerContext ctx, ExternalMessage message) {
        UserSession userSession = UserSessions.me().getUserSession(ctx);

        // 是否可以访问业务方法(action),true 表示可以访问该路由对应的业务方法
        boolean pass = ExternalGlobalConfig.accessAuthenticationHook.pass(userSession, message.getCmdMerge());

        // 当访问验证没通过,通知玩家
        if (!pass) {
            message.setResponseStatus(ActionErrorEnum.verifyIdentity.getCode());
            message.setValidMsg("请先登录,在请求业务方法");
            // 响应结果给用户
            Channel channel = userSession.getChannel();
            channel.writeAndFlush(message);
            return;
        }
... 省略部分代码
}

框架源码、线程、容灾等问题

框架源码的交互时序方面:

  1. 客户端与external建立连接的入口在哪?(accept方法位置,想看看整体流程)
  2. broker的路由寻址逻辑和负载均衡具体在哪里实现的(从一个请求发送过来,到找到指定的逻辑服务器这块逻辑)
  3. 网关和逻辑服之前的通信,还有连接管理的具体位置在哪?源代码是哪个class里面写的能否指一下类名

框架的线程池方面:

  1. 对外、网关、逻辑服几种线程(例如:IO线程 和 工作线程 )具体如何划分的。
  2. 网络模型是什么样子(nio,bio,aio还是io多路复用),顺便能指明一下class类就更好了。

状态持久化针对容灾
1.内部session的状态存储目前都存储在内存,如果服务部署在云端,出现容器漂移、逐出是很常见的事情(物理机也会出现死机等问题),这种情况用户连接都会断开,(此时这种有状态的数据---包含但不限于用户session,是否可以有扩展的口子让使用者自己实现持久化逻辑) 使用者自己就可以重写实现这些有状态数据的持久化细节。(宕机后客户端重连仍然可以恢复用户的有状态数据)
2. 广播消息推送的时候,广播是临时存储在broker内存里面的吗?如果需要广播发送给uid=1,2,3,4,5这5个用户,但是由于中间环节出现不可预知的问题(例如某一台broker服务宕机了),广播是否就没了。如果涉及到一些财务相关的业务,使用广播给2个正在进行交易的用户发送广播,但是其中一个用户external连接出现了问题,客户端有一些重连的逻辑,重连上之后,希望能有一个重试机制这样的。

疑问点:

  1. broker的实现,为什么选择自己实现,为何不采用相对比较成熟的方案,例如zk,etcd。

游戏对外服 netty 业务编排钩子接口

新增 ChannelPipelineHook netty 业务编排的处理器钩子接口,用于游戏对外服。

ExternalServerBuilder 新增 channelPipelineHook ,用于自定义 netty 业务编排的处理器。

废弃标记 ExternalServerBuilder.channelHandlerProcessors 属性及其相关地方为过期,由 ChannelPipelineHook 来代替,使用示例如下。

使用示例代码

public class ExternalServerBuilderApplication {
    public static void main(String[] args) {
        // netty ChannelHandler 编排
        var myChannelPipelineHook = new ChannelPipelineHook() {
            @Override
            public void initChannelPipeline(ChannelPipeline pipeline) {
                pipeline.addLast("ExternalBizHandler", new ExternalBizHandler());

                // pipeline.addFirst("ssl", ssl)
            }
        };

        // 游戏对外服构建器
        ExternalServerBuilder builder = ExternalServer.newBuilder(10100);
        builder.channelPipelineHook(myChannelPipelineHook);
    }
}

通常情况下,这样的编排方式也会更加清晰,因为代码统一在这里做编排了。

开发者在自定义业务编排时,可以通过这个钩子接口,比如添加一个 SSL。

注意事项:在调用 hook 前,会经过 ExternalChannelInitializerCallback.initChannelPipeline(SocketChannel) 。
ExternalChannelInitializerCallback 接口的实现类有

  1. ExternalChannelInitializerCallbackWebsocket
  2. ExternalChannelInitializerCallbackTcp

这些实现类中,会给 ChannelPipeline 添加上一些默认的处理器,通常是编解码相关的。

业务参数自动装箱和拆箱,基础类型:int、long

在实际开发中,会有一些基础类型的业务参数,比如需要经常用到下面类似的

通过 装备-衣服-id 得到衣服装备详细
通过 装备-头盔-id 得到头盔装备详细
.... 
通过 xx-id 得到xx数据 等

这些参数基本都是相同的,如果每一个类似这样的请求都创建一个数据协议,会产生比较多的碎片协议,如

// 装备-头盔-id pb
@ProtobufClass
@FieldDefaults(level = AccessLevel.PUBLIC)
public class EquipHelmetIdPb {
    int id;
}

// 装备-衣服-id pb
@ProtobufClass
@FieldDefaults(level = AccessLevel.PUBLIC)
public class EquipClothesIdPb {
    int id;
}

// 业务action
@ActionController(1)
public class DemoAction {
    @ActionMethod(0)
    public xxx getEquipPb(EquipClothesIdPb equipClothesIdPb) {
    	// 通过装备id , 查询装备
    	int equipId = equipClothesIdPb.id;
        return ...
    }
}

业务框架提供的内置支持

对于这种情况,业务框架将提供一些较好的支持。
首先,业务框架会提供一个通用的 pb 类型,如下

@ProtobufClass
@FieldDefaults(level = AccessLevel.PUBLIC)
public class IntPb {
    int intValue;
}

在代码中的使用,如下伪代码

@ActionController(1)
public class DemoAction {
    @ActionMethod(0)
    public xxx getEquipPb(IntPb equipIdPb) {
    	// 通过装备id , 查询装备
    	int equipId = equipIdPb.intValue;
        return ...
    }
}

由于是业务参数基础包装类型-int,实际上我们还可以这样使用,下面这种用法与上面是等价的。
这种实际上就是有点像是 java 基础类型的自动装箱和拆箱

@ActionController(1)
public class DemoAction {
    @ActionMethod(0)
    public xxx getEquipPb(int equipId) {
    	
        return ...
    }
}

关于对接文档的生成,即使你写成 (int equipId) 这种参数形式,框架会生成大概这样的文档


==================== DemoAction 装备相关 ====================
路由: 1 - 0  --- 【查询装备衣服】 --- 【DemoAction:44】【getEquipPb】
    方法参数: com.iohao.game.common.msg.IntPb
    方法返回值: com.xxx.YourEquipPb # 就是你项目当中的装备pb


参数验证问题

您好,不知道现在是否支持例如springboot那种参数验证分组功能。同一个实体类,我可以在不同方法上验证不同的参数

简化元附加信息的使用

简化元附加信息的使用

在处理 action 时,我们可以通过 FlowContext.userId 可以很方便得到当前用户(玩家)id。

如果开发者想在处理 action 时,携带上一些自定义的信息时,可以通过元附加信息特性来完成。

比如保存当前玩家的英雄角色 id,或者玩家的昵称,又或者是你的项目的 userId 是 string 或其他类型则可以通过元信息这一特性来兼容。

简单的说,就是你想在 FlowContext 中获取一些用户(玩家)特有的信息数据时,可以通过这个特性来实现。

新增 元信息接口 Attachment

/**
 * 元信息接口
 * <pre>
 *     注意:
 *     框架默认使用的是 protobuf 编解码,所以建议子类添加 ProtobufClass 注解
 * </pre>
 *
 * @author 渔民小镇
 * @date 2022-12-11
 */
public interface Attachment extends Serializable {
    /**
     * get userId
     *
     * @return userId
     */
    long getUserId();
}

action 业务参数与返回值增加 List 支持

大概使用方式如下

/** 请求 */
@ProtobufClass
@FieldDefaults(level = AccessLevel.PUBLIC)
public class HelloReq {
    String name;
}

@ActionController(6)
public void ListAction {
    @ActionMethod(60)
    public List<HelloReq> list2list(List<HelloReq> helloReqList) {
        
        HelloReq newHelloReq = new HelloReq();
        newHelloReq.name = "hello list, I'm here ";

        List<HelloReq> newList = new ArrayList<>();
        newList.add(newHelloReq);

        return newList;
    }
}

支持 javax.validation 校验

from https://gitee.com/iohao/iogame/issues/I5SLGJ

由于原来老的业务服务都使用的是javax.validation 做数据校验,而不是 jakarta.validation;在整合服务时会出现不兼容的情况,查看源码 ValidatorKit.java 中确实使用的是jakarta.validation, 所以可以考虑优化 ValidatorKit类,我们可以按需选择使用javax.validation 还是jakarta.validation

关注 ioGame 的游戏服务器开发者持续增多,2022-09 ~ 至今各月的统计数据

关注 ioGame 的游戏服务器开发者持续增多,2022-09 ~ 至今各月的统计数据;

这里的统计信息是关于开发者关注 ioGame 框架相关的,从统计数据中可以看出,由于 ioGame 上手简单,功能强大等优点,得到了众多开发者的关注。如果你想知道 ioGame 有没有人在使用,可以先到这里看下统计数据、开发者的评价与讨论。

https://www.yuque.com/iohao/game/gpxk93#TwVa8

这里展示了每月的统计数据,统计数据来源于语雀后台,这些数据都是真实的、客观存在的、活的

因为成本的原因,某宝某多还没有出现能提供这种服务的商家,所以这样的统计数据也更具真实性。

通过统计数据,我们可以看到每天会有很多开发者在访问 ioGame 的在线文档,并且这些统计数据不是来源于口嗨的,也不是主观创造的。

所以,还在犹豫要不要使用 ioGame 的开发者们,更应该讨论的是“为什么这些开发者会选择使用 ioGame”,而不是 ioGame 有没有人在使用的问题。

点击我,到语雀后台查看 ioGame 的数据

image

ActionAfter 异常信息携带到请求端

see https://gitee.com/iohao/iogame/issues/I5KZTP

业务框架的异常机制,只保存错误码,不会保存错误码对应的异常信息,这是因为:

从字段精简的角度,我们不可能每次响应都带上完整的异常信息给客户端排查问题,因此,我们会定义一些响应码,通过编号进行网络传输,方便客户端定位问题。

但有一种情况是会携带异常信息,就是开启 JSR380 时,具体参考 业务框架开启JSR验证


配合游戏文档生成
https://www.yuque.com/iohao/game/irth38

==================== TankAction 坦克相关 ====================
路由: 2 - 2  --- 【玩家进入房间】 --- 【TankAction:135】【enterRoom】
    方法参数: com.iohao.game.collect.proto.tank.TankEnterRoom
    方法返回值: com.iohao.game.collect.proto.tank.TankEnterRoom
 
 
路由: 2 - 5  --- 【坦克移动】 --- 【TankAction:101】【tankMove】
    方法参数: com.iohao.game.collect.proto.tank.TankLocation
    广播推送: com.iohao.game.collect.proto.tank.TankLocation
 
路由: 2 - 6  --- 【坦克射击(发射子弹)】 --- 【TankAction:74】【shooting】
    触发异常: (方法有可能会触发异常)
    方法参数: com.iohao.game.collect.proto.tank.TankBullet
    广播推送: com.iohao.game.collect.proto.tank.TankBullet
 
==================== 其它广播推送 ====================
路由: 2 - 11  --- 广播推送: com.iohao.game.collect.proto.common.UserInfo

==================== 错误码 ====================
 -1002 : 路由错误码,一般是客户端请求了不存在的路由引起的 
 -1001 : 参数验错误码 
 -1000 : 系统其它错误 
 -1 : class 不存在 
 201 : 子弹不存在或不足 

在生成的游戏文档中,可以看见所有的错误码;
如果做国际化,放在前端会比较好做一些。

是否提供热更新的能力?

在游戏里面,不停服更新是很重要的一个能力,在更新的时候玩家无感,会很大程度的提升用户体验,如果有希望提供个完整的例子,谢谢~

逻辑服请求网关超时

当我瞬间发送几十条的时候,就会出现部分超时现象。。网关那打了日志,发现网关实际收到的频率偏低。感觉像是有大量的请求过了一会才抵达网关(网关会二次转发给对外服,俩者之间目前没产生超时现象)。。。不知道是怎么回事,希望大神请指点一下,我们现在打算部署到生产,结果测试的时候出现了这个现象

逻辑服和网关断连

您好,如果逻辑服频繁请求网关,会出现短时间的断连现象,网关那边会显示此逻辑服下线。过一会会重新连接。

严格登录、路由权限控制

严格登录指的是,如果玩家没有登录,是不能访问其他业务方法的(即 action)。

var accessAuthenticationHook = ExternalGlobalConfig.accessAuthenticationHook;
// 表示登录才能访问业务方法
accessAuthenticationHook.setVerifyIdentity(true);
// 添加不需要登录(身份验证)也能访问的业务方法 (action)
accessAuthenticationHook.addIgnoreAuthenticationCmd(1, 1);

通常这段代码放到游戏对外服中,因为是在游戏对外服做的权限控制。

当 setVerifyIdentity = true 时,是不能访问任何业务方法的,包括开发者编写的登录业务方法。但我们可以放开权限,使得玩家可以访问我们的业务方法。上面的伪代码中,放开了路由 1-1 的访问权限,这样不需要登录也可以访问游戏逻辑服的业务方法了。

默认配置下,setVerifyIdentity = false ,就是不登录就可以访问所有的业务方法。

文档
https://www.yuque.com/iohao/game/tywkqv#qEvtB

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.