游戏对外服介绍
从架构简图中,我们知道了整体架构由三部分组成,分别是 1.游戏对外服、2.游戏网关、3.游戏逻辑服,三者既可相互独立,又可相互融合。
游戏对外服在架构图位置的左边部分,ExternalServer。
介绍
本篇将介绍游戏对外服这部分,及功能扩展等相关的。
游戏对外服的职责
- 保持与用户(玩家)长的连接。
- 帮助开发者屏蔽通信细节、与连接方式的细节。
- 支持多种连接方式,如 WebSocket、TCP、UDP,并支持扩展。
- 将用户(玩家)请求转发到游戏网关
- 可动态增减机器
- 功能扩展,如:路由存在检测、路由权限、UserSession 管理、心跳,及后续要提供但还未提供的熔断、限流、降载、用户流量统计等功能。
动态增减机器的场景
游戏对外服主要负责与用户(玩家)的长连接,先来个假设,假如我们的一台硬件支持我们建立用户连接的上限是 5000 人, 当用户量达到 7000 人时,我们可以多加一个对外服务器来进行分流减压。
通过增加游戏对外服的数量,可以有效地进行连接的负载均衡和流量控制,使得系统能够更好地承受高并发的压力。 由于游戏对外服扩展的简单性,意味着支持同时在线玩家可以轻松的达到百万、千万甚至更多。
支持的连接方式
框架已提供了 TCP、WebSocket、UDP 连接方式的支持,并提供了灵活的方式来实现连接方式的切换。 可以将 TCP、WebSocket、UDP 连接方式与业务代码进行无缝衔接。 开发者可以用一套业务代码,无需任何改动,同时支持多种通信协议。
如果想要切换到不同的连接方式,只需要更改相应的枚举即可,非常简单。 在不使用 ioGame 时,将连接方式从 TCP 改为 WebSocket 或 UDP 等,需要进行大量的调整和改动。 然而,在 ioGame 中,实现这些转换是非常简单的。 此外,不仅可以轻松切换各种连接方式,而且可以同时支持多种连接方式,并使它们在同一应用程序中共存。
连接方式是可扩展的,而且扩展也简单,这意味着之后如果支持了 KCP、QUIC,那么将已有项目的连接方式, 如 TCP、WebSocket、UDP 切换成 KCP、QUIC 也是简单的。
需要再次强调的是,连接方式的切换对业务代码没有任何影响,无需做出任何改动即可实现连接方式的更改。
如何安装
如果你是初次体验或想快速了解框架的,在 pom.xml 添加如下内容,
run-one-netty
已经包含了游戏对外服、Broker(游戏网关)、游戏逻辑服。
<dependency>
<groupId>com.iohao.game</groupId>
<artifactId>run-one-netty</artifactId>
<version>${ioGame.version}</version>
</dependency>
通常在实际开发中,比较推荐的做法中单独为游戏对外服创建一个模块,并在 pom.xml 中添加如下内容
see https://central.sonatype.com/artifact/com.iohao.game/external-netty
<dependency>
<groupId>com.iohao.game</groupId>
<artifactId>external-netty</artifactId>
<version>${ioGame.version}</version>
</dependency>
因为随着业务的增加,后续我们可能需要在对外服中做很多扩展, 将这些与对外服相关的扩展放到对外服模块中是比较好的组织方式。
这些扩展包括,但不限于以下功能
- 心跳
- 限流
- 缓存
- SSL
- 自定义编解码
- 更多连接方式的支持,如 KCP、QUIC 等
- 以及更多自定义的 Netty Handler
创建游戏对外服
通过便捷工具类
通过工具类创建游戏对外服,一些简单的本地测试可用工具来创建。
- code 7,设置游戏对外服端口(默认 10100),并设置连接方式
- code 9,启动
public class Example {
public void start() {
int externalCorePort = ExternalGlobalConfig.externalPort;
ExternalJoinEnum externalJoinEnum = ExternalJoinEnum.WEBSOCKET;
ExternalServer externalServer = ExternalServerCreateKit
.createExternalServer(externalPort, externalJoinEnum);
externalServer.startup();
}
}
通过构建器
通过游戏对外服构建器来创建游戏对外服,可做更多的扩展设置。
- code 4,创建游戏对外服构建器,并设置游戏对外服端口。
- code 5,连接方式 WebSocket,默认不填写也是这个值。
- code 6,与 Broker (游戏网关)的连接地址,默认不填写也是这个值。
- code 8,ExternalCoreSetting,开发者可根据自身业务做扩展。
- code 10,构建、启动
public class Example {
public void start() {
var builder = DefaultExternalServer
.newBuilder(ExternalGlobalConfig.externalPort)
.externalJoinEnum(ExternalJoinEnum.WEBSOCKET)
.brokerAddress(new BrokerAddress("127.0.0.1", IoGameGlobalConfig.brokerPort));
DefaultExternalCoreSetting setting = builder.setting();
builder.build().startup();
}
}
ExternalCoreSetting
DefaultExternalCoreSetting 对象中提供了一些属性设置,开发者可以手动设置做扩展。 如果不做任何设置,框架将在构建过程中发现没有初始化的属性时,会自动填充所需的默认值。
后续我们会介绍如何通过 ExternalCoreSetting 来扩展游戏对外服。
如何添加扩展
下面这段代码是介绍如何做 pipelineCustom 业务编排的。 同样的,如果框架提供的编解码不能满足我们的业务,我们可以重写 pipelineCodec 方法等。
- code 9,对 netty ChannelHandler 编排,重写 MicroBootstrapFlow 的 pipelineCustom 方法
- code 12,使用已有的 handler
- code 14,将 SSL 相关的 handler 添加到 addFirst 中
public class Example {
public void start() {
...
var builder = DefaultExternalServer
.newBuilder(ExternalGlobalConfig.externalPort);
DefaultExternalCoreSetting setting = builder.setting();
setting.setMicroBootstrapFlow(new WebsocketMicroBootstrapFlow() {
@Override
public void pipelineCustom(PipelineContext context) {
super.pipelineCustom(context);
// context.addFirst("ssl", ssl)
}
});
builder.build().startup();
}
}
code 9,我们设置并重写了 WebsocketMicroBootstrapFlow ,因为默认的连接方式是 WebSocket。 如果你使用的是 TCP 的连接方式,则需要重写 TcpMicroBootstrapFlow。 这是因为每种连接方式的编解码、心跳...等实现方式各不相同。
已经支持的连接方式可阅读 MicroBootstrapFlow
扩展自定义编解码
MicroBootstrapFlow 提供了编解码、心跳、自定义的业务相关的编排方法。 开发者有如果特殊业务的,可以按需重写任意一个环节。
本小节介绍的是如何自定义编解码,所以我们只需要关注 pipelineCodec 方法。
重写 pipelineCodec
重写 pipelineCodec 方法,这种方式最灵活,可完全自定义。
- code 7,开发者自定义的编解码实现类
DefaultExternalServerBuilder builder = ...;
builder.setting().setMicroBootstrapFlow(new WebSocketMicroBootstrapFlow() {
@Override
public void pipelineCodec(PipelineContext context) {
...
var externalCodec = new YourExternalCodec();
context.addLast("codec", externalCodec);
}
});
只重写 createExternalCodec
重写 createExternalCodec 方法,用于创建开发者自定义的编解码,其他配置则使用 pipelineCodec 中的默认配置。
- code 6,开发者自定义的编解码实现类
DefaultExternalServerBuilder builder = ...;
builder.setting().setMicroBootstrapFlow(new WebSocketMicroBootstrapFlow() {
@Override
protected MessageToMessageCodec<BinaryWebSocketFrame, BarMessage> createExternalCodec() {
return new YourExternalCodec();
}
});
以下展示的是 WebSocketMicroBootstrapFlow pipelineCodec 相关代码
- code 5,添加 http 相关 handler
- code 6,建立连接前的验证 handler
- code 7,添加 websocket 相关 handler
- code 9,websocket 编解码,createExternalCodec 相当于一个钩子方法。
public class WebSocketMicroBootstrapFlow extends SocketMicroBootstrapFlow {
...
@Override
public void pipelineCodec(PipelineContext context) {
this.httpHandler(context);
this.verifyHandler(context);
this.websocketHandler(context);
var externalCodec = this.createExternalCodec();
context.addLast("codec", externalCodec);
}
@Override
protected MessageToMessageCodec<BinaryWebSocketFrame, BarMessage> createExternalCodec() {
return new WebSocketExternalCodec();
}
};
连接方式
框架允许开发者使用一套业务代码,同时支持多种连接方式或切换连接方式,业务代码无需进行任何修改。
切换连接方式
ExternalJoinEnum 提供了不同的枚举,开发者可以选择不同的连接方式来创建游戏对外服。
static ExternalServer createExternalServer(ExternalJoinEnum join) {
return DefaultExternalServer
.newBuilder(ExternalGlobalConfig.externalPort)
.externalJoinEnum(join)
.build();
}
同时支持多种连接方式
这里演示同进程同时支持多种连接方式的技巧
- code 12 ~ 18,获取游戏对外服列表,每个游戏对外服支持了不同的连接方式,WEBSOCKET、TCP。
- code 21,coc 原则,根据连接协议生成不同的游戏对外服端口。
public class MyApplication {
...
public static void main(String[] args) {
var externalServerList = listExternalServer();
new NettyRunOne()
.setExternalServerList(externalServerList)
.startup();
}
static List<ExternalServer> listExternalServer() {
return List.of(
createExternalServer(ExternalJoinEnum.WEBSOCKET)
, createExternalServer(ExternalJoinEnum.TCP)
, createExternalServer(ExternalJoinEnum.UDP)
);
}
static ExternalServer createExternalServer(ExternalJoinEnum join) {
int port = join.cocPort(ExternalGlobalConfig.externalPort);
return DefaultExternalServer
.newBuilder(port)
.externalJoinEnum(join)
.build();
}
}