iohao / ioGame

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

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

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

FlowContext 增加通信能力,提供同步、异步、异步回调的便捷使用

iohao opened this issue · comments

FlowContext 增加通信能力,提供同步、异步、异步回调的便捷使用

通讯方式特点:单个游戏逻辑服之间的交互 - 可接收响应

文档 - 单个游戏逻辑服之间的交互

示例代码中提供了同步、异步回调、异步的编码风格。其中,异步回调写法具备全链路调用日志跟踪。

... ...省略部分代码
void invokeModuleMessage() {
    // 同步
    invokeModuleMessageSync();
    // 异步回调
    // --- 此回调写法,具备全链路调用日志跟踪 ---
    invokeModuleMessageAsync();
    // 异步
    invokeModuleMessageFuture();
}

void invokeModuleMessageSync() {
    // 路由
    ResponseMessage responseMessage = flowContext.invokeModuleMessage(cmdInfo);
    RoomNumMsg roomNumMsg = responseMessage.getData(RoomNumMsg.class);
    log.info("同步调用 : {}", roomNumMsg.roomCount);

    // 路由、请求参数
    ResponseMessage responseMessage2 = flowContext.invokeModuleMessage(cmdInfo, yourData);
    RoomNumMsg roomNumMsg2 = responseMessage2.getData(RoomNumMsg.class);
    log.info("同步调用 : {}", roomNumMsg2.roomCount);
}

void invokeModuleMessageAsync() {
    // --- 此回调写法,具备全链路调用日志跟踪 ---

    // 路由、回调
    flowContext.invokeModuleMessageAsync(cmdInfo, responseMessage -> {
        RoomNumMsg roomNumMsg = responseMessage.getData(RoomNumMsg.class);
        log.info("异步回调 : {}", roomNumMsg.roomCount);
    });

    // 路由、请求参数、回调
    flowContext.invokeModuleMessageAsync(cmdInfo, yourData, responseMessage -> {
        RoomNumMsg roomNumMsg = responseMessage.getData(RoomNumMsg.class);
        log.info("异步回调 : {}", roomNumMsg.roomCount);
    });
}

void invokeModuleMessageFuture() {
    CompletableFuture<ResponseMessage> future;

    try {
        // 路由
        future = flowContext.invokeModuleMessageFuture(cmdInfo);
        ResponseMessage responseMessage = future.get();
    } catch (InterruptedException | ExecutionException e) {
        throw new RuntimeException(e);
    }

    // 路由、请求参数
    future = flowContext.invokeModuleMessageFuture(cmdInfo, yourData);
}

通讯方式特点:单个游戏逻辑服之间的交互 - void

文档 - 单个游戏逻辑服之间的交互 - void

无阻塞、适合不需要接收响应的业务。被请求的 action 通常是声明为 void 的,因为知道是无响应的,所以只提供了无阻塞的调用。

示例代码

... ...省略部分代码
void invokeModuleVoidMessage() {
    // 路由
    flowContext.invokeModuleVoidMessage(cmdInfo);
    // 路由、请求参数
    flowContext.invokeModuleVoidMessage(cmdInfo, yourData);
}

通讯方式特点:请求同类型多个游戏逻辑服通信结果

文档 - 请求同类型多个游戏逻辑服通信结果

可接收多个游戏逻辑服的响应

示例代码中提供了同步、异步回调、异步的编码风格。其中,异步回调写法具备全链路调用日志跟踪。

void invokeModuleCollectMessage() {
    // 同步
    this.invokeModuleCollectMessageSync();
    // 异步回调
    // --- 此回调写法,具备全链路调用日志跟踪 ---
    this.invokeModuleCollectMessageAsync();
    // 异步
    this.invokeModuleCollectMessageFuture();
}

void invokeModuleCollectMessageSync() {
    // 路由
    ResponseCollectMessage response = flowContext.invokeModuleCollectMessage(cmdInfo);

    for (ResponseCollectItemMessage message : response.getMessageList()) {
        RoomNumMsg roomNumMsg = message.getData(RoomNumMsg.class);
        log.info("同步调用 : {}", roomNumMsg.roomCount);
    }

    // 路由、请求参数
    ResponseCollectMessage response2 = flowContext.invokeModuleCollectMessage(cmdInfo, yourData);
    log.info("同步调用 : {}", response2.getMessageList());
}

