本专栏的项目代码在Github上,如果感兴趣的话,麻烦点点Star,谢谢啦。
如果我们的协议定义如下:
syntax = "proto3";
package com.mikai233.protocol;
message TestReq{
}
message TestResp{
}
在业务层是直接这样处理的:
class TestHandler : MessageHandler {
@Handle
fun handleTestReq(player: PlayerActor, testReq: TestReq) {
player.send(testResp { })
}
}
服务器网关收到的肯定是经过序列化的 Protobuf 二进制消息,需要一种形式将二进制数据解析成 Protobuf 消息,客户端和服务端定义的数据包格式一般会在包头写入包体长度, Protobuf 的协议号。客户端和服务端都根据包体长度去切分数据,然后根据 Protobuf 的协议号去确定包体具体是哪个消息的数据:
message MessageClientToServer{
PingReq ping_req = 1;
GmReq gm_req = 2;
TestReq test_req = 3;
LoginReq login_req = 10001;
}
message MessageServerToClient{
PingResp ping_resp = 1;
GmResp gm_resp = 2;
TestResp test_resp = 3;
LoginResp login_resp = 10001;
TestNotify test_notify = 99999;
}
因此我们需要一个协议号到 Protobuf 消息的映射以及 Protobuf 消息到协议号的映射,客户端发送消息到服务端时,我们根据这个映射将数据包解析成具体的消息,服务端发消息给客户端时,我们根据消息类型找到对应的协议号来封装数据包。所以我们需要如下的代码:
private val ClientToServerMessageById0: Map<KClass<out GeneratedMessage>, Int> = mapOf(
ProtoSystem.PingReq::class to 1,
ProtoSystem.GmReq::class to 2,
ProtoTest.TestReq::class to 3,
ProtoLogin.LoginReq::class to 10_001
)
public val ClientToServerMessageById: Map<KClass<out GeneratedMessage>, Int> = listOf(
ClientToServerMessageById0,
).flatMap { it.entries.map { entry -> entry.key to entry.value } }.toMap()
private val ClientToServerParserById0: Map<Int, Parser<out GeneratedMessage>> = mapOf(
1 to ProtoSystem.PingReq.parser(),
2 to ProtoSystem.GmReq.parser(),
3 to ProtoTest.TestReq.parser(),
10_001 to ProtoLogin.LoginReq.parser()
)
public val ClientToServerParserById: Map<Int, Parser<out GeneratedMessage>> = listOf(
ClientToServerParserById0,
).flatMap { it.entries.map { entry -> entry.key to entry.value } }.toMap()
public enum class CSEnum(
public val id: Int,
) {
PingReq(1),
GmReq(2),
TestReq(3),
LoginReq(10001),
;
public companion object {
private val entriesById: Map<Int, CSEnum> = entries.associateBy { it.id }
public operator fun `get`(id: Int): CSEnum = requireNotNull(entriesById[id]) { "$id not found" }
}
}
与之对应的,还有 ServerToClient
,代码是自动生成的,不需要我们手动去写,这个流程会在 generateProto
这个任务之后做:
tasks.register<JavaExec>("generateProtoMeta") {
group = "other"
mainClass = "com.mikai233.protocol.MessageMetaGeneratorKt"
classpath = sourceSets["main"].runtimeClasspath
}
tasks.named("generateProto") {
finalizedBy("generateProtoMeta")
}
代码生成部分的逻辑就不看了,还是比较多的。