void invokeModuleCollectMessageAsync() {
    // --- 此回调写法,具备全链路调用日志跟踪 ---

    // 路由、回调
    flowContext.invokeModuleCollectMessageAsync(cmdInfo, responseCollectMessage -> {
        List<ResponseCollectItemMessage> messageList = responseCollectMessage.getMessageList();

        for (ResponseCollectItemMessage message : messageList) {
            RoomNumMsg roomNumMsg = message.getData(RoomNumMsg.class);
            log.info("异步回调 : {}", roomNumMsg.roomCount);
        }
    });

    // 路由、请求参数、回调
    flowContext.invokeModuleCollectMessageAsync(cmdInfo, yourData, responseCollectMessage -> {
        log.info("异步回调 : {}", responseCollectMessage.getMessageList());
    });
}

void invokeModuleCollectMessageFuture() {
    CompletableFuture<ResponseCollectMessage> future;

    try {
        // 路由
        future = flowContext.invokeModuleCollectMessageFuture(cmdInfo);
        ResponseCollectMessage response = future.get();
        log.info("异步 : {}", response.getMessageList());
    } catch (InterruptedException | ExecutionException e) {
        throw new RuntimeException(e);
    }

    // 路由、请求参数
    future = flowContext.invokeModuleCollectMessageFuture(cmdInfo, yourData);
}

通讯方式特点:获取玩家所在的游戏对外服的数据与扩展

文档

只会访问玩家所在的【游戏对外服】(即使启动了多个游戏对外服)。

示例代码中提供了同步、异步回调、异步的编码风格。其中,异步回调写法具备全链路调用日志跟踪。

void invokeExternalModuleCollectMessage() {
    // 同步
    this.invokeExternalModuleCollectMessageSync();
    // 异步回调
    // --- 此回调写法,具备全链路调用日志跟踪 ---
    this.invokeExternalModuleCollectMessageAsync();
    // 异步
    this.invokeExternalModuleCollectMessageFuture();
}

void invokeExternalModuleCollectMessageSync() {
    ResponseCollectExternalMessage response;

    // 业务码(类似路由)
    response = flowContext.invokeExternalModuleCollectMessage(bizCode);
    log.info("同步调用 : {}", response);

    // 业务码(类似路由)、请求参数
    response = flowContext.invokeExternalModuleCollectMessage(bizCode, yourData);
    log.info("同步调用 : {}", response);
}

void invokeExternalModuleCollectMessageAsync() {
    // 业务码(类似路由)、回调
    flowContext.invokeExternalModuleCollectMessageAsync(bizCode, response -> {
        response.optionalAnySuccess().ifPresent(itemMessage -> {
            Serializable data = itemMessage.getData();
            log.info("异步回调 {}", data);
        });
    });

    // 业务码(类似路由)、请求参数、回调
    flowContext.invokeExternalModuleCollectMessageAsync(bizCode, yourData, response -> {
        log.info("异步回调");
    });
}

void invokeExternalModuleCollectMessageFuture() {
    CompletableFuture<ResponseCollectExternalMessage> future;

    try {
        // 业务码(类似路由)
        future = flowContext.invokeExternalModuleCollectMessageFuture(bizCode);
        ResponseCollectExternalMessage response = future.get();
        log.info("异步");
    } catch (InterruptedException | ExecutionException e) {
        throw new RuntimeException(e);
    }

    // 业务码(类似路由)、请求参数
    future = flowContext.invokeExternalModuleCollectMessageFuture(bizCode, yourData);
    log.info("异步");
}

通讯方式特点:分步式事件总线

文档 - 分步式事件总线

事件发布后,除了当前进程所有的订阅者能接收到,远程的订阅者也能接收到(跨机器、跨进程)。可以代替 redis pub sub 、 MQ ,并且具备全链路调用日志跟踪,这点是中间件产品做不到的。

示例代码中提供了同步、异步的编码风格。

void eventBus() {
    int userId = 100;
    // 事件源
    YourEventMessage yourEventMessage = new YourEventMessage(userId);

    // 异步执行 - 事件发布后,只会交给 flowContext 关联的业务框架中的订阅者处理
    flowContext.fireMe(yourEventMessage);
    // 同步执行 - 事件发布后,只会交给 flowContext 关联的业务框架中的订阅者处理
    flowContext.fireMeSync(yourEventMessage);

    // 异步执行 - 事件发布后,当前进程所有的订阅者都会接收到。(同进程启动了多个逻辑服)
    flowContext.fireLocal(yourEventMessage);
    // 同步执行 - 事件发布后,当前进程所有的订阅者都会接收到。(同进程启动了多个逻辑服)
    flowContext.fireLocalSync(yourEventMessage);

    // 异步执行 - 除了当前进程所有的订阅者能接收到,远程的订阅者也能接收到(跨机器、跨进程)
    flowContext.fire(yourEventMessage);
}

大多数情况下,在发布事件时,使用 fire 方法就足够了。因为除了当前进程所有的订阅者能接收到,远程的订阅者也能接收到(跨机器、跨进程)

需要注意的是,如果没有任何远程订阅者,将不会触发网络请求。简单的说,事件发布后,当其他进程(其他机器)没有相关订阅者时,只会在内存中传递事件给当前进程的相关订阅者。所以,可以将该通讯方式当作 guava EventBus 来使用。

通讯方式:广播(推送)

文档 - 广播(推送)

示例代码中展示了

  1. 全服广播
  2. 指定单个用户广播
  3. 指定多个用户广播
  4. 给自己发送广播(提供了两种便捷的使用方式,分别是: 1.可指定路由的方式,2.复用当前 action 路由的方式)
@ActionMethod(1)
public void broadcast(FlowContext flowContext) {
    // 全服广播 - 路由、业务数据
    flowContext.broadcast(cmdInfo, yourData);

    // 广播消息给单个用户 - 路由、业务数据、userId
    long userId = 100;
    flowContext.broadcast(cmdInfo, yourData, userId);

    // 广播消息给指定用户列表 - 路由、业务数据、userIdList
    List<Long> userIdList = new ArrayList<>();
    userIdList.add(100L);
    userIdList.add(200L);
    flowContext.broadcast(cmdInfo, yourData, userIdList);

    // 给自己发送消息 - 路由、业务数据
    flowContext.broadcastMe(cmdInfo, yourData);

    // 给自己发送消息 - 业务数据
    // 路由则使用当前 action 的路由。
    flowContext.broadcastMe(yourData);
}

通讯方式:顺序 - 广播(推送)

文档 - 广播(推送)

示例代码中展示了

  1. 全服广播
  2. 指定单个用户广播
  3. 指定多个用户广播
  4. 给自己发送广播(提供了两种便捷的使用方式,分别是: 1.可指定路由的方式,2.复用当前 action 路由的方式)
@ActionMethod(2)
public void broadcastOrder(FlowContext flowContext) {
    // 顺序 - 全服广播 - 路由、业务数据
    flowContext.broadcastOrder(cmdInfo, yourData);

    // 顺序 - 广播消息给单个用户 - 路由、业务数据、userId
    long userId = 100;
    flowContext.broadcastOrder(cmdInfo, yourData, userId);

    // 顺序 - 广播消息给指定用户列表 - 路由、业务数据、userIdList
    List<Long> userIdList = new ArrayList<>();
    userIdList.add(100L);
    userIdList.add(200L);
    flowContext.broadcastOrder(cmdInfo, yourData, userIdList);

    // 顺序 - 给自己发送消息 - 路由、业务数据
    flowContext.broadcastOrderMe(cmdInfo, yourData);

    // 顺序 - 给自己发送消息 - 业务数据
    // 路由则使用当前 action 的路由。
    flowContext.broadcastOrderMe(yourData);
}

该广播会严格按照顺序发送(使用单线程处理),如果不是极端的业务,建议使用 broadcast 系列方法。

通常情况下,在使用 broadcast 系列方法时,同一用户业务处理的时间间隔通常会 > 2 ms ,有这点时间的间隔,用户接收到消息通常都是理论有序的。(游戏客户端每秒刷 60 帧,每帧也需要大约 16.6 ms)